diff --git a/D2BotSoloPlay.dbj b/D2BotSoloPlay.dbj
index e24e1f33..a2a2017d 100644
--- a/D2BotSoloPlay.dbj
+++ b/D2BotSoloPlay.dbj
@@ -1,13 +1,17 @@
-/* eslint-disable no-fallthrough */
/**
* @filename D2BotSoloPlay.dbj
* @author theBGuy
* @desc Entry script for SoloPlay leveling system
*
+*
+* @typedef {import("./sdk/globals")}
+* @typedef {import("./libs/SoloPlay/globals")}
*/
-include("StarterConfig.js");
-// D2BotSoloPlay specific settings - for global settings see libs/StarterConfig.js
+// No touchy!
+include("critical.js"); // required
+
+// D2BotSoloPlay specific settings - for global settings see libs/starter/StarterConfig.js
Starter.Config.InvalidPasswordDelay = 10; // Minutes to wait after getting Invalid Password message
Starter.Config.GameDoesNotExistTimeout = 600; // Seconds to wait before cancelling the 'Game does not exist.' screen
Starter.Config.DelayBeforeLogin = rand(5, 25); // Seconds to wait before logging in
@@ -20,6 +24,7 @@ Starter.Config.GlobalAccountPassword = ""; // Set value for a global password fo
// Override default values for StarterConfig under here by following format
// Starter.Config.ValueToChange = value; // Example: Starter.Config.MinGameTime = 500; // changes MinGameTime to 500 seconds
+// Profile().type === sdk.game.profiletype.SinglePlayer && (Starter.Config.CrashDelay = 2);
/**
* @todo
@@ -28,398 +33,115 @@ Starter.Config.GlobalAccountPassword = ""; // Set value for a global password fo
* - need to handle someone using a premade account
*/
-// No touchy!
-include("polyfill.js");
-include("json2.js");
-include("OOG.js");
-include("automule.js");
-include("gambling.js");
-include("craftingsystem.js");
-include("torchsystem.js");
-include("common/util.js");
-include("common/misc.js");
-include("common/pather.js");
-include("common/prototypes.js");
+// the only things we really need from these are their oog checks
+includeSystemLibs();
+
+// solo specific
include("SoloPlay/Tools/Developer.js");
include("SoloPlay/Tools/CharData.js");
include("SoloPlay/Tools/Tracker.js");
-include("SoloPlay/Tools/NameGen.js");
include("SoloPlay/Tools/OOGOverrides.js");
-include("SoloPlay/Functions/SoloEvents.js");
-include("SoloPlay/Functions/ConfigOverrides.js");
-let Controls = require("./modules/Control");
-let Overrides = require("./modules/Override");
+// is this needed? soloplay doesn't run in default.dbj anymore
+include("SoloPlay/Functions/ConfigOverrides.js");
-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]);
}
-
-let joinInfo;
-let gameTracker;
-Starter.BNET = ([sdk.game.profiletype.Battlenet, sdk.game.profiletype.OpenBattlenet].includes(Profile().type));
-const charClassMap = {"ZON": "amazon", "SOR": "sorceress", "NEC": "necromancer", "PAL": "paladin", "BAR": "barbarian", "DRU": "druid", "SIN": "assassin"};
+delete Starter.AdvancedConfig;
// initialize data files
if (!FileTools.exists("data/" + me.profile + ".json") && DataFile.create()) {
- Starter.firstRun = true;
- delay(Math.floor(rand(1, 20)));
-}
-
-if (!FileTools.exists(CharData.filePath) && CharData.create()) {
- delay(Math.floor(rand(1, 20)));
-}
-
-if (!FileTools.exists(CharData.filePath) && CharData.loginData.create()) {
- delay(Math.floor(rand(1, 20)));
+ Starter.firstRun = true;
}
+!FileTools.exists(CharData.filePath) && CharData.create();
+!FileTools.exists(CharData.login.filePath) && CharData.login.create();
Developer.logPerformance && Tracker.initialize();
-new Overrides.Override(Starter, Starter.receiveCopyData, function (orignal, mode, msg) {
- switch (mode) {
- case 1: // Join Info
- console.log("Got Join Info");
- joinInfo = JSON.parse(msg);
-
- SoloEvents.gameInfo.gameName = joinInfo.gameName.toLowerCase();
- SoloEvents.gameInfo.gamePass = joinInfo.gamePass.toLowerCase();
-
- break;
- case 1638:
- try {
- let obj = JSON.parse(msg);
- Starter.profileInfo.profile = me.profile.toUpperCase();
- Starter.profileInfo.account = obj.Account;
- Starter.profileInfo.password = "";
- Starter.profileInfo.charName = obj.Character;
- Starter.profileInfo.tag = (obj.Tag.trim().capitalize(true) || "");
- Starter.profileInfo.difficulty = obj.Difficulty;
- obj.Realm = obj.Realm.toLowerCase();
- Starter.profileInfo.realm = ["east", "west"].includes(obj.Realm) ? "us" + obj.Realm : obj.Realm;
-
- let buildCheck = Starter.profileInfo.profile.split("-"); // SCL-ZON123
- Starter.profileInfo.hardcore = buildCheck[0].includes("HC"); // SC softcore = false
- Starter.profileInfo.expansion = buildCheck[0].indexOf("CC") === -1; // not CC so not classic - true
- Starter.profileInfo.ladder = buildCheck[0].indexOf("NL") === -1; // not NL so its ladder - true
-
- if (buildCheck.length <= 1) {
- D2Bot.printToConsole('Please update profile name. Example: "HCCNL-PAL" will make a Hardcore Classic NonLadder Paladin', sdk.colors.D2Bot.Gold);
- D2Bot.printToConsole("If you are still confused please read the included readMe. https://github.com/blizzhackers/kolbot-SoloPlay/blob/main/README.md", sdk.colors.D2Bot.Gold);
- D2Bot.stop();
- }
-
- buildCheck[1] = buildCheck[1].toString().substring(0, 3);
-
- if (charClassMap[buildCheck[1]]) {
- Starter.profileInfo.charClass = charClassMap[buildCheck[1]];
- } else {
- throw new Error("Invalid profile name, couldn't set character class");
- }
-
- if (Starter.profileInfo.tag !== "") {
- {
- let soloStats = CharData.getStats();
-
- if (!soloStats.me.finalBuild || soloStats.me.finalBuild !== Starter.profileInfo.tag) {
- D2Bot.setProfile(null, null, null, null, null, Starter.profileInfo.tag);
- soloStats.me.finalBuild = Starter.profileInfo.tag;
- soloStats.me.charms = {};
- CharData.updateData("me", soloStats);
- }
-
- if (!["Start", "Stepping", "Leveling"].includes(soloStats.me.currentBuild) && soloStats.me.currentBuild !== soloStats.me.finalBuild) {
- soloStats.me.currentBuild = "Leveling";
- soloStats.me.charms = {};
- CharData.updateData("me", soloStats);
- }
- }
- } else {
- throw new Error("Please update profile InfoTag. Missing the finalBuild.");
- }
- } catch (e) {
- Misc.errorReport(e, "D2BotSoloPlay.dbj");
- D2Bot.stop();
- }
-
- break;
- default:
- orignal(mode, msg);
- }
-}).apply();
-
-new Overrides.Override(Starter, Starter.scriptMsgEvent, function (orignal, msg) {
- if (typeof msg !== "string") return;
- if (msg === "event") {
- SoloEvents.check = true;
- } else if (msg === "diffChange") {
- Starter.checkDifficulty();
- } else {
- orignal(msg);
- }
-}).apply();
-
-function timer (tick) {
- const currInGame = (getTickCount() - tick);
- let timeStr = " (Time: " + Time.format(currInGame) + ") ";
-
- if (Developer.displayClockInConsole && Developer.logPerformance) {
- try {
- gameTracker === undefined && (gameTracker = Developer.readObj(Tracker.GTPath));
- let [tTime, tInGame, tDays] = [(gameTracker.Total + currInGame), (gameTracker.InGame + currInGame), (gameTracker.Total + currInGame)];
- let [totalTime, totalInGame, totalDays] = [Developer.formatTime(tTime), Developer.formatTime(tInGame), Developer.totalDays(tDays)];
- timeStr += ("(Days: " + totalDays + ") (Total: " + totalTime + ") (IG: " + totalInGame + ") (OOG: " + Developer.formatTime(gameTracker.OOG) + ")");
- } catch (e) {
- console.log(e);
- }
- }
- return timeStr;
-}
-
-const oogCheck = () => (AutoMule.outOfGameCheck() || TorchSystem.outOfGameCheck() || Gambling.outOfGameCheck() || CraftingSystem.outOfGameCheck() || SoloEvents.outOfGameCheck());
-
-const locations = {};
-locations[sdk.game.locations.PreSplash] = () => ControlAction.click();
-locations[sdk.game.locations.GatewaySelect] = () => Controls.GatewayCancel.click();
-locations[sdk.game.locations.SplashScreen] = () => Starter.LocationEvents.login();
-locations[sdk.game.locations.MainMenu] = () => Starter.LocationEvents.login();
-locations[sdk.game.locations.Login] = () => Starter.LocationEvents.login();
-locations[sdk.game.locations.OtherMultiplayer] = () => Starter.LocationEvents.otherMultiplayerSelect();
-locations[sdk.game.locations.TcpIp] = () => Profile().type === sdk.game.profiletype.TcpIpHost ? Controls.TcpIpHost.click() : Controls.TcpIpCancel.click();
-locations[sdk.game.locations.TcpIpEnterIp] = () => Controls.TcpIpCancel.click();
-locations[sdk.game.locations.LoginError] = () => Starter.LocationEvents.loginError();
-locations[sdk.game.locations.LoginUnableToConnect] = () => Starter.LocationEvents.unableToConnect();
-locations[sdk.game.locations.TcpIpUnableToConnect] = () => Starter.LocationEvents.unableToConnect();
-locations[sdk.game.locations.CdKeyInUse] = () => Starter.LocationEvents.loginError();
-locations[sdk.game.locations.InvalidCdKey] = () => Starter.LocationEvents.loginError();
-locations[sdk.game.locations.RealmDown] = () => Starter.LocationEvents.realmDown();
-locations[sdk.game.locations.Disconnected] = () => {
- ControlAction.timeoutDelay("Disconnected", 3000);
- Controls.OkCentered.click();
-};
-locations[sdk.game.locations.RegisterEmail] = () => Controls.EmailDontRegisterContinue.control ? Controls.EmailDontRegisterContinue.click() : Controls.EmailDontRegister.click();
-locations[sdk.game.locations.MainMenuConnecting] = (loc) => !Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, loc) && Controls.LoginCancelWait.click();
-locations[sdk.game.locations.CharSelectPleaseWait] = (loc) => !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, loc) && Controls.OkCentered.click();
-locations[sdk.game.locations.CharSelect] = (loc) => Starter.LocationEvents.charSelect(loc);
-locations[sdk.game.locations.CharSelectConnecting] = (loc) => Starter.LocationEvents.charSelect(loc);
-locations[sdk.game.locations.CharSelectNoChars] = (loc) => Starter.LocationEvents.charSelect(loc);
-locations[sdk.game.locations.SelectDifficultySP] = () => Starter.LocationEvents.selectDifficultySP();
-locations[sdk.game.locations.CharacterCreate] = (loc) => !Starter.locationTimeout(5e3, loc) && Controls.CharSelectExit.click();
-locations[sdk.game.locations.ServerDown] = () => {
- ControlAction.timeoutDelay("Server Down", Time.minutes(5));
- Controls.OkCentered.click();
-};
-locations[sdk.game.locations.LobbyPleaseWait] = (loc) => !Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, loc) && Controls.OkCentered.click();
-locations[sdk.game.locations.Lobby] = () => {
- D2Bot.updateStatus("Lobby");
- ControlAction.saveInfo(Starter.profileInfo);
-
- me.blockKeys = false;
-
- !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 !== "" && Controls.LobbyEnterChat.click()) return;
-
- if (Starter.inGame || Starter.gameInfo.error) {
- !Starter.gameStart && (Starter.gameStart = DataFile.getStats().ingameTick);
-
- if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3 && !joinInfo) {
- ControlAction.timeoutDelay("Min game time wait", Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount());
- }
- }
-
- if (Starter.inGame) {
- if (oogCheck()) return;
-
- 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();
-};
-locations[sdk.game.locations.LobbyChat] = () => Starter.LocationEvents.lobbyChat();
-locations[sdk.game.locations.CreateGame] = (loc) => {
- ControlAction.timeoutDelay("Create Game Delay", Starter.Config.DelayBeforeLogin * 1e3);
- 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());
-
- D2Bot.requestGameInfo();
- delay(500);
-
- // todo - really don't need use profiles set difficulty for online. Only single player so re-write difficulty stuff
- Starter.checkDifficulty();
-
- Starter.gameInfo.gameName = DataFile.getStats().gameName;
- Starter.gameInfo.gamePass = Starter.randomString(5, true);
-
- switch (true) {
- case Starter.gameInfo.gameName === "":
- case Starter.gameInfo.gameName === "Name":
- Starter.gameInfo.gameName = Starter.profileInfo.charName.substring(0, 7) + "-" + Starter.randomString(3, false) + "-";
-
- break;
- }
-
- // 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();
- }
-
- ControlAction.createGame((Starter.gameInfo.gameName + Starter.gameCount), Starter.gameInfo.gamePass, Starter.gameInfo.difficulty, Starter.Config.CreateGameDelay * 1000);
- Starter.lastGameStatus = "pending";
- Starter.setNextGame(Starter.gameInfo);
- Starter.locationTimeout(10000, loc);
-};
-locations[sdk.game.locations.GameNameExists] = () => {
- Controls.CreateGameWindow.click();
- Starter.gameCount += 1;
- Starter.lastGameStatus = "ready";
-};
-locations[sdk.game.locations.WaitingInLine] = () => Starter.LocationEvents.waitingInLine();
-locations[sdk.game.locations.JoinGame] = () => Starter.LocationEvents.openCreateGameWindow();
-locations[sdk.game.locations.Ladder] = () => Starter.LocationEvents.openCreateGameWindow();
-locations[sdk.game.locations.ChannelList] = () => Starter.LocationEvents.openCreateGameWindow();
-locations[sdk.game.locations.LobbyLostConnection] = () => {
- ControlAction.timeoutDelay("LostConnection", 3000);
- Controls.OkCentered.click();
-};
-locations[sdk.game.locations.GameDoesNotExist] = () => Starter.LocationEvents.gameDoesNotExist();
-locations[sdk.game.locations.GameIsFull] = () => Starter.LocationEvents.openCreateGameWindow();
-
function main () {
- debugLog(me.profile);
- addEventListener("copydata", Starter.receiveCopyData);
- addEventListener("scriptmsg", Starter.scriptMsgEvent);
-
- let oogTick = getTickCount();
-
- while (!Starter.handle) {
- delay(100);
- }
-
- DataFile.updateStats("handle", Starter.handle);
- delay(500);
-
- while (!D2Bot.init()) {
- delay(250);
- }
- load("tools/heartbeat.js");
-
- while (!Object.keys(Starter.gameInfo).length) {
- D2Bot.requestGameInfo();
- delay(500);
- }
-
- if (Profile().type === sdk.game.profiletype.TcpIpJoin) {
- D2Bot.printToConsole("TcpJoin is unsupported.");
- D2Bot.stop();
- }
-
- // Developer.logPerformance && Tracker.checkValidity();
- Starter.gameCount = (DataFile.getStats().runs + 1 || 1);
-
- if (Starter.gameInfo.error) {
- ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3);
- Starter.BNET && 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);
- // console.log(Starter.profileInfo);
- }
-
- if (Starter.profileInfo.charName === "") {
- console.log("Generating Character Name");
- Starter.profileInfo.charName = NameGen();
- delay(50);
- }
-
- let resPenalty, areaName, diffName;
- const xp = () => me.getStat(sdk.stats.Experience) > 0 ? Experience.progress() : 0;
- const tGold = () => me.getStat(sdk.stats.Gold) + me.getStat(sdk.stats.GoldBank);
- const fireRes = (resPenalty) => Math.min(75 + me.getStat(sdk.stats.MaxFireResist), me.getStat(sdk.stats.FireResist) - resPenalty);
- const coldRes = (resPenalty) => Math.min(75 + me.getStat(sdk.stats.MaxColdResist), me.getStat(sdk.stats.ColdResist) - resPenalty);
- const lightRes = (resPenalty) => Math.min(75 + me.getStat(sdk.stats.MaxLightResist), me.getStat(sdk.stats.LightResist) - resPenalty);
- const poisRes = (resPenalty) => Math.min(75 + me.getStat(sdk.stats.MaxPoisonResist), me.getStat(sdk.stats.PoisonResist) - resPenalty);
-
- 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;
- resPenalty = me.gametype === 0 ? [0, 20, 50][me.diff] : [0, 40, 100][me.diff];
- diffName = ["Norm", "Night", "Hell"][me.diff];
- DataFile.updateStats("runs", Starter.gameCount);
- DataFile.updateStats("ingameTick");
- Developer.logPerformance && Tracker.update((getTickCount() - oogTick));
- Developer.displayClockInConsole && (gameTracker = Developer.readObj(Tracker.GTPath));
- oogTick = 0;
- }
-
- if (me.ingame && me.gameReady) {
- let statusString = "";
-
- try {
- let [exp, myGold, fr, cr, lr, pr] = [xp(), tGold(), fireRes(resPenalty), coldRes(resPenalty), lightRes(resPenalty), poisRes(resPenalty)];
- areaName = !!me.area ? Pather.getAreaName(me.area) : "";
- statusString = me.name + " | Lvl: " + me.charlvl + " (" + exp + "%) (Diff: " + diffName + ") (A: " + areaName + ") (G: " + myGold + ") (F: " + fr + "/C: " + cr + "/L: " + lr + "/P: " + pr + ")";
- } catch (e) {
- console.error(e);
- }
-
- D2Bot.updateStatus(statusString + timer(Starter.gameStart));
- }
- }
-
- delay(1000);
- }
-
- // was in game so start recording oog time
- Starter.inGame && oogTick === 0 && (oogTick = getTickCount());
- Starter.isUp = "no";
-
- try {
- let loc = getLocation();
- (locations[loc] !== undefined) && locations[loc](loc);
- } catch (e) {
- console.error(e, "LOCATION: " + getLocation());
- }
- delay(1000);
- }
+ debugLog(me.profile);
+ addEventListener("copydata", Starter.receiveCopyData);
+ addEventListener("scriptmsg", Starter.scriptMsgEvent);
+
+ let oogTick = getTickCount();
+
+ while (!Starter.handle) {
+ delay(3);
+ }
+
+ DataFile.updateStats("handle", Starter.handle);
+ D2Bot.handle = Starter.handle;
+ delay(500);
+
+ load("threads/heartbeat.js");
+
+ if (Profile().type === sdk.game.profiletype.TcpIpJoin) {
+ D2Bot.printToConsole("TcpJoin is unsupported.");
+ D2Bot.stop();
+ }
+
+ Starter.gameCount = (DataFile.getStats().runs + 1 || 1);
+
+ while (!Object.keys(Starter.gameInfo).length) {
+ delay(rand(200, 1500));
+ D2Bot.requestGameInfo();
+ delay(500);
+ }
+
+ if (Starter.gameInfo.error) {
+ ControlAction.timeoutDelay("Crash Delay", Starter.Config.CrashDelay * 1e3);
+ Starter.BNET && D2Bot.updateRuns();
+ }
+
+ DataFile.updateStats("debugInfo", JSON.stringify({ currScript: "none", area: "out of game" }));
+
+ while (!Object.keys(Starter.profileInfo).length) {
+ delay(rand(200, 1500));
+ D2Bot.getProfile();
+ 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");
+ Developer.logPerformance && Tracker.update((getTickCount() - oogTick));
+ oogTick = 0;
+ D2Bot.updateStatus("In-Game :: Initializing threads...");
+ } else {
+ // Tracker
+ if (Developer.logPerformance) {
+ if (getTickCount() - Tracker.tick > Time.minutes(3)) {
+ Tracker.tick = getTickCount();
+
+ try {
+ Tracker.update();
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ }
+ }
+ }
+
+ delay(1000);
+ }
+
+ // was in game so start recording oog time
+ Starter.inGame && oogTick === 0 && (oogTick = getTickCount());
+ Starter.isUp = "no";
+
+ LocationAction.run();
+ delay(1000);
+ }
}
diff --git a/README.md b/README.md
index 9f230f1b..49b6c352 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
![extract into](https://i.imgur.com/TcRmoRm.png)
-*- "the one bot to rule them all."*
+*"the one bot to rule them all."*
-
+For a very quick kolbot+soloplay installer check out https://github.com/theBGuy/kolbot-soloplay-installer
# Table of contents
- [What is SoloPlay?](#the-big-question-what-is-kolbot-soloplay)
@@ -13,8 +13,7 @@
- [Install-Guide](#install-guide)
- [Extras](#extras)
- [Discord](#discord)
-
-
+- [Donate](#donate)
# The big question, What is Kolbot-SoloPlay?
- SoloPlay is a D2BS based auto-play system to level any single legacy diablo 2 character class from 1-99. That sounds like a bunch of verbage so let me break it down a bit. D2BS stands for ``Diablo 2 Botting System`` if you are familiar with Kolbot that is what you are using. SoloPlay only works for Diablo 2, so before you ask it is not for Diablo 2 Resurrected. Alright next, what is an auto-play system? In simple terms, SoloPlay works by set it and forget philosophy. It is profile driven so the only thing you need to worry about is filling out the profile with the correct format and then press start (how to set up the profile is defined below). After that it takes care of the rest, no setting up config files or settings files (like sonic or horde), ect. The goal is to be the fastest leveling system there is across all modes (classic/expansion, hardcore/softcore, ladder/non-ladder)
@@ -75,11 +74,15 @@
### **Q: When will the bot change to the final build I selected?**
**A:** In classic, the bot will switch to the final build after it defeats diablo and meets a level requirement.
-In expansion, it transitions to the final build when final gear requirements are met ``(Navigate to libs\SoloPlay\``[``BuildFiles``](libs/SoloPlay/BuildFiles/)) and look for the file with the name of the final build you choose to see what items are needed for each build and what level is required for classic).
+In expansion, it transitions to the final build when final gear requirements are met ``(Navigate to libs\SoloPlay\``[``BuildFiles\``](libs/SoloPlay/BuildFiles/)) and look for the file with the name of the final build you choose to see what items are needed for each build and what level is required for classic.
+
+### **Q: Where can I see what items are used in the final build I selected?**
+
+**A:** Same place as above answer ``(Navigate to libs\SoloPlay\``[``BuildFiles\``](libs/SoloPlay/BuildFiles/))
### **Q: The bot has beaten diablo (classic) / baal, so why isn't moving on to the next difficulty?**
-**A:** The bot will only progress once it has reached a minimum character level (`navigate to libs\SoloPlay\Config\classname.js` and see `Config.levelCap` for level requirments) and will not start the next difficulty with negative resistances. If the bot is more than 5 levels higher than the minimum character level and has not reached the required resistances, it will automatically move to the next difficulty.
+**A:** The bot will only progress once it has reached a minimum character level (``navigate to libs\SoloPlay\``[``BuildFiles``](libs/SoloPlay/BuildFiles/)``\classname\classname.js`` and see `CharInfo.levelCap` for level requirments) and will not start the next difficulty with negative resistances. If the bot is more than 5 levels higher than the minimum character level and has not reached the required resistances, it will automatically move to the next difficulty.
### **Q: How can I run more than one of the same class?**
@@ -87,7 +90,7 @@ In expansion, it transitions to the final build when final gear requirements are
### **Q: HELP!!! There is an error when starting the bot?**
-**A:** There was a bad installation OR the profile settings are wrong. First verify that you using the kolbot version linked the install guide below. Next, confirm you have installed all the files into their proper locations (including overwriting the existing `default.dbj`). Finally, verify the profile name and infotag follow the format of the install guide's instructions.
+**A:** There was a bad installation OR the profile settings are wrong. First verify that you using the kolbot version linked the install guide below. Next, confirm you have installed all the files into their proper locations. Finally, verify the profile name and infotag follow the format of the install guide's instructions.
### **Q: HELP!!! The bot auto created my account and I can't find the password!**
@@ -101,6 +104,14 @@ In expansion, it transitions to the final build when final gear requirements are
**A:** A Bumper is a level 40 character that has not done baal quest in normal and is used to "bump" low level characters to hell difficulty where they can power level following chaos runs.
+### **Q: Can I enable my own Pickit files?**
+
+**A:** Yes, similar to how core kolbot works open the class config file located `libs\SoloPlay\Config\` and look for the section header.
+
+`/* Pickit configuration. */`
+
+add your pickit files here or uncomment the kolton nip already present.
+
### **Q: Does this work for Diablo 2 Resurected?**
**A:** No, Kolbot does not work with d2r and SoloPlay runs using Kolbot. SoloPlay only works on diablo 2.
@@ -116,10 +127,11 @@ In expansion, it transitions to the final build when final gear requirements are
| Step | Instructions | |
|:------:|:-------|-------:|
-| 1.| Download Kolbot here: [github.com/blizzhackers/kolbot](https://github.com/blizzhackers/kolbot). |![blizzhackers github](https://i.imgur.com/RksqKEA.jpg) |
+| 1.| Download Kolbot here: [github.com/blizzhackers/kolbot](https://github.com/blizzhackers/kolbot). |![blizzhackers github](https://github.com/user-attachments/assets/0667e3f0-06ba-4ca7-b3c4-187e0c9c289c) |
| 2.| Click the green button to Download SoloPlay. |![enter image description here](https://i.imgur.com/cNqZDbW.jpg) |
-| 3.a| Copy and paste the following: `default.dbj`, `D2BotSoloPlay.dbj`, and the entire `\libs` folder into `\d2bs\kolbot\`.| ![kolbot](https://i.imgur.com/WNxJOhq.png) |
-|3.b|A successful installation will show 1 new file in the folder: `D2BotSoloPlay.dbj` and look similar to the following image|![image](https://user-images.githubusercontent.com/60308670/131760184-ba777302-908e-4247-b9b7-1c9331028b2c.png)| 4.| Select Add for new a Kolbot Profile. | ![Add-profile.jpg](https://imgur.com/tHs9ZoH.jpg)|
+| 3.a| Copy and paste the following: `D2BotSoloPlay.dbj`, and the entire `\libs` folder into `\d2bs\kolbot\`.| ![image](https://github.com/user-attachments/assets/b213af6f-9088-4d44-8584-889365f23b7c)|
+|3.b|A successful installation will show 1 new file in the folder: `D2BotSoloPlay.dbj` and look similar to the following image|![image](https://github.com/user-attachments/assets/d5ef5093-7045-4407-a430-136b972417ab)
+| 4.| Select Add for new a Kolbot Profile. | ![Add-profile.jpg](https://imgur.com/tHs9ZoH.jpg)|
| 4.a| Select and Input a profile name. See the **[Possible Profile Name Choices](#possible-profile-names)** below for a list of available options. | ![extract into](https://i.imgur.com/2YcGKVH.png) |
| 4.b| ***Optional*** Input your account name. If no name than a random account is created. | |
| 4.c|***Optional*** Input your account password. If no name than a random password is created. | |
@@ -193,8 +205,18 @@ https://youtu.be/qYHUw6nNn74
- If you have any questions please join me on my discord
https://discord.gg/5pjTC2zH6N
+## Contributing
+Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
+
![Kolbot-SoloPlay Paladin](https://user-images.githubusercontent.com/60308670/165398785-8ef1afd7-d232-4bc4-a23e-a1f1321ce0ed.png)
+## Donate
+
+
+
+
+[![Donate](https://img.shields.io/badge/PayPal-Donate-blue.svg?style=for-the-badge&logo=paypal)](https://www.paypal.com/donate/?business=Y9S4AWX9QUZXW&no_recurring=0¤cy_code=USD)
+
## Statistics (will become filled out as data becomes available)
| Level | Amazon | Sorceress | Necromancer | Paladin | Barbarian | Druid | Assassin |
|:------:|:------:|:-------:|:-------:|:------:|:------:|:------:|:-----:|
@@ -206,8 +228,6 @@ https://discord.gg/5pjTC2zH6N
## Brief History
Kolbot-SoloPlay was built off the base structure of SoloLeveling by isid0re. Autoplay scripts/systems aren't a new concept, some to note are sonic, autoplay, and AutoSorc. None of the existing ones were able to do other character classes though so SoloLeveling was created by modding Questing.js. Almost from the beginning, Isid0re and I were bouncing ideas off each other. At that time, I was working on a separate project. We discussed ideas that helped both of our projects. I officially joined in around 4 months or so after the Github repo went public and was actively involved in the project until 6/30/2021. I contributed updates including but not limited to: item based respec, the overlay, logging equipped items, showing tier values on items, many bug fixes, sorting, D2BotSoloCleaner, performance tracking, ect. Due to some personal conflicts between isid0re and myself, I decided to create GuysSoloLeveling to have all of my ideas in one place. On 6/30/2021 I created this repo and on 7/13/2021 I made it public. On 9/1/2021, I changed the name to Kolbot-SoloPlay after some major changes in structure and continue to update to make SoloPlay the best leveling system for legacy diablo 2.
-## Contributing
-Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
## License
[GPL-3.0](https://choosealicense.com/licenses/gpl-3.0/)
diff --git a/default.dbj b/default.dbj
deleted file mode 100644
index 8cafbace..00000000
--- a/default.dbj
+++ /dev/null
@@ -1,276 +0,0 @@
-/**
-* @filename default.dbj
-* @author kolton
-* @desc gets executed upon gamejoin, main thread for bot
-*
-*/
-js_strict(true);
-
-include("json2.js");
-include("NTItemParser.dbl");
-include("OOG.js");
-include("AutoMule.js");
-include("Gambling.js");
-include("CraftingSystem.js");
-include("TorchSystem.js");
-include("MuleLogger.js");
-include("GameAction.js");
-include("common/util.js");
-
-includeCommonLibs();
-
-function main () {
- D2Bot.init(); // Get D2Bot# handle
- D2Bot.ingame();
-
- (function (global, original) {
- global.load = function (...args) {
- console.trace();
- 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");
-
- // SoloPlay runs in it's own thread
- if (getScript("D2BotSoloPlay.dbj")) {
- load("libs/SoloPlay/SoloPlay.js");
- return true;
- }
-
- 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")) {
- include("ItemDB.js");
- load("tools/AreaWatcher.js");
-
- while (true) {
- delay(1000);
- }
- }
-
- let sojPause;
- let sojCounter = 0;
- let startTime = getTickCount();
-
- this.scriptEvent = function (msg) {
- 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.DebugMode && FileTools.exists("libs/modules/Guard")) {
- require("libs/modules/Guard");
- }
-
- 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;
- }
- }
-
- 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");
- }
-
- scriptBroadcast("quit");
-
- return true;
-}
diff --git a/libs/SoloPlay/BuildFiles/Runewords/AncientsPledge.js b/libs/SoloPlay/BuildFiles/Runewords/AncientsPledge.js
index 7af7dd3f..c8b69cc9 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/AncientsPledge.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/AncientsPledge.js
@@ -1,44 +1,44 @@
-(function() {
- if (!me.checkItem({name: sdk.locale.items.AncientsPledge}).have && !me.hell) {
- // Cube to Ort rune
- if (me.normal && !me.getItem(sdk.items.runes.Ort)) {
- Config.Recipes.push([Recipe.Rune, "Ral Rune"]);
- }
+(function () {
+ if (!me.checkItem({ name: sdk.locale.items.AncientsPledge }).have && !me.hell) {
+ // Cube to Ort rune
+ if (me.normal && !me.getItem(sdk.items.runes.Ort)) {
+ Config.Recipes.push([Recipe.Rune, "Ral Rune"]);
+ }
- const apRunes = [
- "[name] == RalRune # # [maxquantity] == 1",
- "[name] == OrtRune # # [maxquantity] == 1",
- "[name] == TalRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(apRunes);
- }
+ const apRunes = [
+ "[name] == RalRune # # [maxquantity] == 1",
+ "[name] == OrtRune # # [maxquantity] == 1",
+ "[name] == TalRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(apRunes);
+ }
- const apShields = [
- "me.normal && [name] == largeshield && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
- "!me.hell && [name] == kiteshield && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
- "([name] == dragonshield || [name] == scutum) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(apShields);
+ const apShields = [
+ "me.normal && [name] == largeshield && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
+ "!me.hell && [name] == kiteshield && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
+ "([name] == dragonshield || [name] == scutum) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(apShields);
- if (me.paladin) {
- NTIP.addLine("([name] == targe || [name] == rondache || [name] == heraldicshield || [name] == aerinshield || [name] == akarantarge || [name] == akaranrondache || [name] == gildedshield ||[name] == protectorshield || [name] == sacredtarge) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [fireresist] > 0 && [sockets] == 3");
- Config.Runewords.push([Runeword.AncientsPledge, "targe"]);
- Config.Runewords.push([Runeword.AncientsPledge, "rondache"]);
- Config.Runewords.push([Runeword.AncientsPledge, "heraldicshield"]);
- Config.Runewords.push([Runeword.AncientsPledge, "aerinshield"]);
- Config.Runewords.push([Runeword.AncientsPledge, "akarantarge"]);
- Config.Runewords.push([Runeword.AncientsPledge, "akaranrondache"]);
- Config.Runewords.push([Runeword.AncientsPledge, "protectorshield"]);
- Config.Runewords.push([Runeword.AncientsPledge, "gildedshield"]);
- Config.Runewords.push([Runeword.AncientsPledge, "sacredtarge"]);
+ if (me.paladin) {
+ NTIP.addLine("([name] == targe || [name] == rondache || [name] == heraldicshield || [name] == aerinshield || [name] == akarantarge || [name] == akaranrondache || [name] == gildedshield ||[name] == protectorshield || [name] == sacredtarge) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [fireresist] > 0 && [sockets] == 3");
+ Config.Runewords.push([Runeword.AncientsPledge, "targe"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "rondache"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "heraldicshield"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "aerinshield"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "akarantarge"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "akaranrondache"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "protectorshield"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "gildedshield"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "sacredtarge"]);
- Config.KeepRunewords.push("[type] == auricshields # [fireresist]+[lightresist]+[coldresist]+[poisonresist] >= 187");
- }
+ Config.KeepRunewords.push("[type] == auricshields # [fireresist]+[lightresist]+[coldresist]+[poisonresist] >= 187");
+ }
- Config.Runewords.push([Runeword.AncientsPledge, "dragonshield"]);
- Config.Runewords.push([Runeword.AncientsPledge, "scutum"]);
- Config.Runewords.push([Runeword.AncientsPledge, "kiteshield"]);
- Config.Runewords.push([Runeword.AncientsPledge, "largeshield"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "dragonshield"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "scutum"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "kiteshield"]);
+ Config.Runewords.push([Runeword.AncientsPledge, "largeshield"]);
- Config.KeepRunewords.push("[type] == shield # [fireresist]+[lightresist]+[coldresist]+[poisonresist] >= 187");
+ Config.KeepRunewords.push("[type] == shield # [fireresist]+[lightresist]+[coldresist]+[poisonresist] >= 187");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Bone.js b/libs/SoloPlay/BuildFiles/Runewords/Bone.js
index b7080c6a..2bfcec55 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Bone.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Bone.js
@@ -1,33 +1,33 @@
-(function() {
- const Bone = [
- "[name] == UmRune # # [maxquantity] == 2",
- "[name] == SolRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Bone);
+(function () {
+ const Bone = [
+ "[name] == UmRune # # [maxquantity] == 2",
+ "[name] == SolRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Bone);
- // Cube to Um Rune
- if (Item.getQuantityOwned(me.getItem(sdk.items.runes.Um)) < 2) {
- Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
- Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- }
+ // Cube to Um Rune
+ if (me.getOwned({ classid: sdk.items.runes.Um }).length < 2) {
+ Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ }
- // Have Um rune before looking for base
- if (me.getItem(sdk.items.runes.Um)) {
- NTIP.addLine(
- "([name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == lightplate || [name] == mageplate || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1"
- );
- }
+ // Have Um rune before looking for base
+ if (me.getItem(sdk.items.runes.Um)) {
+ NTIP.addLine(
+ "([name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == lightplate || [name] == mageplate || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1"
+ );
+ }
- Config.Runewords.push([Runeword.Bone, "demonhidearmor"]);
- Config.Runewords.push([Runeword.Bone, "duskshroud"]);
- Config.Runewords.push([Runeword.Bone, "ghostarmor"]);
- Config.Runewords.push([Runeword.Bone, "lightplate"]);
- Config.Runewords.push([Runeword.Bone, "mageplate"]);
- Config.Runewords.push([Runeword.Bone, "serpentskinarmor"]);
- Config.Runewords.push([Runeword.Bone, "trellisedarmor"]);
- Config.Runewords.push([Runeword.Bone, "wyrmhide"]);
+ Config.Runewords.push([Runeword.Bone, "demonhidearmor"]);
+ Config.Runewords.push([Runeword.Bone, "duskshroud"]);
+ Config.Runewords.push([Runeword.Bone, "ghostarmor"]);
+ Config.Runewords.push([Runeword.Bone, "lightplate"]);
+ Config.Runewords.push([Runeword.Bone, "mageplate"]);
+ Config.Runewords.push([Runeword.Bone, "serpentskinarmor"]);
+ Config.Runewords.push([Runeword.Bone, "trellisedarmor"]);
+ Config.Runewords.push([Runeword.Bone, "wyrmhide"]);
- Config.KeepRunewords.push("[type] == armor # [fireresist] == 30 && [normaldamagereduction] == 7");
+ Config.KeepRunewords.push("[type] == armor # [fireresist] == 30 && [normaldamagereduction] == 7");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/BreathOfTheDying.js b/libs/SoloPlay/BuildFiles/Runewords/BreathOfTheDying.js
index cc15d0d5..746a0972 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/BreathOfTheDying.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/BreathOfTheDying.js
@@ -1,22 +1,35 @@
-(function() {
- const BoTD = [
- "[name] == VexRune",
- "me.diff == 2 && [name] == HelRune # # [maxquantity] == 1",
- "[name] == ElRune # # [maxquantity] == 1",
- "[name] == EldRune # # [maxquantity] == 1",
- "[name] == ZodRune",
- "me.diff == 2 && [name] == EthRune # # [maxquantity] == 1",
- "[name] == colossusblade && [flag] == ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 6 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(BoTD);
+(function () {
+ const BoTD = [
+ "[name] == VexRune",
+ "me.diff == 2 && [name] == HelRune # # [maxquantity] == 1",
+ "[name] == ElRune # # [maxquantity] == 1",
+ "[name] == EldRune # # [maxquantity] == 1",
+ "[name] == ZodRune",
+ "me.diff == 2 && [name] == EthRune # # [maxquantity] == 1",
+ "[name] == colossusblade && [flag] == ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 6 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(BoTD);
- // Have Zod rune but do not have a base yet
- if (!Check.haveBase("colossusblade", 6) && me.getItem(sdk.items.runes.Zod)) {
- NTIP.addLine("[name] == colossusblade && [flag] == ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- }
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.ColossusBlade,
+ mode: sdk.items.mode.inStorage,
+ sockets: 6,
+ ethereal: true,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- Config.Recipes.push([Recipe.Socket.Weapon, "colossusblade", Roll.Eth]);
- Config.Runewords.push([Runeword.BreathoftheDying, "colossusblade"]);
+ // Have Zod rune but do not have a base yet
+ if (!me.getOwned(wanted).length
+ && me.getItem(sdk.items.runes.Zod)) {
+ NTIP.addLine("[name] == colossusblade && [flag] == ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ }
- Config.KeepRunewords.push("[type] == sword # [ias] >= 60 && [enhanceddamage] >= 350");
+ Config.Recipes.push([Recipe.Socket.Weapon, "colossusblade", Roll.Eth]);
+ Config.Runewords.push([Runeword.BreathoftheDying, "colossusblade"]);
+
+ Config.KeepRunewords.push("[type] == sword # [ias] >= 60 && [enhanceddamage] >= 350");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/CallToArms.js b/libs/SoloPlay/BuildFiles/Runewords/CallToArms.js
index 40ad013f..200ab374 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/CallToArms.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/CallToArms.js
@@ -1,43 +1,57 @@
-(function() {
- const CTA = [
- "[name] == AmnRune # # [maxquantity] == 1",
- "[name] == RalRune # # [maxquantity] == 1",
- "[name] == MalRune",
- "[name] == IstRune",
- "[name] == OhmRune",
- ];
- NTIP.arrayLooping(CTA);
+(function () {
+ const CTA = [
+ "[name] == AmnRune # # [maxquantity] == 1",
+ "[name] == RalRune # # [maxquantity] == 1",
+ "[name] == MalRune",
+ "[name] == IstRune",
+ "[name] == OhmRune",
+ ];
+ NTIP.buildList(CTA);
- // Have Ohm before collecting base
- if (me.getItem(sdk.items.runes.Ohm)) {
- NTIP.addLine("[name] == crystalsword && [quality] >= normal && [quality] <= superior # [sockets] == 5 # [maxquantity] == 1");
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.CrystalSword,
+ mode: sdk.items.mode.inStorage,
+ sockets: 5,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- // Have Ohm+Mal+Ist rune but do not have a base yet
- if (me.getItem(sdk.items.runes.Ist) && me.getItem(sdk.items.runes.Mal) && !Check.haveBase("crystalsword", 5)) {
- NTIP.addLine("[name] == crystalsword && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- Config.Recipes.push([Recipe.Socket.Weapon, "crystalsword"]);
- }
- }
+ // Have Ohm before collecting base
+ if (me.getItem(sdk.items.runes.Ohm)) {
+ NTIP.addLine("[name] == crystalsword && [quality] >= normal && [quality] <= superior # [sockets] == 5 # [maxquantity] == 1");
- // Cube to Mal rune
- if (!me.getItem(sdk.items.runes.Mal)) {
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- }
+ // Have Ohm+Mal+Ist rune but do not have a base yet
+ if (me.getItem(sdk.items.runes.Ist)
+ && me.getItem(sdk.items.runes.Mal)
+ && !me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == crystalsword && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ Config.Recipes.push([Recipe.Socket.Weapon, "crystalsword"]);
+ }
+ }
- // Cube to Ohm Rune
- if (!me.getItem(sdk.items.runes.Ohm)) {
- Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ // Cube to Mal rune
+ if (!me.getItem(sdk.items.runes.Mal)) {
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ }
- if (me.checkItem({name: sdk.locale.items.HeartoftheOak}).have || ["Zealer", "Smiter", "Auradin", "Meteorb", "Blizzballer", "Cold"].includes(SetUp.finalBuild)) {
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- }
- }
+ // Cube to Ohm Rune
+ if (!me.getItem(sdk.items.runes.Ohm)) {
+ Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Runewords.push([Runeword.CallToArms, "crystalsword"]);
- Config.KeepRunewords.push("[type] == sword # [plusskillbattleorders] >= 1");
+ if (me.checkItem({ name: sdk.locale.items.HeartoftheOak }).have
+ || ["Zealer", "Smiter", "Auradin", "Meteorb", "Blizzballer", "Cold"].includes(SetUp.finalBuild)) {
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ }
+ }
+
+ Config.Runewords.push([Runeword.CallToArms, "crystalsword"]);
+ Config.KeepRunewords.push("[type] == sword # [plusskillbattleorders] >= 1");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js b/libs/SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js
index 1c8ee74e..2f3f0626 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js
@@ -1,53 +1,66 @@
-(function() {
- const CoH = [
- "[name] == DolRune # # [maxquantity] == 1",
- "[name] == UmRune",
- "[name] == BerRune",
- "[name] == IstRune",
- ];
- NTIP.arrayLooping(CoH);
-
- // Cube to Ber rune
- if (!me.getItem(sdk.items.runes.Ber)) {
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have || ["Plaguewolf", "Wolf", "Uberconc"].includes(SetUp.finalBuild)) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
-
- if (me.checkItem({name: sdk.locale.items.Grief}).have || ["Uberconc"].indexOf(SetUp.finalBuild) === -1) {
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- }
-
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- }
-
- // Cube to Um rune
- if (!me.getItem(sdk.items.runes.Um)) {
- Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- }
-
- // Have Ber rune before looking for normal base
- if (me.getItem(sdk.items.runes.Ber)) {
- if (!Check.haveBase("armor", 4)) {
- NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 0 # [maxquantity] == 1");
- }
-
- NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
- } else {
- NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] == superior # [enhanceddefense] >= 10 && [sockets] == 4 # [maxquantity] == 1");
- }
-
- Config.Recipes.push([Recipe.Socket.Armor, "archonplate", Roll.NonEth]);
- Config.Recipes.push([Recipe.Socket.Armor, "duskshroud", Roll.NonEth]);
- Config.Recipes.push([Recipe.Socket.Armor, "wyrmhide", Roll.NonEth]);
-
- Config.Runewords.push([Runeword.ChainsofHonor, "archonplate"]);
- Config.Runewords.push([Runeword.ChainsofHonor, "duskshroud"]);
- Config.Runewords.push([Runeword.ChainsofHonor, "wyrmhide"]);
-
- Config.KeepRunewords.push("[type] == armor # [fireresist] == 65 && [hpregen] == 7");
+(function () {
+ const CoH = [
+ "[name] == DolRune # # [maxquantity] == 1",
+ "[name] == UmRune",
+ "[name] == BerRune",
+ "[name] == IstRune",
+ ];
+ NTIP.buildList(CoH);
+
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Armor,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ ethereal: false,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType
+ && [sdk.items.ArchonPlate, sdk.items.DuskShroud, sdk.items.Wyrmhide].includes(item.classid);
+ }
+ };
+
+ // Cube to Ber rune
+ if (!me.getItem(sdk.items.runes.Ber)) {
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have || ["Plaguewolf", "Wolf", "Uberconc"].includes(SetUp.finalBuild)) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+
+ if (me.checkItem({ name: sdk.locale.items.Grief }).have || ["Uberconc"].indexOf(SetUp.finalBuild) === -1) {
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ }
+
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ }
+
+ // Cube to Um rune
+ if (!me.getItem(sdk.items.runes.Um)) {
+ Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ }
+
+ // Have Ber rune before looking for normal base
+ if (me.getItem(sdk.items.runes.Ber)) {
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 0 # [maxquantity] == 1");
+ }
+
+ NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
+ } else {
+ NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] == superior # [enhanceddefense] >= 10 && [sockets] == 4 # [maxquantity] == 1");
+ }
+
+ Config.Recipes.push([Recipe.Socket.Armor, "archonplate", Roll.NonEth]);
+ Config.Recipes.push([Recipe.Socket.Armor, "duskshroud", Roll.NonEth]);
+ Config.Recipes.push([Recipe.Socket.Armor, "wyrmhide", Roll.NonEth]);
+
+ Config.Runewords.push([Runeword.ChainsofHonor, "archonplate"]);
+ Config.Runewords.push([Runeword.ChainsofHonor, "duskshroud"]);
+ Config.Runewords.push([Runeword.ChainsofHonor, "wyrmhide"]);
+
+ Config.KeepRunewords.push("[type] == armor # [fireresist] == 65 && [hpregen] == 7");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Chaos.js b/libs/SoloPlay/BuildFiles/Runewords/Chaos.js
index 9f43835f..975e7d96 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Chaos.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Chaos.js
@@ -1,14 +1,14 @@
-(function() {
- const Chaos = [
- "[name] == FalRune # # [maxquantity] == 1",
- "[name] == OhmRune",
- "[name] == UmRune",
- "[name] == suwayyah && [quality] >= normal && [quality] <= superior # ([assassinskills]+[shadowdisciplinesskilltab]+[skillvenom]+[skilldeathsentry]+[skillfade]+[skillshadowmaster]) >= 1 && [sockets] == 3 # [maxquantity] == 1",
- "[name] == suwayyah && [quality] == normal # ([assassinskills]+[shadowdisciplinesskilltab]+[skillvenom]+[skilldeathsentry]+[skillfade]+[skillshadowmaster]) >= 1 && [sockets] == 0 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Chaos);
+(function () {
+ const Chaos = [
+ "[name] == FalRune # # [maxquantity] == 1",
+ "[name] == OhmRune",
+ "[name] == UmRune",
+ "[name] == suwayyah && [quality] >= normal && [quality] <= superior # ([assassinskills]+[shadowdisciplinesskilltab]+[skillvenom]+[skilldeathsentry]+[skillfade]+[skillshadowmaster]) >= 1 && [sockets] == 3 # [maxquantity] == 1",
+ "[name] == suwayyah && [quality] == normal # ([assassinskills]+[shadowdisciplinesskilltab]+[skillvenom]+[skilldeathsentry]+[skillfade]+[skillshadowmaster]) >= 1 && [sockets] == 0 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Chaos);
- Config.Runewords.push([Runeword.Chaos, "suwayyah"]);
+ Config.Runewords.push([Runeword.Chaos, "suwayyah"]);
- Config.KeepRunewords.push("[type] == assassinclaw # [plusskillwhirlwind] == 1");
+ Config.KeepRunewords.push("[type] == assassinclaw # [plusskillwhirlwind] == 1");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/CrescentMoon.js b/libs/SoloPlay/BuildFiles/Runewords/CrescentMoon.js
index 65e4156a..a0d80e0e 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/CrescentMoon.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/CrescentMoon.js
@@ -1,55 +1,55 @@
-(function() {
- const Crescent = [
- "[name] == ShaelRune # # [maxquantity] == 2",
- "[name] == UmRune",
- "[name] == TirRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Crescent);
+(function () {
+ const Crescent = [
+ "[name] == ShaelRune # # [maxquantity] == 2",
+ "[name] == UmRune",
+ "[name] == TirRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Crescent);
- if (me.barbarian) {
- // Cube to Um Rune
- if (!me.getItem(sdk.items.runes.Um)) {
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- }
+ if (me.barbarian) {
+ // Cube to Um Rune
+ if (!me.getItem(sdk.items.runes.Um)) {
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ }
- // Have Shael and Um runes before looking for base
- if (me.getItem(sdk.items.runes.Shael) && me.getItem(sdk.items.runes.Um)) {
- NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 3");
- NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3");
- } else {
- NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] == superior # [enhanceddamage] >= 5 && [sockets] == 3");
- }
+ // Have Shael and Um runes before looking for base
+ if (me.getItem(sdk.items.runes.Shael) && me.getItem(sdk.items.runes.Um)) {
+ NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 3");
+ NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3");
+ } else {
+ NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] == superior # [enhanceddamage] >= 5 && [sockets] == 3");
+ }
- Config.Runewords.push([Runeword.CrescentMoon, "dimensionalblade"]);
- Config.Runewords.push([Runeword.CrescentMoon, "battlesword"]);
- Config.Runewords.push([Runeword.CrescentMoon, "runesword"]);
- Config.Runewords.push([Runeword.CrescentMoon, "conquestsword"]);
- Config.Runewords.push([Runeword.CrescentMoon, "crypticsword"]);
- Config.Runewords.push([Runeword.CrescentMoon, "phaseblade"]);
- Config.Runewords.push([Runeword.CrescentMoon, "espandon"]);
- Config.Runewords.push([Runeword.CrescentMoon, "tusksword"]);
- Config.Runewords.push([Runeword.CrescentMoon, "zweihander"]);
- Config.Runewords.push([Runeword.CrescentMoon, "legendsword"]);
- Config.Runewords.push([Runeword.CrescentMoon, "highlandblade"]);
- Config.Runewords.push([Runeword.CrescentMoon, "balrogblade"]);
- Config.Runewords.push([Runeword.CrescentMoon, "championsword"]);
- Config.Runewords.push([Runeword.CrescentMoon, "colossussword"]);
- }
+ Config.Runewords.push([Runeword.CrescentMoon, "dimensionalblade"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "battlesword"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "runesword"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "conquestsword"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "crypticsword"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "phaseblade"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "espandon"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "tusksword"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "zweihander"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "legendsword"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "highlandblade"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "balrogblade"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "championsword"]);
+ Config.Runewords.push([Runeword.CrescentMoon, "colossussword"]);
+ }
- if (me.paladin) {
- NTIP.addLine("[name] == phaseblade && [quality] == normal # ([sockets] == 0 || [sockets] == 3) # [maxquantity] == 1");
+ if (me.paladin) {
+ NTIP.addLine("[name] == phaseblade && [quality] == normal # ([sockets] == 0 || [sockets] == 3) # [maxquantity] == 1");
- // Cube to Um rune
- if (!me.getItem(sdk.items.runes.Um)) {
- Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
- Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- }
+ // Cube to Um rune
+ if (!me.getItem(sdk.items.runes.Um)) {
+ Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ }
- Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade", Roll.NonEth]);
- Config.Runewords.push([Runeword.CrescentMoon, "phaseblade"]);
- }
+ Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade", Roll.NonEth]);
+ Config.Runewords.push([Runeword.CrescentMoon, "phaseblade"]);
+ }
- Config.KeepRunewords.push("[type] == sword # [ias] >= 20 && [passiveltngpierce] >= 35");
+ Config.KeepRunewords.push("[type] == sword # [ias] >= 20 && [passiveltngpierce] >= 35");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/DragonArmor.js b/libs/SoloPlay/BuildFiles/Runewords/DragonArmor.js
index 0fc5fd74..63e9664b 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/DragonArmor.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/DragonArmor.js
@@ -1,20 +1,20 @@
-(function() {
- const DragonArmor = [
- "[name] == SurRune",
- "[name] == LoRune",
- "[name] == SolRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(DragonArmor);
+(function () {
+ const DragonArmor = [
+ "[name] == SurRune",
+ "[name] == LoRune",
+ "[name] == SolRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(DragonArmor);
- // Cube to Sur rune
- if (!me.getItem(sdk.items.runes.Sur)) {
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- }
+ // Cube to Sur rune
+ if (!me.getItem(sdk.items.runes.Sur)) {
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ }
- // Have Sur and Lo rune before attempting to make runeword
- if (me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Sur)) {
- Config.Runewords.push([Runeword.Dragon, "archonplate", Roll.NonEth]);
- }
+ // Have Sur and Lo rune before attempting to make runeword
+ if (me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Sur)) {
+ Config.Runewords.push([Runeword.Dragon, "archonplate", Roll.NonEth]);
+ }
- Config.KeepRunewords.push("[type] == armor # [holyfireaura] >= 14");
+ Config.KeepRunewords.push("[type] == armor # [holyfireaura] >= 14");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/DreamHelm.js b/libs/SoloPlay/BuildFiles/Runewords/DreamHelm.js
index 166b629b..c7ac17e1 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/DreamHelm.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/DreamHelm.js
@@ -1,12 +1,12 @@
-(function() {
- const DreamHelm = [
- "[name] == IoRune # # [maxquantity] == 1",
- "[name] == JahRune",
- "[name] == PulRune",
- "[name] == bonevisage && [flag] != ethereal && [quality] == superior # [enhanceddefense] >= 15 && [sockets] == 3 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(DreamHelm);
+(function () {
+ const DreamHelm = [
+ "[name] == IoRune # # [maxquantity] == 1",
+ "[name] == JahRune",
+ "[name] == PulRune",
+ "[name] == bonevisage && [flag] != ethereal && [quality] == superior # [enhanceddefense] >= 15 && [sockets] == 3 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(DreamHelm);
- Config.Runewords.push([Runeword.Dream, "bonevisage"]);
- Config.KeepRunewords.push("[type] == helm # [holyshockaura] >= 15");
+ Config.Runewords.push([Runeword.Dream, "bonevisage"]);
+ Config.KeepRunewords.push("[type] == helm # [holyshockaura] >= 15");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/DreamShield.js b/libs/SoloPlay/BuildFiles/Runewords/DreamShield.js
index 007526ba..93936860 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/DreamShield.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/DreamShield.js
@@ -1,17 +1,28 @@
-(function() {
- const DreamShield = [
- "[name] == IoRune # # [maxquantity] == 1",
- "[name] == JahRune",
- "[name] == PulRune",
- "[name] == sacredtarge && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [fireresist] >= 45 && [sockets] == 3 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(DreamShield);
+(function () {
+ const DreamShield = [
+ "[name] == IoRune # # [maxquantity] == 1",
+ "[name] == JahRune",
+ "[name] == PulRune",
+ "[name] == sacredtarge && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [fireresist] >= 45 && [sockets] == 3 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(DreamShield);
- if (!Check.haveBase("sacredtarge", 3)) {
- NTIP.addLine("[name] == sacredtarge && [flag] != ethereal && [quality] == normal # [fireresist] >= 45 && [sockets] == 0 # [maxquantity] == 1");
- }
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.SacredTarge,
+ mode: sdk.items.mode.inStorage,
+ sockets: 3,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- Config.Recipes.push([Recipe.Socket.Shield, "sacredtarge", Roll.NonEth]);
- Config.Runewords.push([Runeword.Dream, "sacredtarge"]);
- Config.KeepRunewords.push("[type] == auricshields # [holyshockaura] >= 15");
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == sacredtarge && [flag] != ethereal && [quality] == normal # [fireresist] >= 45 && [sockets] == 0 # [maxquantity] == 1");
+ }
+
+ Config.Recipes.push([Recipe.Socket.Shield, "sacredtarge", Roll.NonEth]);
+ Config.Runewords.push([Runeword.Dream, "sacredtarge"]);
+ Config.KeepRunewords.push("[type] == auricshields # [holyshockaura] >= 15");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Duress.js b/libs/SoloPlay/BuildFiles/Runewords/Duress.js
index fa808529..adf37b5d 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Duress.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Duress.js
@@ -1,34 +1,34 @@
-(function() {
- const Duress = [
- "[name] == ShaelRune # # [maxquantity] == 1",
- "[name] == UmRune # # [maxquantity] == 1",
- "[name] == ThulRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Duress);
+(function () {
+ const Duress = [
+ "[name] == ShaelRune # # [maxquantity] == 1",
+ "[name] == UmRune # # [maxquantity] == 1",
+ "[name] == ThulRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Duress);
- // Cube to Um rune
- if (!me.getItem(sdk.items.runes.Um)) {
- Config.Recipes.push([Recipe.Rune, "Io Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
- Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- }
+ // Cube to Um rune
+ if (!me.getItem(sdk.items.runes.Um)) {
+ Config.Recipes.push([Recipe.Rune, "Io Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ }
- // Have Um and Shael runes before looking for base
- if (me.getItem(sdk.items.runes.Um) && me.getItem(sdk.items.runes.Shael)) {
- NTIP.addLine("([name] == archonplate || [name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == boneweave || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
- }
+ // Have Um and Shael runes before looking for base
+ if (me.getItem(sdk.items.runes.Um) && me.getItem(sdk.items.runes.Shael)) {
+ NTIP.addLine("([name] == archonplate || [name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == boneweave || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
+ }
- Config.Runewords.push([Runeword.Duress, "archonplate"]);
- Config.Runewords.push([Runeword.Duress, "demonhidearmor"]);
- Config.Runewords.push([Runeword.Duress, "duskshroud"]);
- Config.Runewords.push([Runeword.Duress, "ghostarmor"]);
- Config.Runewords.push([Runeword.Duress, "boneweave"]);
- Config.Runewords.push([Runeword.Duress, "serpentskinarmor"]);
- Config.Runewords.push([Runeword.Duress, "trellisedarmor"]);
- Config.Runewords.push([Runeword.Duress, "wyrmhide"]);
+ Config.Runewords.push([Runeword.Duress, "archonplate"]);
+ Config.Runewords.push([Runeword.Duress, "demonhidearmor"]);
+ Config.Runewords.push([Runeword.Duress, "duskshroud"]);
+ Config.Runewords.push([Runeword.Duress, "ghostarmor"]);
+ Config.Runewords.push([Runeword.Duress, "boneweave"]);
+ Config.Runewords.push([Runeword.Duress, "serpentskinarmor"]);
+ Config.Runewords.push([Runeword.Duress, "trellisedarmor"]);
+ Config.Runewords.push([Runeword.Duress, "wyrmhide"]);
- Config.KeepRunewords.push("[type] == armor # [coldresist] == 45");
+ Config.KeepRunewords.push("[type] == armor # [coldresist] == 45");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Enigma.js b/libs/SoloPlay/BuildFiles/Runewords/Enigma.js
index 38ba82ad..2f42991d 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Enigma.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Enigma.js
@@ -1,45 +1,45 @@
-(function() {
- const Enigma = [
- "[name] == JahRune",
- "[name] == IthRune # # [maxquantity] == 1",
- "[name] == BerRune",
- ];
- NTIP.arrayLooping(Enigma);
+(function () {
+ const Enigma = [
+ "[name] == JahRune",
+ "[name] == IthRune # # [maxquantity] == 1",
+ "[name] == BerRune",
+ ];
+ NTIP.buildList(Enigma);
- // Cube to Jah rune
- if (!me.getItem(sdk.items.runes.Jah)) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- }
+ // Cube to Jah rune
+ if (!me.getItem(sdk.items.runes.Jah)) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ }
- // Cube to Ber rune
- if (!me.getItem(sdk.items.runes.Ber)) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- }
+ // Cube to Ber rune
+ if (!me.getItem(sdk.items.runes.Ber)) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ }
- // Have Ber and Jah runes before looking for normal base
- if (me.getItem(sdk.items.runes.Ber) && me.getItem(sdk.items.runes.Jah)) {
- Config.Runewords.push([Runeword.Enigma, "mageplate", Roll.NonEth]);
- Config.Runewords.push([Runeword.Enigma, "duskshroud", Roll.NonEth]);
- Config.Runewords.push([Runeword.Enigma, "wyrmhide", Roll.NonEth]);
- Config.Runewords.push([Runeword.Enigma, "scarabhusk", Roll.NonEth]);
+ // Have Ber and Jah runes before looking for normal base
+ if (me.getItem(sdk.items.runes.Ber) && me.getItem(sdk.items.runes.Jah)) {
+ Config.Runewords.push([Runeword.Enigma, "mageplate", Roll.NonEth]);
+ Config.Runewords.push([Runeword.Enigma, "duskshroud", Roll.NonEth]);
+ Config.Runewords.push([Runeword.Enigma, "wyrmhide", Roll.NonEth]);
+ Config.Runewords.push([Runeword.Enigma, "scarabhusk", Roll.NonEth]);
- NTIP.addLine("([name] == mageplate || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
- } else {
- NTIP.addLine("([name] == mageplate || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [flag] != ethereal && [quality] == superior # [enhanceddefense] >= 10 && [sockets] == 3 # [maxquantity] == 1");
- }
+ NTIP.addLine("([name] == mageplate || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
+ } else {
+ NTIP.addLine("([name] == mageplate || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [flag] != ethereal && [quality] == superior # [enhanceddefense] >= 10 && [sockets] == 3 # [maxquantity] == 1");
+ }
- Config.KeepRunewords.push("[type] == armor # [itemallskills] == 2");
+ Config.KeepRunewords.push("[type] == armor # [itemallskills] == 2");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Exile.js b/libs/SoloPlay/BuildFiles/Runewords/Exile.js
index 6324efcd..b3a62594 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Exile.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Exile.js
@@ -1,18 +1,28 @@
-(function() {
- const Exile = [
- "[name] == VexRune",
- "[name] == OhmRune",
- "[name] == IstRune",
- "[name] == DolRune # # [maxquantity] == 1",
- "[name] == sacredtarge && [quality] >= normal && [quality] <= superior # [fireresist] >= 30 && [sockets] == 4 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Exile);
+(function () {
+ const Exile = [
+ "[name] == VexRune",
+ "[name] == OhmRune",
+ "[name] == IstRune",
+ "[name] == DolRune # # [maxquantity] == 1",
+ "[name] == sacredtarge && [quality] >= normal && [quality] <= superior # [fireresist] >= 30 && [sockets] == 4 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Exile);
- if (!Check.haveBase("sacredtarge", 4)) {
- NTIP.addLine("[name] == sacredtarge && [quality] == normal && [flag] == ethereal # [fireresist] >= 30 && [sockets] == 0 # [maxquantity] == 1");
- }
+ const wanted = {
+ classid: sdk.items.SacredTarge,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- Config.Recipes.push([Recipe.Socket.Shield, "sacredtarge"]);
- Config.Runewords.push([Runeword.Exile, "sacredtarge"]);
- Config.KeepRunewords.push("[type] == auricshields # [defianceaura] >= 13");
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == sacredtarge && [quality] == normal && [flag] == ethereal # [fireresist] >= 30 && [sockets] == 0 # [maxquantity] == 1");
+ }
+
+ Config.Recipes.push([Recipe.Socket.Shield, "sacredtarge"]);
+ Config.Runewords.push([Runeword.Exile, "sacredtarge"]);
+ Config.KeepRunewords.push("[type] == auricshields # [defianceaura] >= 13");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Faith.js b/libs/SoloPlay/BuildFiles/Runewords/Faith.js
index 4bbfb61f..21385b7f 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Faith.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Faith.js
@@ -1,57 +1,70 @@
-(function() {
- const FaithRunes = [
- "[name] == OhmRune",
- "[name] == JahRune",
- "[name] == EldRune # # [maxquantity] == 1",
- "[name] == LemRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(FaithRunes);
- // Cube to Ohm and Keep cubing to Jah rune
- if (Item.getQuantityOwned(me.getItem(sdk.items.runes.Ohm) > 1) && me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- if (!me.getItem(sdk.items.runes.Jah)) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- }
- !me.getItem(sdk.items.runes.Jah) && Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
- // Cube to Jah rune
- if (!me.getItem(sdk.items.runes.Jah) && me.checkItem({name: sdk.locale.items.ChainofHonor}).have) {
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- }
+(function () {
+ const FaithRunes = [
+ "[name] == OhmRune",
+ "[name] == JahRune",
+ "[name] == EldRune # # [maxquantity] == 1",
+ "[name] == LemRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(FaithRunes);
+ // Cube to Ohm and Keep cubing to Jah rune
+ if (me.getOwned({ classid: sdk.items.runes.Ohm }).length > 1
+ && me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ if (!me.getItem(sdk.items.runes.Jah)) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ }
+ !me.getItem(sdk.items.runes.Jah) && Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+ // Cube to Jah rune
+ if (!me.getItem(sdk.items.runes.Jah)
+ && me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have) {
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ }
- if (me.amazon) {
- if (me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Jah)) {
- if (!Check.haveBase("amazonbow", 4)) {
- NTIP.addLine("[name] == grandmatronbow && [quality] == normal # [bowandcrossbowskilltab] == 3 && [sockets] == 0 # [maxquantity] == 1");
- }
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.AmazonBow,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- NTIP.addLine("[name] == grandmatronbow && [quality] >= normal && [quality] <= superior # [bowandcrossbowskilltab] >= 1 && [sockets] == 4 # [maxquantity] == 1");
- } else {
- NTIP.addLine("[name] == grandmatronbow && [quality] == superior # [bowandcrossbowskilltab] == 3 && [enhanceddamage] >= 5 && [sockets] == 4 # [maxquantity] == 1");
- }
+ if (me.amazon) {
+ if (me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Jah)) {
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == grandmatronbow && [quality] == normal # [bowandcrossbowskilltab] == 3 && [sockets] == 0 # [maxquantity] == 1");
+ }
- Config.Runewords.push([Runeword.Faith, "grandmatronbow"]);
+ NTIP.addLine("[name] == grandmatronbow && [quality] >= normal && [quality] <= superior # [bowandcrossbowskilltab] >= 1 && [sockets] == 4 # [maxquantity] == 1");
+ } else {
+ NTIP.addLine("[name] == grandmatronbow && [quality] == superior # [bowandcrossbowskilltab] == 3 && [enhanceddamage] >= 5 && [sockets] == 4 # [maxquantity] == 1");
+ }
- Config.Recipes.push([Recipe.Socket.Bow, "grandmatronbow"]);
+ Config.Runewords.push([Runeword.Faith, "grandmatronbow"]);
+ Config.Recipes.push([Recipe.Socket.Bow, "grandmatronbow"]);
+ } else {
+ if (me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Jah)) {
+ wanted.itemType = sdk.items.type.Bow;
+
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("([name] == wardbow || [name] == bladebow || [name] == diamonbow) && [quality] == superior # [enhanceddamage] >= 5 && [sockets] == 4 # [maxquantity] == 1");
+ } else {
+ NTIP.addLine("([name] == wardbow || [name] == bladebow || [name] == diamonbow) && [quality] == superior # [enhanceddamage] == 15 && [sockets] == 4 # [maxquantity] == 1");
+ }
- } else {
- if (me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Jah)) {
- if (!Check.haveBase("amazonbow", 4)) {
- NTIP.addLine("([name] == wardbow || [name] == bladebow || [name] == diamonbow) && [quality] == superior # [enhanceddamage] >= 5 && [sockets] == 4 # [maxquantity] == 1");
- } else {
- NTIP.addLine("([name] == wardbow || [name] == bladebow || [name] == diamonbow) && [quality] == superior # [enhanceddamage] == 15 && [sockets] == 4 # [maxquantity] == 1");
- }
+ Config.Runewords.push([Runeword.Faith, "wardbow"]);
+ Config.Runewords.push([Runeword.Faith, "bladebow"]);
+ Config.Runewords.push([Runeword.Faith, "diamonbow"]);
+ }
+ }
- Config.Runewords.push([Runeword.Faith, "wardbow"]);
- Config.Runewords.push([Runeword.Faith, "bladebow"]);
- Config.Runewords.push([Runeword.Faith, "diamonbow"]);
- }
- }
-
- Config.KeepRunewords.push("([type] == bow || [type] == amazonbow) && [flag] == runeword # [fanaticismaura] >= 12 && [itemallskills] >= 1");
+ Config.KeepRunewords.push("([type] == bow || [type] == amazonbow) && [flag] == runeword # [fanaticismaura] >= 12 && [itemallskills] >= 1");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Fortitude.js b/libs/SoloPlay/BuildFiles/Runewords/Fortitude.js
index f639000e..cc09ccdb 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Fortitude.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Fortitude.js
@@ -1,38 +1,51 @@
-(function() {
- const Fortitude = [
- "[name] == ElRune # # [maxquantity] == 1",
- "[name] == SolRune # # [maxquantity] == 1",
- "[name] == DolRune # # [maxquantity] == 1",
- "[name] == LoRune",
- ];
- NTIP.arrayLooping(Fortitude);
+(function () {
+ const Fortitude = [
+ "[name] == ElRune # # [maxquantity] == 1",
+ "[name] == SolRune # # [maxquantity] == 1",
+ "[name] == DolRune # # [maxquantity] == 1",
+ "[name] == LoRune",
+ ];
+ NTIP.buildList(Fortitude);
- // Have Lo rune before looking for normal base
- if (me.getItem(sdk.items.runes.Lo)) {
- if (!Check.haveBase("armor", 4)) {
- NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 0 # [maxquantity] == 1");
- }
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Armor,
+ mode: sdk.items.mode.inStorage,
+ ethereal: false,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType
+ && [sdk.items.ArchonPlate, sdk.items.DuskShroud, sdk.items.Wyrmhide].includes(item.classid);
+ }
+ };
- NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
- } else {
- NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] == superior # [enhanceddefense] >= 10 && [sockets] == 4 # [maxquantity] == 1");
- }
+ // Have Lo rune before looking for normal base
+ if (me.getItem(sdk.items.runes.Lo)) {
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 0 # [maxquantity] == 1");
+ }
- // Cube to Lo rune
- if (!me.getItem(sdk.items.runes.Lo)) {
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
+ NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
+ } else {
+ NTIP.addLine("([name] == archonplate || [name] == duskshroud || [name] == wyrmhide) && [flag] != ethereal && [quality] == superior # [enhanceddefense] >= 10 && [sockets] == 4 # [maxquantity] == 1");
+ }
- Config.Recipes.push([Recipe.Socket.Armor, "archonplate", Roll.NonEth]);
- Config.Recipes.push([Recipe.Socket.Armor, "duskshroud", Roll.NonEth]);
- Config.Recipes.push([Recipe.Socket.Armor, "wyrmhide", Roll.NonEth]);
+ // Cube to Lo rune
+ if (!me.getItem(sdk.items.runes.Lo)) {
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
- Config.Runewords.push([Runeword.Fortitude, "archonplate"]);
- Config.Runewords.push([Runeword.Fortitude, "duskshroud"]);
- Config.Runewords.push([Runeword.Fortitude, "wyrmhide"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "archonplate", Roll.NonEth]);
+ Config.Recipes.push([Recipe.Socket.Armor, "duskshroud", Roll.NonEth]);
+ Config.Recipes.push([Recipe.Socket.Armor, "wyrmhide", Roll.NonEth]);
- Config.KeepRunewords.push("[type] == armor # [enhanceddefense] >= 200 && [enhanceddamage] >= 300");
+ Config.Runewords.push([Runeword.Fortitude, "archonplate"]);
+ Config.Runewords.push([Runeword.Fortitude, "duskshroud"]);
+ Config.Runewords.push([Runeword.Fortitude, "wyrmhide"]);
+
+ Config.KeepRunewords.push("[type] == armor # [enhanceddefense] >= 200 && [enhanceddamage] >= 300");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Fury.js b/libs/SoloPlay/BuildFiles/Runewords/Fury.js
index cd08d497..ef66b3c7 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Fury.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Fury.js
@@ -1,14 +1,14 @@
-(function() {
- const Fury = [
- "[name] == JahRune",
- "[name] == GulRune",
- "[name] == EthRune ## [maxquantity] == 1",
- "[name] == suwayyah && [quality] >= normal && [quality] <= superior # ([assassinskills]+[shadowdisciplinesskilltab]+[skillvenom]+[skilldeathsentry]+[skillfade]+[skillshadowmaster]) >= 1 && [sockets] == 3 # [maxquantity] == 1",
- "[name] == suwayyah && [quality] == normal # ([assassinskills]+[shadowdisciplinesskilltab]+[skillvenom]+[skilldeathsentry]+[skillfade]+[skillshadowmaster]) >= 1 && [sockets] == 0 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Fury);
+(function () {
+ const Fury = [
+ "[name] == JahRune",
+ "[name] == GulRune",
+ "[name] == EthRune ## [maxquantity] == 1",
+ "[name] == suwayyah && [quality] >= normal && [quality] <= superior # ([assassinskills]+[shadowdisciplinesskilltab]+[skillvenom]+[skilldeathsentry]+[skillfade]+[skillshadowmaster]) >= 1 && [sockets] == 3 # [maxquantity] == 1",
+ "[name] == suwayyah && [quality] == normal # ([assassinskills]+[shadowdisciplinesskilltab]+[skillvenom]+[skilldeathsentry]+[skillfade]+[skillshadowmaster]) >= 1 && [sockets] == 0 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Fury);
- Config.Runewords.push([Runeword.Fury, "suwayyah"]);
+ Config.Runewords.push([Runeword.Fury, "suwayyah"]);
- Config.KeepRunewords.push("[type] == assassinclaw # [itemallskills] == 2 && [ias] == 40 && [itemdeadlystrike] == 33");
+ Config.KeepRunewords.push("[type] == assassinclaw # [lifeleech] >= 6 && [ias] == 40 && [itemdeadlystrike] == 33");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Grief.js b/libs/SoloPlay/BuildFiles/Runewords/Grief.js
index c43cca08..87c0fc62 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Grief.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Grief.js
@@ -1,49 +1,61 @@
-(function() {
- const Grief = [
- "[name] == EthRune # # [maxquantity] == 1",
- "[name] == TirRune # # [maxquantity] == 1",
- "[name] == LoRune # # [maxquantity] == 1",
- "[name] == MalRune # # [maxquantity] == 1",
- "[name] == RalRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Grief);
+(function () {
+ const Grief = [
+ "[name] == EthRune # # [maxquantity] == 1",
+ "[name] == TirRune # # [maxquantity] == 1",
+ "[name] == LoRune # # [maxquantity] == 1",
+ "[name] == MalRune # # [maxquantity] == 1",
+ "[name] == RalRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Grief);
- if (me.getItem(sdk.items.runes.Lo)) {
- NTIP.addLine("[name] == phaseblade && [quality] >= normal && [quality] <= superior # [sockets] == 5 # [maxquantity] == 1");
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.PhaseBlade,
+ mode: sdk.items.mode.inStorage,
+ sockets: 5,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- if (!Check.haveBase("phaseblade", 5)) {
- NTIP.addLine("[name] == phaseblade && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade"]);
- }
- } else {
- NTIP.addLine("[name] == phaseblade && [quality] == superior # [enhanceddamage] >= 10 && [sockets] == 5 # [maxquantity] == 1");
- }
+ if (me.getItem(sdk.items.runes.Lo)) {
+ NTIP.addLine("[name] == phaseblade && [quality] >= normal && [quality] <= superior # [sockets] == 5 # [maxquantity] == 1");
- // Cube to Lo Rune
- if (!me.getItem(sdk.items.runes.Lo)) {
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == phaseblade && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade"]);
+ }
+ } else {
+ NTIP.addLine("[name] == phaseblade && [quality] == superior # [enhanceddamage] >= 10 && [sockets] == 5 # [maxquantity] == 1");
+ }
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have || ["Smiter", "Zealer"].indexOf(SetUp.finalBuild) === -1) {
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
- }
+ // Cube to Lo Rune
+ if (!me.getItem(sdk.items.runes.Lo)) {
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- // Cube to Mal Rune
- if (!me.getItem(sdk.items.runes.Mal)) {
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- }
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have
+ || ["Smiter", "Zealer"].indexOf(SetUp.finalBuild) === -1) {
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+ }
- if (SetUp.finalBuild === "Plaguewolf") {
- // Only start making Grief after Chains of Honor is made
- if (Check.haveItem("armor", "runeword", "Chains of Honor")) {
- Config.Runewords.push([Runeword.Grief, "phaseblade"]);
- Config.KeepRunewords.push("[type] == sword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20");
- }
- } else {
- Config.Runewords.push([Runeword.Grief, "phaseblade"]);
- Config.KeepRunewords.push("[type] == sword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20");
- }
+ // Cube to Mal Rune
+ if (!me.getItem(sdk.items.runes.Mal)) {
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ }
+
+ if (SetUp.finalBuild === "Plaguewolf") {
+ // Only start making Grief after Chains of Honor is made
+ if (me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have) {
+ Config.Runewords.push([Runeword.Grief, "phaseblade"]);
+ Config.KeepRunewords.push("[type] == sword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20");
+ }
+ } else {
+ Config.Runewords.push([Runeword.Grief, "phaseblade"]);
+ Config.KeepRunewords.push("[type] == sword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20");
+ }
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/HandOfJustice.js b/libs/SoloPlay/BuildFiles/Runewords/HandOfJustice.js
index 2c539db0..90d83eca 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/HandOfJustice.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/HandOfJustice.js
@@ -1,31 +1,31 @@
-(function() {
- const HoJ = [
- "[name] == SurRune",
- "[name] == ChamRune",
- "[name] == AmnRune # # [maxquantity] == 1",
- "[name] == LoRune",
- "[name] == phaseblade && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(HoJ);
+(function () {
+ const HoJ = [
+ "[name] == SurRune",
+ "[name] == ChamRune",
+ "[name] == AmnRune # # [maxquantity] == 1",
+ "[name] == LoRune",
+ "[name] == phaseblade && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(HoJ);
- // Cube to Lo rune
- if (!me.getItem(sdk.items.runes.Lo)) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ // Cube to Lo rune
+ if (!me.getItem(sdk.items.runes.Lo)) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
- }
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+ }
- // Cube to Cham rune
- if (!me.getItem(sdk.items.runes.Cham)) {
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- Config.Recipes.push([Recipe.Rune, "Jah Rune"]);
- }
+ // Cube to Cham rune
+ if (!me.getItem(sdk.items.runes.Cham)) {
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Jah Rune"]);
+ }
- Config.Runewords.push([Runeword.HandofJustice, "phaseblade"]);
- Config.KeepRunewords.push("[type] == sword # [holyfireaura] >= 16");
+ Config.Runewords.push([Runeword.HandofJustice, "phaseblade"]);
+ Config.KeepRunewords.push("[type] == sword # [holyfireaura] >= 16");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js b/libs/SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js
index 8ff82f21..e643314f 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js
@@ -1,35 +1,47 @@
-(function() {
- const HotO = [
- "[name] == ThulRune # # [maxquantity] == 1",
- "[name] == PulRune",
- "[name] == KoRune # # [maxquantity] == 1",
- "[name] == VexRune",
- ];
- NTIP.arrayLooping(HotO);
+(function () {
+ const HotO = [
+ "[name] == ThulRune # # [maxquantity] == 1",
+ "[name] == PulRune",
+ "[name] == KoRune # # [maxquantity] == 1",
+ "[name] == VexRune",
+ ];
+ NTIP.buildList(HotO);
- // Have Vex rune before looking for base
- if (me.getItem(sdk.items.runes.Vex)) {
- NTIP.addLine("([name] == flail || [name] == knout) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Mace,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ ethereal: false,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- // Have Vex rune but do not have a base yet
- if (!Check.haveBase("mace", 4)) {
- NTIP.addLine("([name] == flail || [name] == knout) && [flag] != ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- Config.Recipes.push([Recipe.Socket.Weapon, "flail"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "knout"]);
- }
- }
+ // Have Vex rune before looking for base
+ if (me.getItem(sdk.items.runes.Vex)) {
+ NTIP.addLine("([name] == flail || [name] == knout) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
- // Cube to Vex rune
- if (!me.getItem(sdk.items.runes.Vex)) {
- Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- }
+ // Have Vex rune but do not have a base yet
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("([name] == flail || [name] == knout) && [flag] != ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ Config.Recipes.push([Recipe.Socket.Weapon, "flail"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "knout"]);
+ }
+ }
- Config.Runewords.push([Runeword.HeartoftheOak, "knout"]);
- Config.Runewords.push([Runeword.HeartoftheOak, "flail"]);
- Config.KeepRunewords.push("[type] == mace # [itemallskills] == 3");
+ // Cube to Vex rune
+ if (!me.getItem(sdk.items.runes.Vex)) {
+ Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ }
+
+ Config.Runewords.push([Runeword.HeartoftheOak, "knout"]);
+ Config.Runewords.push([Runeword.HeartoftheOak, "flail"]);
+ Config.KeepRunewords.push("[type] == mace # [itemallskills] == 3");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Honor.js b/libs/SoloPlay/BuildFiles/Runewords/Honor.js
index 14c79560..552ab402 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Honor.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Honor.js
@@ -1,42 +1,54 @@
-(function() {
- const Honor = [
- "[name] == AmnRune # # [maxquantity] == 1",
- "[name] == ElRune # # [maxquantity] == 1",
- "[name] == IthRune # # [maxquantity] == 1",
- "[name] == TirRune # # [maxquantity] == 1",
- "[name] == SolRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Honor);
+(function () {
+ const Honor = [
+ "[name] == AmnRune # # [maxquantity] == 1",
+ "[name] == ElRune # # [maxquantity] == 1",
+ "[name] == IthRune # # [maxquantity] == 1",
+ "[name] == TirRune # # [maxquantity] == 1",
+ "[name] == SolRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Honor);
- // Cube to Amn rune
- if (!me.getItem(sdk.items.runes.Amn)) {
- Config.Recipes.push([Recipe.Rune, "Thul Rune"]);
- }
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Sword,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ ethereal: false,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- // Have Sol rune before looking for base
- if (me.getItem(sdk.items.runes.Sol)) {
- if (!Check.haveBase("sword", 5)) {
- if (Pather.accessToAct(5) && !me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.Completed)) {
- NTIP.addLine("((me.diff == 0 && [name] == flamberge) || (me.diff > 0 && [name] == zweihander) || (me.diff == 2 && [name] == colossussword)) && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [level] >= 41 # [sockets] == 0 # [maxquantity] == 1");
- } else {
- NTIP.addLine("([name] == flamberge || [name] == zweihander || [name] == dimensionalblade || [name] == phaseblade || [name] == colossussword) && [flag] != ethereal && [quality] == normal && [level] >= 41 # [sockets] == 0 # [maxquantity] == 1");
- }
- }
+ // Cube to Amn rune
+ if (!me.getItem(sdk.items.runes.Amn)) {
+ Config.Recipes.push([Recipe.Rune, "Thul Rune"]);
+ }
- NTIP.addLine("[type] == Sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 187 # [sockets] == 5 # [maxquantity] == 1");
- }
+ // Have Sol rune before looking for base
+ if (me.getItem(sdk.items.runes.Sol)) {
+ if (!me.getOwned(wanted).length) {
+ if (me.accessToAct(5) && !me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.Completed)) {
+ NTIP.addLine("((me.diff == 0 && [name] == flamberge) || (me.diff > 0 && [name] == zweihander) || (me.diff == 2 && [name] == colossussword)) && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [level] >= 41 # [sockets] == 0 # [maxquantity] == 1");
+ } else {
+ NTIP.addLine("([name] == flamberge || [name] == zweihander || [name] == dimensionalblade || [name] == phaseblade || [name] == colossussword) && [flag] != ethereal && [quality] == normal && [level] >= 41 # [sockets] == 0 # [maxquantity] == 1");
+ }
+ }
- Config.Runewords.push([Runeword.Honor, "dimensionalblade"]);
- Config.Runewords.push([Runeword.Honor, "flamberge"]);
- Config.Runewords.push([Runeword.Honor, "zweihander"]);
- Config.Runewords.push([Runeword.Honor, "phaseblade"]);
- Config.Runewords.push([Runeword.Honor, "colossussword"]);
+ NTIP.addLine("[type] == Sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 187 # [sockets] == 5 # [maxquantity] == 1");
+ }
- Config.Recipes.push([Recipe.Socket.Weapon, "flamberge"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "zweihander"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "dimensionalblade"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "colossussword"]);
+ Config.Runewords.push([Runeword.Honor, "dimensionalblade"]);
+ Config.Runewords.push([Runeword.Honor, "flamberge"]);
+ Config.Runewords.push([Runeword.Honor, "zweihander"]);
+ Config.Runewords.push([Runeword.Honor, "phaseblade"]);
+ Config.Runewords.push([Runeword.Honor, "colossussword"]);
- Config.KeepRunewords.push("[type] == sword # [enhanceddamage] >= 160 && [tohit] >= 250 && [itemallskills] >= 1");
+ Config.Recipes.push([Recipe.Socket.Weapon, "flamberge"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "zweihander"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "dimensionalblade"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "colossussword"]);
+
+ Config.KeepRunewords.push("[type] == sword # [enhanceddamage] >= 160 && [tohit] >= 250 && [itemallskills] >= 1");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Ice.js b/libs/SoloPlay/BuildFiles/Runewords/Ice.js
index 6c6b1249..22eba936 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Ice.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Ice.js
@@ -1,56 +1,75 @@
-(function() {
- const IceRunes = [
- "[name] == AmnRune # # [maxquantity] == 1",
- "[name] == ShaelRune # # [maxquantity] == 1",
- "[name] == JahRune",
- "[name] == LoRune",
- ];
- NTIP.arrayLooping(IceRunes);
-
- // Cube to Lo and Keep cubing to Jah rune
- if (Item.getQuantityOwned(me.getItem(sdk.items.runes.Lo)) > 1 && me.checkItem({name: sdk.locale.items.ChainofHonor}).have) {
- if (!me.getItem(sdk.items.runes.Jah)) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
- !me.getItem(sdk.items.runes.Jah) && Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- }
- // Cube to Jah rune
- if (!me.getItem(sdk.items.runes.Jah) && me.checkItem({name: sdk.locale.items.ChainofHonor}).have) {
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- }
-
- if (me.amazon) {
- NTIP.addLine("([name] == matriarchalbow || [name] == grandmatronbow) && [quality] == superior # [bowandcrossbowskilltab] == 3 && [enhanceddamage] >= 10 && [sockets] == 4 # [maxquantity] == 1");
-
- Config.Runewords.push([Runeword.Ice, "matriarchalbow"]);
- Config.Runewords.push([Runeword.Ice, "grandmatronbow"]);
-
- Config.Recipes.push([Recipe.Socket.Bow, "matriarchalbow"]);
- Config.Recipes.push([Recipe.Socket.Bow, "grandmatronbow"]);
-
- if (!Check.haveBase("amazonbow", 4) && me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Jah)) {
- NTIP.addLine("([name] == matriarchalbow || [name] == grandmatronbow) && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- }
-
- Config.KeepRunewords.push("[type] == amazonbow # [enhanceddamage] >= 140 && [passivecoldpierce] >= 25");
-
- } else {
- NTIP.addLine("[type] == demoncrossbow && [quality] == superior # [enhanceddamage] >= 10 && [sockets] == 4 # [maxquantity] == 1");
-
- Config.Runewords.push([Runeword.Ice, "demoncrossbow"]);
-
- Config.Recipes.push([Recipe.Socket.Crossbow, "demoncrossbow"]);
-
- if (!Check.haveBase("crossbow", 4) && me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Jah)) {
- NTIP.addLine("[name] == demoncrossbow && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- }
-
- Config.KeepRunewords.push("[type] == demoncrossbow # [enhanceddamage] >= 140 && [passivecoldpierce] >= 25");
- }
+(function () {
+ const IceRunes = [
+ "[name] == AmnRune # # [maxquantity] == 1",
+ "[name] == ShaelRune # # [maxquantity] == 1",
+ "[name] == JahRune",
+ "[name] == LoRune",
+ ];
+ NTIP.buildList(IceRunes);
+
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.AmazonBow,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ ethereal: false,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+
+ // Cube to Lo and Keep cubing to Jah rune
+ if (me.getOwned({ classid: sdk.items.runes.Lo }).length > 1
+ && me.checkItem({ name: sdk.locale.items.ChainofHonor }).have) {
+ if (!me.getItem(sdk.items.runes.Jah)) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+ !me.getItem(sdk.items.runes.Jah) && Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ }
+ // Cube to Jah rune
+ if (!me.getItem(sdk.items.runes.Jah) && me.checkItem({ name: sdk.locale.items.ChainofHonor }).have) {
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ }
+
+ if (me.amazon) {
+ NTIP.addLine("([name] == matriarchalbow || [name] == grandmatronbow) && [quality] == superior # [bowandcrossbowskilltab] == 3 && [enhanceddamage] >= 10 && [sockets] == 4 # [maxquantity] == 1");
+
+ Config.Runewords.push([Runeword.Ice, "matriarchalbow"]);
+ Config.Runewords.push([Runeword.Ice, "grandmatronbow"]);
+
+ Config.Recipes.push([Recipe.Socket.Bow, "matriarchalbow"]);
+ Config.Recipes.push([Recipe.Socket.Bow, "grandmatronbow"]);
+
+ if (!me.getOwned(wanted).length
+ && me.getItem(sdk.items.runes.Lo)
+ && me.getItem(sdk.items.runes.Jah)) {
+ NTIP.addLine("([name] == matriarchalbow || [name] == grandmatronbow) && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ }
+
+ Config.KeepRunewords.push("[type] == amazonbow # [enhanceddamage] >= 140 && [passivecoldpierce] >= 25");
+
+ } else {
+ wanted.itemType = sdk.items.type.Crossbow;
+ wanted.classid = sdk.items.DemonCrossbow;
+ NTIP.addLine("[type] == demoncrossbow && [quality] == superior # [enhanceddamage] >= 10 && [sockets] == 4 # [maxquantity] == 1");
+
+
+ if (!me.getOwned(wanted).length
+ && me.getItem(sdk.items.runes.Lo)
+ && me.getItem(sdk.items.runes.Jah)) {
+ NTIP.addLine("[name] == demoncrossbow && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ }
+
+ Config.Runewords.push([Runeword.Ice, "demoncrossbow"]);
+ Config.Recipes.push([Recipe.Socket.Crossbow, "demoncrossbow"]);
+
+ Config.KeepRunewords.push("[type] == demoncrossbow # [enhanceddamage] >= 140 && [passivecoldpierce] >= 25");
+ }
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/KingsGrace.js b/libs/SoloPlay/BuildFiles/Runewords/KingsGrace.js
index 10e8686e..a7971e28 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/KingsGrace.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/KingsGrace.js
@@ -1,29 +1,29 @@
-(function() {
- const KingsGrace = [
- "[name] == AmnRune # # [maxquantity] == 1",
- "[name] == RalRune # # [maxquantity] == 1",
- "[name] == ThulRune # # [maxquantity] == 1",
- "[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 3 # [maxquantity] == 1",
- "([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == colossussword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(KingsGrace);
+(function () {
+ const KingsGrace = [
+ "[name] == AmnRune # # [maxquantity] == 1",
+ "[name] == RalRune # # [maxquantity] == 1",
+ "[name] == ThulRune # # [maxquantity] == 1",
+ "[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 3 # [maxquantity] == 1",
+ "([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == colossussword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(KingsGrace);
- Config.Runewords.push([Runeword.KingsGrace, "broadsword"]);
- Config.Runewords.push([Runeword.KingsGrace, "longsword"]);
- Config.Runewords.push([Runeword.KingsGrace, "warsword"]);
- Config.Runewords.push([Runeword.KingsGrace, "giantsword"]);
- Config.Runewords.push([Runeword.KingsGrace, "flamberge"]);
- Config.Runewords.push([Runeword.KingsGrace, "dimensionalblade"]);
- Config.Runewords.push([Runeword.KingsGrace, "battlesword"]);
- Config.Runewords.push([Runeword.KingsGrace, "runesword"]);
- Config.Runewords.push([Runeword.KingsGrace, "ancientsword"]);
- Config.Runewords.push([Runeword.KingsGrace, "espandon"]);
- Config.Runewords.push([Runeword.KingsGrace, "tusksword"]);
- Config.Runewords.push([Runeword.KingsGrace, "zweihander"]);
- Config.Runewords.push([Runeword.KingsGrace, "legendsword"]);
- Config.Runewords.push([Runeword.KingsGrace, "highlandblade"]);
- Config.Runewords.push([Runeword.KingsGrace, "balrogblade"]);
- Config.Runewords.push([Runeword.KingsGrace, "colossussword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "broadsword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "longsword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "warsword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "giantsword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "flamberge"]);
+ Config.Runewords.push([Runeword.KingsGrace, "dimensionalblade"]);
+ Config.Runewords.push([Runeword.KingsGrace, "battlesword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "runesword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "ancientsword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "espandon"]);
+ Config.Runewords.push([Runeword.KingsGrace, "tusksword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "zweihander"]);
+ Config.Runewords.push([Runeword.KingsGrace, "legendsword"]);
+ Config.Runewords.push([Runeword.KingsGrace, "highlandblade"]);
+ Config.Runewords.push([Runeword.KingsGrace, "balrogblade"]);
+ Config.Runewords.push([Runeword.KingsGrace, "colossussword"]);
- Config.KeepRunewords.push("[type] == sword # [enhanceddamage] >= 100 && [lifeleech] >= 7");
+ Config.KeepRunewords.push("[type] == sword # [enhanceddamage] >= 100 && [lifeleech] >= 7");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/LastWish.js b/libs/SoloPlay/BuildFiles/Runewords/LastWish.js
index fd5ac37e..657c5541 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/LastWish.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/LastWish.js
@@ -1,31 +1,31 @@
-(function() {
- // Jah/Mal/Jah/Sur/Jah/Ber
- const LW = [
- "[name] == JahRune",
- "[name] == MalRune",
- "[name] == SurRune",
- "[name] == BerRune",
- "[name] == phaseblade && [quality] >= normal && [quality] <= superior # [sockets] == 6 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(LW);
- // Cube to Jah/Sur rune
- if (!me.getItem(sdk.items.runes.Jah) || !me.getItem(sdk.items.runes.Sur)) {
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
+(function () {
+ // Jah/Mal/Jah/Sur/Jah/Ber
+ const LW = [
+ "[name] == JahRune",
+ "[name] == MalRune",
+ "[name] == SurRune",
+ "[name] == BerRune",
+ "[name] == phaseblade && [quality] >= normal && [quality] <= superior # [sockets] == 6 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(LW);
+ // Cube to Jah/Sur rune
+ if (!me.getItem(sdk.items.runes.Jah) || !me.getItem(sdk.items.runes.Sur)) {
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- if (!me.getItem(sdk.items.runes.Jah)) {
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- }
-
- Config.Runewords.push([Runeword.LastWish, "phaseblade"]);
- Config.KeepRunewords.push("[type] == sword # [mightaura] >= 17");
- }
+ if (!me.getItem(sdk.items.runes.Jah)) {
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ }
+
+ Config.Runewords.push([Runeword.LastWish, "phaseblade"]);
+ Config.KeepRunewords.push("[type] == sword # [mightaura] >= 17");
+ }
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Lawbringer.js b/libs/SoloPlay/BuildFiles/Runewords/Lawbringer.js
index 930a1dd0..1c31be3b 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Lawbringer.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Lawbringer.js
@@ -1,33 +1,33 @@
-(function() {
- const Lawbringer = [
- "[name] == AmnRune # # [maxquantity] == 1",
- "[name] == LemRune",
- "[name] == KoRune",
- ];
- NTIP.arrayLooping(Lawbringer);
+(function () {
+ const Lawbringer = [
+ "[name] == AmnRune # # [maxquantity] == 1",
+ "[name] == LemRune",
+ "[name] == KoRune",
+ ];
+ NTIP.buildList(Lawbringer);
- // Have Lem and Ko runes before looking for normal base
- if (me.getItem(sdk.items.runes.Lem) && me.getItem(sdk.items.runes.Ko)) {
- NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 3");
- NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3");
- } else {
- NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] == superior # [enhanceddamage] >= 5 && [sockets] == 3");
- }
+ // Have Lem and Ko runes before looking for normal base
+ if (me.getItem(sdk.items.runes.Lem) && me.getItem(sdk.items.runes.Ko)) {
+ NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 3");
+ NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3");
+ } else {
+ NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] == superior # [enhanceddamage] >= 5 && [sockets] == 3");
+ }
- Config.Runewords.push([Runeword.Lawbringer, "dimensionalblade"]);
- Config.Runewords.push([Runeword.Lawbringer, "battlesword"]);
- Config.Runewords.push([Runeword.Lawbringer, "runesword"]);
- Config.Runewords.push([Runeword.Lawbringer, "conquestsword"]);
- Config.Runewords.push([Runeword.Lawbringer, "crypticsword"]);
- Config.Runewords.push([Runeword.Lawbringer, "phaseblade"]);
- Config.Runewords.push([Runeword.Lawbringer, "espandon"]);
- Config.Runewords.push([Runeword.Lawbringer, "tusksword"]);
- Config.Runewords.push([Runeword.Lawbringer, "zweihander"]);
- Config.Runewords.push([Runeword.Lawbringer, "legendsword"]);
- Config.Runewords.push([Runeword.Lawbringer, "highlandblade"]);
- Config.Runewords.push([Runeword.Lawbringer, "balrogblade"]);
- Config.Runewords.push([Runeword.Lawbringer, "championsword"]);
- Config.Runewords.push([Runeword.Lawbringer, "colossussword"]);
+ Config.Runewords.push([Runeword.Lawbringer, "dimensionalblade"]);
+ Config.Runewords.push([Runeword.Lawbringer, "battlesword"]);
+ Config.Runewords.push([Runeword.Lawbringer, "runesword"]);
+ Config.Runewords.push([Runeword.Lawbringer, "conquestsword"]);
+ Config.Runewords.push([Runeword.Lawbringer, "crypticsword"]);
+ Config.Runewords.push([Runeword.Lawbringer, "phaseblade"]);
+ Config.Runewords.push([Runeword.Lawbringer, "espandon"]);
+ Config.Runewords.push([Runeword.Lawbringer, "tusksword"]);
+ Config.Runewords.push([Runeword.Lawbringer, "zweihander"]);
+ Config.Runewords.push([Runeword.Lawbringer, "legendsword"]);
+ Config.Runewords.push([Runeword.Lawbringer, "highlandblade"]);
+ Config.Runewords.push([Runeword.Lawbringer, "balrogblade"]);
+ Config.Runewords.push([Runeword.Lawbringer, "championsword"]);
+ Config.Runewords.push([Runeword.Lawbringer, "colossussword"]);
- Config.KeepRunewords.push("[type] == sword # [sanctuaryaura] >= 16");
+ Config.KeepRunewords.push("[type] == sword # [sanctuaryaura] >= 16");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Lore.js b/libs/SoloPlay/BuildFiles/Runewords/Lore.js
index 216b658e..ce271e1d 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Lore.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Lore.js
@@ -1,135 +1,135 @@
-(function() {
- if (!Check.haveItem("helm", "runeword", "Lore")) {
- const loreRunes = [
- "[name] == OrtRune # # [maxquantity] == 1",
- "[name] == SolRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(loreRunes);
+(function () {
+ if (!me.checkItem({ name: sdk.locale.items.Lore }).have) {
+ const loreRunes = [
+ "[name] == OrtRune # # [maxquantity] == 1",
+ "[name] == SolRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(loreRunes);
- // Cube to Sol rune
- if (!me.getItem(sdk.items.runes.Sol)) {
- Config.Recipes.push([Recipe.Rune, "Ort Rune"]);
- Config.Recipes.push([Recipe.Rune, "Thul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Amn Rune"]);
- }
- } else {
- // Cube to Sol rune
- if (!me.getItem(sdk.items.runes.Sol)) {
- Config.Recipes.push([Recipe.Rune, "Amn Rune"]);
- }
- }
+ // Cube to Sol rune
+ if (!me.getItem(sdk.items.runes.Sol)) {
+ Config.Recipes.push([Recipe.Rune, "Ort Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Thul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Amn Rune"]);
+ }
+ } else {
+ // Cube to Sol rune
+ if (!me.getItem(sdk.items.runes.Sol)) {
+ Config.Recipes.push([Recipe.Rune, "Amn Rune"]);
+ }
+ }
- let classLoreHelm = [];
- const loreHelm = [
- "!me.hell && ([name] == crown || [name] == bonehelm || [name] == fullhelm) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
- "([name] == casque || [name] == sallet || [name] == deathmask || [name] == grimhelm) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
- ];
+ let classLoreHelm = [];
+ const loreHelm = [
+ "!me.hell && ([name] == crown || [name] == bonehelm || [name] == fullhelm) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
+ "([name] == casque || [name] == sallet || [name] == deathmask || [name] == grimhelm) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
+ ];
- if (me.druid) {
- classLoreHelm = [
- "[name] == OrtRune # # [maxquantity] == 1",
- "[name] == SolRune # # [maxquantity] == 1",
- "[type] == pelt && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2",
- "[type] == pelt && [quality] == normal # ([druidskills]+[elementalskilltab]+[skillcyclonearmor]+[skilltwister]+[skilltornado]+[skillhurricane]) >= 1 && [sockets] == 0",
- ];
- NTIP.arrayLooping(classLoreHelm);
+ if (me.druid) {
+ classLoreHelm = [
+ "[name] == OrtRune # # [maxquantity] == 1",
+ "[name] == SolRune # # [maxquantity] == 1",
+ "[type] == pelt && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2",
+ "[type] == pelt && [quality] == normal # ([druidskills]+[elementalskilltab]+[skillcyclonearmor]+[skilltwister]+[skilltornado]+[skillhurricane]) >= 1 && [sockets] == 0",
+ ];
+ NTIP.buildList(classLoreHelm);
- // Normal Helms
- if (Item.getEquippedItem(sdk.body.Head).tier < 150) {
- NTIP.arrayLooping(loreHelm);
- }
+ // Normal Helms
+ if (me.equipped.get(sdk.body.Head).tier < 150) {
+ NTIP.buildList(loreHelm);
+ }
- // Pelts
- Config.Runewords.push([Runeword.Lore, "wolfhead"]);
- Config.Runewords.push([Runeword.Lore, "hawkhelm"]);
- Config.Runewords.push([Runeword.Lore, "antlers"]);
- Config.Runewords.push([Runeword.Lore, "falconmask"]);
- Config.Runewords.push([Runeword.Lore, "spiritmask"]);
- Config.Runewords.push([Runeword.Lore, "alphahelm"]);
- Config.Runewords.push([Runeword.Lore, "griffonheaddress"]);
- Config.Runewords.push([Runeword.Lore, "hunter'sguise"]);
- Config.Runewords.push([Runeword.Lore, "sacredfeathers"]);
- Config.Runewords.push([Runeword.Lore, "totemicmask"]);
- Config.Runewords.push([Runeword.Lore, "bloodspirit"]);
- Config.Runewords.push([Runeword.Lore, "sunspirit"]);
- Config.Runewords.push([Runeword.Lore, "earthspirit"]);
- Config.Runewords.push([Runeword.Lore, "skyspirit"]);
- Config.Runewords.push([Runeword.Lore, "dreamspirit"]);
+ // Pelts
+ Config.Runewords.push([Runeword.Lore, "wolfhead"]);
+ Config.Runewords.push([Runeword.Lore, "hawkhelm"]);
+ Config.Runewords.push([Runeword.Lore, "antlers"]);
+ Config.Runewords.push([Runeword.Lore, "falconmask"]);
+ Config.Runewords.push([Runeword.Lore, "spiritmask"]);
+ Config.Runewords.push([Runeword.Lore, "alphahelm"]);
+ Config.Runewords.push([Runeword.Lore, "griffonheaddress"]);
+ Config.Runewords.push([Runeword.Lore, "hunter'sguise"]);
+ Config.Runewords.push([Runeword.Lore, "sacredfeathers"]);
+ Config.Runewords.push([Runeword.Lore, "totemicmask"]);
+ Config.Runewords.push([Runeword.Lore, "bloodspirit"]);
+ Config.Runewords.push([Runeword.Lore, "sunspirit"]);
+ Config.Runewords.push([Runeword.Lore, "earthspirit"]);
+ Config.Runewords.push([Runeword.Lore, "skyspirit"]);
+ Config.Runewords.push([Runeword.Lore, "dreamspirit"]);
- Config.Recipes.push([Recipe.Socket.Helm, "wolfhead"]);
- Config.Recipes.push([Recipe.Socket.Helm, "hawkhelm"]);
- Config.Recipes.push([Recipe.Socket.Helm, "antlers"]);
- Config.Recipes.push([Recipe.Socket.Helm, "falconmask"]);
- Config.Recipes.push([Recipe.Socket.Helm, "alphahelm"]);
- Config.Recipes.push([Recipe.Socket.Helm, "griffonheaddress"]);
- Config.Recipes.push([Recipe.Socket.Helm, "hunter'sguise"]);
- Config.Recipes.push([Recipe.Socket.Helm, "sacredfeathers"]);
- Config.Recipes.push([Recipe.Socket.Helm, "totemicmask"]);
- Config.Recipes.push([Recipe.Socket.Helm, "bloodspirit"]);
- Config.Recipes.push([Recipe.Socket.Helm, "sunspirit"]);
- Config.Recipes.push([Recipe.Socket.Helm, "earthspirit"]);
- Config.Recipes.push([Recipe.Socket.Helm, "skyspirit"]);
- Config.Recipes.push([Recipe.Socket.Helm, "dreamspirit"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "wolfhead"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "hawkhelm"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "antlers"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "falconmask"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "alphahelm"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "griffonheaddress"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "hunter'sguise"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "sacredfeathers"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "totemicmask"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "bloodspirit"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "sunspirit"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "earthspirit"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "skyspirit"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "dreamspirit"]);
- Config.KeepRunewords.push("[type] == pelt # [lightresist] >= 25");
- }
+ Config.KeepRunewords.push("[type] == pelt # [lightresist] >= 25");
+ }
- if (me.barbarian) {
- classLoreHelm = [
- "[name] == OrtRune # # [maxquantity] == 1",
- "[name] == SolRune # # [maxquantity] == 1",
- "[type] == primalhelm && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [strreq] <= 150 # [sockets] == 2",
- "[type] == primalhelm && [flag] != ethereal && [quality] == normal && [strreq] <= 150 # ([barbarianskills]+[barbcombatskilltab]+[skillbattleorders]+[skillfrenzy]+[skilldoubleswing]+[skillnaturalresistance]) >= 1 && [sockets] == 0",
- ];
- NTIP.arrayLooping(classLoreHelm);
+ if (me.barbarian) {
+ classLoreHelm = [
+ "[name] == OrtRune # # [maxquantity] == 1",
+ "[name] == SolRune # # [maxquantity] == 1",
+ "[type] == primalhelm && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [strreq] <= 150 # [sockets] == 2",
+ "[type] == primalhelm && [flag] != ethereal && [quality] == normal && [strreq] <= 150 # ([barbarianskills]+[barbcombatskilltab]+[skillbattleorders]+[skillfrenzy]+[skilldoubleswing]+[skillnaturalresistance]) >= 1 && [sockets] == 0",
+ ];
+ NTIP.buildList(classLoreHelm);
- // Normal Helms
- if (Item.getEquippedItem(sdk.body.Head).tier < 150) {
- NTIP.arrayLooping(loreHelm);
- }
+ // Normal Helms
+ if (me.equipped.get(sdk.body.Head).tier < 150) {
+ NTIP.buildList(loreHelm);
+ }
- // Primal Helms
- Config.Runewords.push([Runeword.Lore, "jawbonecap"]);
- Config.Runewords.push([Runeword.Lore, "fangedhelm"]);
- Config.Runewords.push([Runeword.Lore, "hornedhelm"]);
- Config.Runewords.push([Runeword.Lore, "assaulthelmet"]);
- Config.Runewords.push([Runeword.Lore, "avengerguard"]);
- Config.Runewords.push([Runeword.Lore, "jawbonevisor"]);
- Config.Runewords.push([Runeword.Lore, "lionhelm"]);
- Config.Runewords.push([Runeword.Lore, "ragemask"]);
- Config.Runewords.push([Runeword.Lore, "savagehelmet"]);
- Config.Runewords.push([Runeword.Lore, "slayerguard"]);
- Config.Runewords.push([Runeword.Lore, "carnagehelm"]);
- Config.Runewords.push([Runeword.Lore, "furyvisor"]);
+ // Primal Helms
+ Config.Runewords.push([Runeword.Lore, "jawbonecap"]);
+ Config.Runewords.push([Runeword.Lore, "fangedhelm"]);
+ Config.Runewords.push([Runeword.Lore, "hornedhelm"]);
+ Config.Runewords.push([Runeword.Lore, "assaulthelmet"]);
+ Config.Runewords.push([Runeword.Lore, "avengerguard"]);
+ Config.Runewords.push([Runeword.Lore, "jawbonevisor"]);
+ Config.Runewords.push([Runeword.Lore, "lionhelm"]);
+ Config.Runewords.push([Runeword.Lore, "ragemask"]);
+ Config.Runewords.push([Runeword.Lore, "savagehelmet"]);
+ Config.Runewords.push([Runeword.Lore, "slayerguard"]);
+ Config.Runewords.push([Runeword.Lore, "carnagehelm"]);
+ Config.Runewords.push([Runeword.Lore, "furyvisor"]);
- Config.Recipes.push([Recipe.Socket.Helm, "jawbonecap"]);
- Config.Recipes.push([Recipe.Socket.Helm, "fangedhelm"]);
- Config.Recipes.push([Recipe.Socket.Helm, "hornedhelm"]);
- Config.Recipes.push([Recipe.Socket.Helm, "assaulthelmet"]);
- Config.Recipes.push([Recipe.Socket.Helm, "avengerguard"]);
- Config.Recipes.push([Recipe.Socket.Helm, "jawbonevisor"]);
- Config.Recipes.push([Recipe.Socket.Helm, "lionhelm"]);
- Config.Recipes.push([Recipe.Socket.Helm, "ragemask"]);
- Config.Recipes.push([Recipe.Socket.Helm, "savagehelmet"]);
- Config.Recipes.push([Recipe.Socket.Helm, "slayerguard"]);
- Config.Recipes.push([Recipe.Socket.Helm, "carnagehelm"]);
- Config.Recipes.push([Recipe.Socket.Helm, "furyvisor"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "jawbonecap"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "fangedhelm"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "hornedhelm"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "assaulthelmet"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "avengerguard"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "jawbonevisor"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "lionhelm"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "ragemask"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "savagehelmet"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "slayerguard"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "carnagehelm"]);
+ Config.Recipes.push([Recipe.Socket.Helm, "furyvisor"]);
- Config.KeepRunewords.push("[type] == primalhelm # [LightResist] >= 25");
- }
+ Config.KeepRunewords.push("[type] == primalhelm # [LightResist] >= 25");
+ }
- if (!me.druid && !me.barbarian) {
- NTIP.arrayLooping(loreHelm);
- }
+ if (!me.druid && !me.barbarian) {
+ NTIP.buildList(loreHelm);
+ }
- // Normal helms
- Config.Runewords.push([Runeword.Lore, "crown"]);
- Config.Runewords.push([Runeword.Lore, "grimhelm"]);
- Config.Runewords.push([Runeword.Lore, "bonehelm"]);
- Config.Runewords.push([Runeword.Lore, "sallet"]);
- Config.Runewords.push([Runeword.Lore, "casque"]);
- Config.Runewords.push([Runeword.Lore, "deathmask"]);
- Config.Runewords.push([Runeword.Lore, "fullhelm"]);
+ // Normal helms
+ Config.Runewords.push([Runeword.Lore, "crown"]);
+ Config.Runewords.push([Runeword.Lore, "grimhelm"]);
+ Config.Runewords.push([Runeword.Lore, "bonehelm"]);
+ Config.Runewords.push([Runeword.Lore, "sallet"]);
+ Config.Runewords.push([Runeword.Lore, "casque"]);
+ Config.Runewords.push([Runeword.Lore, "deathmask"]);
+ Config.Runewords.push([Runeword.Lore, "fullhelm"]);
- Config.KeepRunewords.push("([type] == circlet || [type] == helm) # [lightresist] >= 25");
+ Config.KeepRunewords.push("([type] == circlet || [type] == helm) # [lightresist] >= 25");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Malice.js b/libs/SoloPlay/BuildFiles/Runewords/Malice.js
index f25fa26f..04345cd2 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Malice.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Malice.js
@@ -1,21 +1,21 @@
-(function() {
- const Malice = [
- "[name] == IthRune # # [maxquantity] == 1",
- "[name] == ElRune # # [maxquantity] == 1",
- "[name] == EthRune # # [maxquantity] == 1",
- "[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 3 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Malice);
+(function () {
+ const Malice = [
+ "[name] == IthRune # # [maxquantity] == 1",
+ "[name] == ElRune # # [maxquantity] == 1",
+ "[name] == EthRune # # [maxquantity] == 1",
+ "[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 3 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Malice);
- Config.Runewords.push([Runeword.Malice, "crystalsword"]);
- Config.Runewords.push([Runeword.Malice, "broadsword"]);
- Config.Runewords.push([Runeword.Malice, "longsword"]);
- Config.Runewords.push([Runeword.Malice, "warsword"]);
- Config.Runewords.push([Runeword.Malice, "giantsword"]);
- Config.Runewords.push([Runeword.Malice, "flamberge"]);
- Config.Runewords.push([Runeword.Malice, "espandon"]);
- Config.Runewords.push([Runeword.Malice, "tusksword"]);
- Config.Runewords.push([Runeword.Malice, "zweihander"]);
+ Config.Runewords.push([Runeword.Malice, "crystalsword"]);
+ Config.Runewords.push([Runeword.Malice, "broadsword"]);
+ Config.Runewords.push([Runeword.Malice, "longsword"]);
+ Config.Runewords.push([Runeword.Malice, "warsword"]);
+ Config.Runewords.push([Runeword.Malice, "giantsword"]);
+ Config.Runewords.push([Runeword.Malice, "flamberge"]);
+ Config.Runewords.push([Runeword.Malice, "espandon"]);
+ Config.Runewords.push([Runeword.Malice, "tusksword"]);
+ Config.Runewords.push([Runeword.Malice, "zweihander"]);
- Config.KeepRunewords.push("[type] == sword # [enhanceddamage] >= 33 && [tohit] >= 50");
+ Config.KeepRunewords.push("[type] == sword # [enhanceddamage] >= 33 && [tohit] >= 50");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/MercDoom.js b/libs/SoloPlay/BuildFiles/Runewords/MercDoom.js
index 03c6ed73..2acafa9c 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/MercDoom.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/MercDoom.js
@@ -1,63 +1,77 @@
-(function() {
- const Doom = [
- "[name] == HelRune # # [maxquantity] == 1",
- "[name] == OhmRune",
- "[name] == LoRune",
- "[name] == UmRune",
- "[name] == ChamRune",
- ];
- NTIP.arrayLooping(Doom);
+(function () {
+ const Doom = [
+ "[name] == HelRune # # [maxquantity] == 1",
+ "[name] == OhmRune",
+ "[name] == LoRune",
+ "[name] == UmRune",
+ "[name] == ChamRune",
+ ];
+ NTIP.buildList(Doom);
- // Have Cham, Lo, and Ohm Rune before looking for normal base
- if (me.getItem(sdk.items.runes.Cham) && me.getItem(sdk.items.runes.Lo) && me.getItem(sdk.items.runes.Ohm)) {
- if (!Check.haveBase("polearm", 5)) {
- NTIP.addLine("([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- }
- NTIP.addLine("([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 5 # [maxquantity] == 1");
- } else {
- NTIP.addLine("([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] == superior # [enhanceddamage] >= 10 && [sockets] == 5 # [maxquantity] == 1");
- }
- // Cube to Cham
- if (!me.getItem(sdk.items.runes.Cham)) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- if ((me.barbarian && me.haveAll([{name: sdk.locale.items.Grief}, {name: sdk.locale.items.Fortitude}]))
- || (["Witchyzon", "Wfzon"].includes(SetUp.finalBuild) && me.checkItem({name: sdk.locale.items.ChainsofHonor}).have)
- || (SetUp.currentBuild === "Faithbowzon")) {
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- }
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- Config.Recipes.push([Recipe.Rune, "Jah Rune"]);
- }
- // Cube to Lo
- if (!me.getItem(sdk.items.runes.Lo)) {
- if ((me.barbarian) || (SetUp.currentBuild === "Faithbowzon" && me.checkItem({name: sdk.locale.items.CalltoArms}).have)
- || (["Witchyzon", "Wfzon"].includes(SetUp.finalBuild) && me.haveAll([{name: sdk.locale.items.ChainsofHonor}, {name: sdk.locale.items.CalltoArms}]))) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
- }
- // Cube to Ohm
- if (!me.getItem(sdk.items.runes.Ohm)) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- }
- Config.Recipes.push([Recipe.Socket.Weapon, "giantthresher"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "greatpoleaxe"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "crypticaxe"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "thresher"]);
- Config.Runewords.push([Runeword.Doom, "giantthresher"]);
- Config.Runewords.push([Runeword.Doom, "greatpoleaxe"]);
- Config.Runewords.push([Runeword.Doom, "crypticaxe"]);
- Config.Runewords.push([Runeword.Doom, "thresher"]);
- Config.KeepRunewords.push("[type] == polearm # [holyfreezeaura] == 12");
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Polearm,
+ mode: sdk.items.mode.inStorage,
+ sockets: 5,
+ ethereal: true,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+
+ // Have Cham, Lo, and Ohm Rune before looking for normal base
+ if (me.haveRunes([sdk.items.runes.Cham, sdk.items.runes.Lo, sdk.items.runes.Ohm])) {
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ }
+ NTIP.addLine("([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 5 # [maxquantity] == 1");
+ } else {
+ NTIP.addLine("([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] == superior # [enhanceddamage] >= 10 && [sockets] == 5 # [maxquantity] == 1");
+ }
+ // Cube to Cham
+ if (!me.getItem(sdk.items.runes.Cham)) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ if ((me.barbarian && me.haveAll([{ name: sdk.locale.items.Grief }, { name: sdk.locale.items.Fortitude }]))
+ || (["Witchyzon", "Wfzon"].includes(SetUp.finalBuild) && me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have)
+ || (SetUp.currentBuild === "Faithbowzon")) {
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ }
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Jah Rune"]);
+ }
+ // Cube to Lo
+ if (!me.getItem(sdk.items.runes.Lo)) {
+ if (me.barbarian
+ || (SetUp.currentBuild === "Faithbowzon" && me.checkItem({ name: sdk.locale.items.CalltoArms }).have)
+ || (["Witchyzon", "Wfzon"].includes(SetUp.finalBuild)
+ && me.haveAll([{ name: sdk.locale.items.ChainsofHonor }, { name: sdk.locale.items.CalltoArms }]))) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+ }
+ // Cube to Ohm
+ if (!me.getItem(sdk.items.runes.Ohm)) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ }
+ Config.Recipes.push([Recipe.Socket.Weapon, "giantthresher"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "greatpoleaxe"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "crypticaxe"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "thresher"]);
+ Config.Runewords.push([Runeword.Doom, "giantthresher"]);
+ Config.Runewords.push([Runeword.Doom, "greatpoleaxe"]);
+ Config.Runewords.push([Runeword.Doom, "crypticaxe"]);
+ Config.Runewords.push([Runeword.Doom, "thresher"]);
+ Config.KeepRunewords.push("[type] == polearm # [holyfreezeaura] == 12");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/MercFortitude.js b/libs/SoloPlay/BuildFiles/Runewords/MercFortitude.js
index 22a51270..711183bf 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/MercFortitude.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/MercFortitude.js
@@ -1,52 +1,52 @@
-(function() {
- const fort = [
- "[name] == ElRune # # [maxquantity] == 1",
- "[name] == SolRune # # [maxquantity] == 1",
- "[name] == DolRune # # [maxquantity] == 1",
- "[name] == LoRune",
- "([name] == hellforgeplate || [name] == krakenshell || [name] == archonplate || [name] == balrogskin || [name] == boneweave || [name] == greathauberk || [name] == loricatedmail || [name] == diamondmail || [name] == wirefleece || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [quality] == normal && [flag] == ethereal # [Defense] >= 1000 && [sockets] == 4 # [maxquantity] == 1",
- "([name] == hellforgeplate || [name] == krakenshell || [name] == archonplate || [name] == balrogskin || [name] == boneweave || [name] == greathauberk || [name] == loricatedmail || [name] == diamondmail || [name] == wirefleece || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [quality] == normal && [flag] == ethereal # [Defense] >= 700 && [sockets] == 0 # [maxquantity] == 1",
- ];
+(function () {
+ const fort = [
+ "[name] == ElRune # # [maxquantity] == 1",
+ "[name] == SolRune # # [maxquantity] == 1",
+ "[name] == DolRune # # [maxquantity] == 1",
+ "[name] == LoRune",
+ "([name] == hellforgeplate || [name] == krakenshell || [name] == archonplate || [name] == balrogskin || [name] == boneweave || [name] == greathauberk || [name] == loricatedmail || [name] == diamondmail || [name] == wirefleece || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [quality] == normal && [flag] == ethereal # [Defense] >= 1000 && [sockets] == 4 # [maxquantity] == 1",
+ "([name] == hellforgeplate || [name] == krakenshell || [name] == archonplate || [name] == balrogskin || [name] == boneweave || [name] == greathauberk || [name] == loricatedmail || [name] == diamondmail || [name] == wirefleece || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [quality] == normal && [flag] == ethereal # [Defense] >= 700 && [sockets] == 0 # [maxquantity] == 1",
+ ];
- if (["Zealer", "Smiter", "Frenzy", "Whirlwind", "Uberconc", "Wolf"].includes(SetUp.finalBuild)) {
- // Make Grief first, if using it for final build
- if (me.checkItem({name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword}).have) {
- NTIP.arrayLooping(fort);
- }
- } else if (["Blova", "Lightning"].includes(SetUp.currentBuild)) {
- // Make Chains of Honor first for Blova/Lightning, or already have ber so Lo isn't needed for cubing
- if (me.checkItem({name: sdk.locale.items.ChainsofHonor}).have || me.getItem(sdk.items.runes.Ber)) {
- NTIP.arrayLooping(fort);
- }
- } else {
- NTIP.arrayLooping(fort);
- }
+ if (["Zealer", "Smiter", "Frenzy", "Whirlwind", "Uberconc", "Wolf"].includes(SetUp.finalBuild)) {
+ // Make Grief first, if using it for final build
+ if (me.checkItem({ name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword }).have) {
+ NTIP.buildList(fort);
+ }
+ } else if (["Blova", "Lightning"].includes(SetUp.currentBuild)) {
+ // Make Chains of Honor first for Blova/Lightning, or already have ber so Lo isn't needed for cubing
+ if (me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have || me.getItem(sdk.items.runes.Ber)) {
+ NTIP.buildList(fort);
+ }
+ } else {
+ NTIP.buildList(fort);
+ }
- Config.Recipes.push([Recipe.Socket.Armor, "hellforgeplate"]);
- Config.Recipes.push([Recipe.Socket.Armor, "krakenshell"]);
- Config.Recipes.push([Recipe.Socket.Armor, "archonplate"]);
- Config.Recipes.push([Recipe.Socket.Armor, "balrogskin"]);
- Config.Recipes.push([Recipe.Socket.Armor, "boneweave"]);
- Config.Recipes.push([Recipe.Socket.Armor, "greathauberk"]);
- Config.Recipes.push([Recipe.Socket.Armor, "loricatedmail"]);
- Config.Recipes.push([Recipe.Socket.Armor, "diamondmail"]);
- Config.Recipes.push([Recipe.Socket.Armor, "wirefleece"]);
- Config.Recipes.push([Recipe.Socket.Armor, "scarabhusk"]);
- Config.Recipes.push([Recipe.Socket.Armor, "wyrmhide"]);
- Config.Recipes.push([Recipe.Socket.Armor, "duskshroud"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "hellforgeplate"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "krakenshell"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "archonplate"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "balrogskin"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "boneweave"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "greathauberk"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "loricatedmail"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "diamondmail"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "wirefleece"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "scarabhusk"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "wyrmhide"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "duskshroud"]);
- Config.Runewords.push([Runeword.Fortitude, "hellforgeplate"]);
- Config.Runewords.push([Runeword.Fortitude, "krakenshell"]);
- Config.Runewords.push([Runeword.Fortitude, "archonplate"]);
- Config.Runewords.push([Runeword.Fortitude, "balrogskin"]);
- Config.Runewords.push([Runeword.Fortitude, "boneweave"]);
- Config.Runewords.push([Runeword.Fortitude, "greathauberk"]);
- Config.Runewords.push([Runeword.Fortitude, "loricatedmail"]);
- Config.Runewords.push([Runeword.Fortitude, "diamondmail"]);
- Config.Runewords.push([Runeword.Fortitude, "wirefleece"]);
- Config.Runewords.push([Runeword.Fortitude, "scarabhusk"]);
- Config.Runewords.push([Runeword.Fortitude, "wyrmhide"]);
- Config.Runewords.push([Runeword.Fortitude, "duskshroud"]);
+ Config.Runewords.push([Runeword.Fortitude, "hellforgeplate"]);
+ Config.Runewords.push([Runeword.Fortitude, "krakenshell"]);
+ Config.Runewords.push([Runeword.Fortitude, "archonplate"]);
+ Config.Runewords.push([Runeword.Fortitude, "balrogskin"]);
+ Config.Runewords.push([Runeword.Fortitude, "boneweave"]);
+ Config.Runewords.push([Runeword.Fortitude, "greathauberk"]);
+ Config.Runewords.push([Runeword.Fortitude, "loricatedmail"]);
+ Config.Runewords.push([Runeword.Fortitude, "diamondmail"]);
+ Config.Runewords.push([Runeword.Fortitude, "wirefleece"]);
+ Config.Runewords.push([Runeword.Fortitude, "scarabhusk"]);
+ Config.Runewords.push([Runeword.Fortitude, "wyrmhide"]);
+ Config.Runewords.push([Runeword.Fortitude, "duskshroud"]);
- Config.KeepRunewords.push("[type] == armor # [enhanceddefense] >= 200 && [enhanceddamage] >= 300");
+ Config.KeepRunewords.push("[type] == armor # [enhanceddefense] >= 200 && [enhanceddamage] >= 300");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/MercInfinity.js b/libs/SoloPlay/BuildFiles/Runewords/MercInfinity.js
index e52a4c66..f6e5537a 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/MercInfinity.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/MercInfinity.js
@@ -1,36 +1,36 @@
-(function() {
- const Inf = [
- "[name] == BerRune",
- "[name] == MalRune",
- "[name] == IstRune",
- "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] == normal # [Sockets] == 0 # [maxquantity] == 1",
- "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [quality] >= normal && [quality] <= Superior # [Sockets] == 4 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Inf);
+(function () {
+ const Inf = [
+ "[name] == BerRune",
+ "[name] == MalRune",
+ "[name] == IstRune",
+ "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] == normal # [Sockets] == 0 # [maxquantity] == 1",
+ "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [quality] >= normal && [quality] <= Superior # [Sockets] == 4 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Inf);
- // Cube to Ber rune
- if (me.findItems(sdk.items.runes.Ber).length < 2) {
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have || me.barbarian) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
+ // Cube to Ber rune
+ if (me.findItems(sdk.items.runes.Ber).length < 2) {
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have || me.barbarian) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- }
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ }
- Config.Recipes.push([Recipe.Socket.Weapon, "giantthresher"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "greatpoleaxe"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "crypticaxe"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "thresher"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "giantthresher"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "greatpoleaxe"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "crypticaxe"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "thresher"]);
- Config.Runewords.push([Runeword.Infinity, "giantthresher"]);
- Config.Runewords.push([Runeword.Infinity, "greatpoleaxe"]);
- Config.Runewords.push([Runeword.Infinity, "crypticaxe"]);
- Config.Runewords.push([Runeword.Infinity, "thresher"]);
+ Config.Runewords.push([Runeword.Infinity, "giantthresher"]);
+ Config.Runewords.push([Runeword.Infinity, "greatpoleaxe"]);
+ Config.Runewords.push([Runeword.Infinity, "crypticaxe"]);
+ Config.Runewords.push([Runeword.Infinity, "thresher"]);
- Config.KeepRunewords.push("[type] == polearm # [convictionaura] >= 12");
+ Config.KeepRunewords.push("[type] == polearm # [convictionaura] >= 12");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/MercInsight.js b/libs/SoloPlay/BuildFiles/Runewords/MercInsight.js
index 9c34ef93..b52dbbd8 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/MercInsight.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/MercInsight.js
@@ -1,35 +1,75 @@
-(function() {
- let highTier = "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher)";
- let lowTier = "([name] == voulge || [name] == scythe || [name] == poleaxe || [name] == halberd || [name] == warscythe || [name] == bill || [name] == battlescythe || [name] == partizan || [name] == grimscythe)";
- const Insight = [
- (highTier + " && [flag] == ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1"),
- "!me.hell && " + lowTier + " && [quality] >= normal && [quality] <= superior && [flag] != runeword # [sockets] == 4 # [maxquantity] == 1",
- (highTier + " && [quality] >= normal && [quality] <= superior && [flag] != runeword # [sockets] == 4 # [maxquantity] == 1"),
- ];
- NTIP.arrayLooping(Insight);
-
- if (!me.hell && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== sdk.locale.items.Insight && !Check.haveBase("polearm", 4)) {
- NTIP.addLine("[name] == voulge && [flag] != ethereal && [quality] == normal && [level] >= 26 && [level] <= 40 # [sockets] == 0 # [maxquantity] == 1");
- }
-
- Config.Recipes.push([Recipe.Socket.Weapon, "giantthresher"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "greatpoleaxe"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "crypticaxe"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "thresher"]);
-
- Config.Runewords.push([Runeword.Insight, "giantthresher"]);
- Config.Runewords.push([Runeword.Insight, "greatpoleaxe"]);
- Config.Runewords.push([Runeword.Insight, "crypticaxe"]);
- Config.Runewords.push([Runeword.Insight, "thresher"]);
- Config.Runewords.push([Runeword.Insight, "grimscythe"]);
- Config.Runewords.push([Runeword.Insight, "partizan"]);
- Config.Runewords.push([Runeword.Insight, "battlescythe"]);
- Config.Runewords.push([Runeword.Insight, "bill"]);
- Config.Runewords.push([Runeword.Insight, "Warscythe"]);
- Config.Runewords.push([Runeword.Insight, "halberd"]);
- Config.Runewords.push([Runeword.Insight, "poleaxe"]);
- Config.Runewords.push([Runeword.Insight, "scythe"]);
- Config.Runewords.push([Runeword.Insight, "voulge"]);
-
- Config.KeepRunewords.push("[type] == polearm # [meditationaura] >= 12");
+(function () {
+ let low = [
+ sdk.items.Voulge, sdk.items.Scythe, sdk.items.Poleaxe,
+ sdk.items.Halberd, sdk.items.WarScythe
+ ].map(el => "[name] == " + el).join(" || ");
+
+ let mid = [
+ sdk.items.Bill, sdk.items.BattleScythe,
+ sdk.items.Partizan, sdk.items.GrimScythe
+ ].map(el => "[name] == " + el).join(" || ");
+
+ let high = [
+ sdk.items.Thresher, sdk.items.CrypticAxe,
+ sdk.items.GreatPoleaxe, sdk.items.GiantThresher
+ ].map(el => "[name] == " + el).join(" || ");
+
+ const Insight = [
+ ("(" + high + ") && [flag] == ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1"),
+ ("(" + high + ") && [quality] >= normal && [quality] <= superior && [flag] != runeword # [sockets] == 4 # [maxquantity] == 1"),
+ ("(" + mid + ") && [quality] >= normal && [quality] <= superior && [flag] != runeword # [sockets] == 4 # [maxquantity] == 1"),
+ ];
+
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Polearm,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+
+ Config.Recipes.push([Recipe.Socket.Weapon, "giantthresher"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "greatpoleaxe"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "crypticaxe"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "thresher"]);
+
+ Config.Runewords.push([Runeword.Insight, "giantthresher"]);
+ Config.Runewords.push([Runeword.Insight, "greatpoleaxe"]);
+ Config.Runewords.push([Runeword.Insight, "crypticaxe"]);
+ Config.Runewords.push([Runeword.Insight, "thresher"]);
+ Config.Runewords.push([Runeword.Insight, "grimscythe"]);
+ Config.Runewords.push([Runeword.Insight, "partizan"]);
+ Config.Runewords.push([Runeword.Insight, "battlescythe"]);
+ Config.Runewords.push([Runeword.Insight, "bill"]);
+
+ if (!me.hell) {
+ Insight.push("(" + low + ") && [quality] >= normal && [quality] <= superior && [flag] != runeword # [sockets] == 4 # [maxquantity] == 1");
+ Config.Runewords.push([Runeword.Insight, "Warscythe"]);
+ Config.Runewords.push([Runeword.Insight, "halberd"]);
+ Config.Runewords.push([Runeword.Insight, "poleaxe"]);
+ Config.Runewords.push([Runeword.Insight, "scythe"]);
+ Config.Runewords.push([Runeword.Insight, "voulge"]);
+ }
+
+ let currEquipped = Item.getMercEquipped(sdk.body.RightArm).prefixnum;
+
+ // if (currEquipped === sdk.locale.items.Insight) {
+ // if (Storage.Stash.UsedSpacePercent() < 75
+ // || me.haveRunes([sdk.items.runes.Ral, sdk.items.runes.Tir, sdk.items.runes.Tal, sdk.items.runes.Sol])) {
+ // NTIP.buildList(Insight);
+ // }
+ // } else {
+ // }
+ NTIP.buildList(Insight);
+
+ if (!me.hell
+ && currEquipped !== sdk.locale.items.Insight
+ && !me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == voulge && [flag] != ethereal && [quality] == normal && [level] >= 26 && [level] <= 40 # [sockets] == 0 # [maxquantity] == 1");
+ }
+
+ Config.KeepRunewords.push("[type] == polearm # [meditationaura] >= 12");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/MercPride.js b/libs/SoloPlay/BuildFiles/Runewords/MercPride.js
index 3da997d7..097d242b 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/MercPride.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/MercPride.js
@@ -1,36 +1,36 @@
-(function() {
- const Pride = [
- "[name] == ChamRune",
- "[name] == SurRune",
- "[name] == IoRune ## [maxquantity] == 1",
- "[name] == LoRune",
- "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] == normal # [Sockets] == 0 # [maxquantity] == 1",
- "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [quality] >= normal && [quality] <= Superior # [Sockets] == 4 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Pride);
+(function () {
+ const Pride = [
+ "[name] == ChamRune",
+ "[name] == SurRune",
+ "[name] == IoRune ## [maxquantity] == 1",
+ "[name] == LoRune",
+ "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [flag] == ethereal && [quality] == normal # [Sockets] == 0 # [maxquantity] == 1",
+ "([name] == thresher || [name] == crypticaxe || [name] == greatpoleaxe || [name] == giantthresher) && [quality] >= normal && [quality] <= Superior # [Sockets] == 4 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Pride);
- // Cube to Sur/Lo rune
- if (!me.getItem(sdk.items.runes.Sur) || !me.getItem(sdk.items.runes.Lo)) {
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have || me.barbarian) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
+ // Cube to Sur/Lo rune
+ if (!me.getItem(sdk.items.runes.Sur) || !me.getItem(sdk.items.runes.Lo)) {
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have || me.barbarian) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
- !me.getItem(sdk.items.runes.Sur) && Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- }
+ !me.getItem(sdk.items.runes.Sur) && Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ }
- Config.Recipes.push([Recipe.Socket.Weapon, "giantthresher"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "greatpoleaxe"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "crypticaxe"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "thresher"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "giantthresher"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "greatpoleaxe"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "crypticaxe"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "thresher"]);
- Config.Runewords.push([Runeword.Pride, "giantthresher"]);
- Config.Runewords.push([Runeword.Pride, "greatpoleaxe"]);
- Config.Runewords.push([Runeword.Pride, "crypticaxe"]);
- Config.Runewords.push([Runeword.Pride, "thresher"]);
+ Config.Runewords.push([Runeword.Pride, "giantthresher"]);
+ Config.Runewords.push([Runeword.Pride, "greatpoleaxe"]);
+ Config.Runewords.push([Runeword.Pride, "crypticaxe"]);
+ Config.Runewords.push([Runeword.Pride, "thresher"]);
- Config.KeepRunewords.push("[type] == polearm # [concentrationaura] >= 16");
+ Config.KeepRunewords.push("[type] == polearm # [concentrationaura] >= 16");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/MercTreachery.js b/libs/SoloPlay/BuildFiles/Runewords/MercTreachery.js
index baa468a6..913acb66 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/MercTreachery.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/MercTreachery.js
@@ -1,48 +1,48 @@
-(function() {
- const treach = [
- "[name] == ShaelRune # # [maxquantity] == 1",
- "[name] == ThulRune # # [maxquantity] == 1",
- "[name] == LemRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(treach);
-
- const MercTreachery = [
- "([name] == breastplate || [name] == mageplate || [name] == hellforgeplate || [name] == krakenshell || [name] == archonplate || [name] == balrogskin || [name] == boneweave || [name] == greathauberk || [name] == loricatedmail || [name] == diamondmail || [name] == wirefleece || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
- "!me.normal && ([name] == hellforgeplate || [name] == krakenshell || [name] == archonplate || [name] == balrogskin || [name] == boneweave || [name] == greathauberk || [name] == loricatedmail || [name] == diamondmail || [name] == wirefleece || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [quality] == normal && [flag] == ethereal # [sockets] == 0 # [maxquantity] == 1",
- ];
+(function () {
+ const treach = [
+ "[name] == ShaelRune # # [maxquantity] == 1",
+ "[name] == ThulRune # # [maxquantity] == 1",
+ "[name] == LemRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(treach);
+
+ const MercTreachery = [
+ "([name] == breastplate || [name] == mageplate || [name] == hellforgeplate || [name] == krakenshell || [name] == archonplate || [name] == balrogskin || [name] == boneweave || [name] == greathauberk || [name] == loricatedmail || [name] == diamondmail || [name] == wirefleece || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
+ "!me.normal && ([name] == hellforgeplate || [name] == krakenshell || [name] == archonplate || [name] == balrogskin || [name] == boneweave || [name] == greathauberk || [name] == loricatedmail || [name] == diamondmail || [name] == wirefleece || [name] == scarabhusk || [name] == wyrmhide || [name] == duskshroud) && [quality] == normal && [flag] == ethereal # [sockets] == 0 # [maxquantity] == 1",
+ ];
- // Have Shael and Lem before looking for base
- if (me.getItem(sdk.items.runes.Shael) && me.getItem(sdk.items.runes.Lem)) {
- NTIP.arrayLooping(MercTreachery);
- }
+ // Have Shael and Lem before looking for base
+ if (me.getItem(sdk.items.runes.Shael) && me.getItem(sdk.items.runes.Lem)) {
+ NTIP.buildList(MercTreachery);
+ }
- Config.Recipes.push([Recipe.Socket.Armor, "hellforgeplate"]);
- Config.Recipes.push([Recipe.Socket.Armor, "krakenshell"]);
- Config.Recipes.push([Recipe.Socket.Armor, "archonplate"]);
- Config.Recipes.push([Recipe.Socket.Armor, "balrogskin"]);
- Config.Recipes.push([Recipe.Socket.Armor, "boneweave"]);
- Config.Recipes.push([Recipe.Socket.Armor, "greathauberk"]);
- Config.Recipes.push([Recipe.Socket.Armor, "loricatedmail"]);
- Config.Recipes.push([Recipe.Socket.Armor, "diamondmail"]);
- Config.Recipes.push([Recipe.Socket.Armor, "wirefleece"]);
- Config.Recipes.push([Recipe.Socket.Armor, "scarabhusk"]);
- Config.Recipes.push([Recipe.Socket.Armor, "wyrmhide"]);
- Config.Recipes.push([Recipe.Socket.Armor, "duskshroud"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "hellforgeplate"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "krakenshell"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "archonplate"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "balrogskin"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "boneweave"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "greathauberk"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "loricatedmail"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "diamondmail"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "wirefleece"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "scarabhusk"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "wyrmhide"]);
+ Config.Recipes.push([Recipe.Socket.Armor, "duskshroud"]);
- Config.Runewords.push([Runeword.Treachery, "breastplate"]);
- Config.Runewords.push([Runeword.Treachery, "mageplate"]);
- Config.Runewords.push([Runeword.Treachery, "hellforgeplate"]);
- Config.Runewords.push([Runeword.Treachery, "krakenshell"]);
- Config.Runewords.push([Runeword.Treachery, "archonplate"]);
- Config.Runewords.push([Runeword.Treachery, "balrogskin"]);
- Config.Runewords.push([Runeword.Treachery, "boneweave"]);
- Config.Runewords.push([Runeword.Treachery, "greathauberk"]);
- Config.Runewords.push([Runeword.Treachery, "loricatedmail"]);
- Config.Runewords.push([Runeword.Treachery, "diamondmail"]);
- Config.Runewords.push([Runeword.Treachery, "wirefleece"]);
- Config.Runewords.push([Runeword.Treachery, "scarabhusk"]);
- Config.Runewords.push([Runeword.Treachery, "wyrmhide"]);
- Config.Runewords.push([Runeword.Treachery, "duskshroud"]);
+ Config.Runewords.push([Runeword.Treachery, "breastplate"]);
+ Config.Runewords.push([Runeword.Treachery, "mageplate"]);
+ Config.Runewords.push([Runeword.Treachery, "hellforgeplate"]);
+ Config.Runewords.push([Runeword.Treachery, "krakenshell"]);
+ Config.Runewords.push([Runeword.Treachery, "archonplate"]);
+ Config.Runewords.push([Runeword.Treachery, "balrogskin"]);
+ Config.Runewords.push([Runeword.Treachery, "boneweave"]);
+ Config.Runewords.push([Runeword.Treachery, "greathauberk"]);
+ Config.Runewords.push([Runeword.Treachery, "loricatedmail"]);
+ Config.Runewords.push([Runeword.Treachery, "diamondmail"]);
+ Config.Runewords.push([Runeword.Treachery, "wirefleece"]);
+ Config.Runewords.push([Runeword.Treachery, "scarabhusk"]);
+ Config.Runewords.push([Runeword.Treachery, "wyrmhide"]);
+ Config.Runewords.push([Runeword.Treachery, "duskshroud"]);
- Config.KeepRunewords.push("[type] == armor # [ias] == 45 && [coldresist] == 30");
+ Config.KeepRunewords.push("[type] == armor # [ias] == 45 && [coldresist] == 30");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Myth.js b/libs/SoloPlay/BuildFiles/Runewords/Myth.js
index 22c11f82..6b04fe9d 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Myth.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Myth.js
@@ -1,35 +1,35 @@
-(function() {
- const Myth = [
- "[name] == HelRune # # [maxquantity] == 1",
- "[name] == AmnRune # # [maxquantity] == 1",
- "[name] == NefRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Myth);
+(function () {
+ const Myth = [
+ "[name] == HelRune # # [maxquantity] == 1",
+ "[name] == AmnRune # # [maxquantity] == 1",
+ "[name] == NefRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Myth);
- // Cube to Hel rune
- if (!me.getItem(sdk.items.runes.Hel)) {
- Config.Recipes.push([Recipe.Rune, "Dol Rune"]);
- }
+ // Cube to Hel rune
+ if (!me.getItem(sdk.items.runes.Hel)) {
+ Config.Recipes.push([Recipe.Rune, "Dol Rune"]);
+ }
- // Have Hel rune before looking for base
- if (me.getItem(sdk.items.runes.Hel)) {
- NTIP.addLine("([name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == lightplate || [name] == mageplate || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
- }
+ // Have Hel rune before looking for base
+ if (me.getItem(sdk.items.runes.Hel)) {
+ NTIP.addLine("([name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == lightplate || [name] == mageplate || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
+ }
- // Have Hel rune and currently equipped armor is low tier
- if (me.getItem(sdk.items.runes.Hel) && Item.getEquippedItem(sdk.body.Armor).tier < 200) {
- NTIP.addLine("[name] == breastplate && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
- }
+ // Have Hel rune and currently equipped armor is low tier
+ if (me.getItem(sdk.items.runes.Hel) && me.equipped.get(sdk.body.Armor).tier < 200) {
+ NTIP.addLine("[name] == breastplate && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
+ }
- Config.Runewords.push([Runeword.Myth, "breastplate"]);
- Config.Runewords.push([Runeword.Myth, "demonhidearmor"]);
- Config.Runewords.push([Runeword.Myth, "duskshroud"]);
- Config.Runewords.push([Runeword.Myth, "ghostarmor"]);
- Config.Runewords.push([Runeword.Myth, "lightplate"]);
- Config.Runewords.push([Runeword.Myth, "mageplate"]);
- Config.Runewords.push([Runeword.Myth, "serpentskinarmor"]);
- Config.Runewords.push([Runeword.Myth, "trellisedarmor"]);
- Config.Runewords.push([Runeword.Myth, "wyrmhide"]);
+ Config.Runewords.push([Runeword.Myth, "breastplate"]);
+ Config.Runewords.push([Runeword.Myth, "demonhidearmor"]);
+ Config.Runewords.push([Runeword.Myth, "duskshroud"]);
+ Config.Runewords.push([Runeword.Myth, "ghostarmor"]);
+ Config.Runewords.push([Runeword.Myth, "lightplate"]);
+ Config.Runewords.push([Runeword.Myth, "mageplate"]);
+ Config.Runewords.push([Runeword.Myth, "serpentskinarmor"]);
+ Config.Runewords.push([Runeword.Myth, "trellisedarmor"]);
+ Config.Runewords.push([Runeword.Myth, "wyrmhide"]);
- Config.KeepRunewords.push("[type] == armor # [barbarianskills] == 2");
+ Config.KeepRunewords.push("[type] == armor # [barbarianskills] == 2");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/PDiamondShield.js b/libs/SoloPlay/BuildFiles/Runewords/PDiamondShield.js
index aa6fc36e..72246c81 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/PDiamondShield.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/PDiamondShield.js
@@ -1,16 +1,16 @@
-(function() {
- const PDiamondShield = [
- "[name] == perfectdiamond # # [maxquantity] == 3",
- "[name] == towershield && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(PDiamondShield);
+(function () {
+ const PDiamondShield = [
+ "[name] == perfectdiamond # # [maxquantity] == 3",
+ "[name] == towershield && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(PDiamondShield);
- // cube to Pdiamonds
- if (Item.getQuantityOwned(me.getItem(sdk.items.gems.Perfect.Diamond)) < 3) {
- Config.Recipes.push([Recipe.Gem, "flawlessdiamond"]);
- }
+ // cube to Pdiamonds
+ if (me.getOwned({ classid: sdk.items.gems.Perfect.Diamond }).length < 3) {
+ Config.Recipes.push([Recipe.Gem, "flawlessdiamond"]);
+ }
- Config.Runewords.push([Runeword.PDiamondShield, "towershield"]);
+ Config.Runewords.push([Runeword.PDiamondShield, "towershield"]);
- Config.KeepRunewords.push("[type] == shield # [allres] == 57");
+ Config.KeepRunewords.push("[type] == shield # [allres] == 57");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/PhoenixShield.js b/libs/SoloPlay/BuildFiles/Runewords/PhoenixShield.js
index 2c91c9ce..21d5beef 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/PhoenixShield.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/PhoenixShield.js
@@ -1,41 +1,57 @@
-(function() {
- const PhoenixRunes = [
- "[name] == VexRune",
- "[name] == LoRune",
- "[name] == JahRune",
- "[name] == monarch && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1"
- ];
- NTIP.arrayLooping(PhoenixRunes);
- // Cube to vex and Keep cubing to Jah rune
- if (!me.getItem(sdk.items.runes.Jah)) {
- if (Item.getQuantityOwned(me.getItem(sdk.items.runes.Vex)) < 2) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- }
- (Item.getQuantityOwned(me.getItem(sdk.items.runes.Vex)) > 1 && !me.getItem(sdk.items.runes.Jah) && Config.Recipes.push([Recipe.Rune, "Vex Rune"]));
- }
- // Cube to Lo and Keep cubing to Jah rune
- if (Item.getQuantityOwned(me.getItem(sdk.items.runes.Lo)) > 1 && me.checkItem({name: sdk.locale.items.HeartoftheOak}).have) {
- if (!me.getItem(sdk.items.runes.Jah)) {
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
- !me.getItem(sdk.items.runes.Jah) && Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- }
- // Cube to Jah rune
- if (me.checkItem({name: sdk.locale.items.Enigma}).have && !me.getItem(sdk.items.runes.Jah)) {
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- }
+(function () {
+ const PhoenixRunes = [
+ "[name] == VexRune",
+ "[name] == LoRune",
+ "[name] == JahRune",
+ "[name] == monarch && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1"
+ ];
+ NTIP.buildList(PhoenixRunes);
- if (!Check.haveBase("shield", 4)) {
- NTIP.addLine("[name] == monarch && [flag] != ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- }
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.Monarch,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- Config.Recipes.push([Recipe.Socket.Shield, "monarch", Roll.NonEth]);
- Config.Runewords.push([Runeword.Phoenix, "monarch"]);
+ // Cube to vex and Keep cubing to Jah rune
+ if (!me.getItem(sdk.items.runes.Jah)) {
+ if (me.getOwned({ classid: sdk.items.runes.Vex }).length < 2) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ }
+ if (me.getOwned({ classid: sdk.items.runes.Vex }).length > 1 && !me.getItem(sdk.items.runes.Jah)) {
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ }
+ }
+ // Cube to Lo and Keep cubing to Jah rune
+ if (me.getOwned({ classid: sdk.items.runes.Lo }).length > 1
+ && me.checkItem({ name: sdk.locale.items.HeartoftheOak }).have) {
+ if (!me.getItem(sdk.items.runes.Jah)) {
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+ !me.getItem(sdk.items.runes.Jah) && Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ }
+ // Cube to Jah rune
+ if (me.checkItem({ name: sdk.locale.items.Enigma }).have
+ && !me.getItem(sdk.items.runes.Jah)) {
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ }
- Config.KeepRunewords.push("[type] == shield # [passivefirepierce] >= 28");
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == monarch && [flag] != ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ }
+
+ Config.Recipes.push([Recipe.Socket.Shield, "monarch", Roll.NonEth]);
+ Config.Runewords.push([Runeword.Phoenix, "monarch"]);
+
+ Config.KeepRunewords.push("[type] == shield # [passivefirepierce] >= 28");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Rhyme.js b/libs/SoloPlay/BuildFiles/Runewords/Rhyme.js
index 10e6340f..d47b1763 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Rhyme.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Rhyme.js
@@ -1,42 +1,42 @@
-(function() {
- const rhyme = [
- "[name] == ShaelRune # # [maxquantity] == 1",
- "[name] == EthRune # # [maxquantity] == 1",
- "[type] == voodooheads && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
- "[type] == voodooheads && [quality] == normal # ([necromancerskills]+[poisonandboneskilltab]+[skillbonespear]+[skillbonespirit]+[skillteeth]+[skillbonewall]+[skillboneprison]+[skillamplifydamage]) >= 1 && [sockets] == 0 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(rhyme);
+(function () {
+ const rhyme = [
+ "[name] == ShaelRune # # [maxquantity] == 1",
+ "[name] == EthRune # # [maxquantity] == 1",
+ "[type] == voodooheads && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
+ "[type] == voodooheads && [quality] == normal # ([necromancerskills]+[poisonandboneskilltab]+[skillbonespear]+[skillbonespirit]+[skillteeth]+[skillbonewall]+[skillboneprison]+[skillamplifydamage]) >= 1 && [sockets] == 0 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(rhyme);
- Config.Runewords.push([Runeword.Rhyme, "preservedhead"]);
- Config.Runewords.push([Runeword.Rhyme, "zombiehead"]);
- Config.Runewords.push([Runeword.Rhyme, "unravellerhead"]);
- Config.Runewords.push([Runeword.Rhyme, "gargoylehead"]);
- Config.Runewords.push([Runeword.Rhyme, "demonhead"]);
- Config.Runewords.push([Runeword.Rhyme, "mummifiedtrophy"]);
- Config.Runewords.push([Runeword.Rhyme, "fetishtrophy"]);
- Config.Runewords.push([Runeword.Rhyme, "sextontrophy"]);
- Config.Runewords.push([Runeword.Rhyme, "cantortrophy"]);
- Config.Runewords.push([Runeword.Rhyme, "hierophanttrophy"]);
- Config.Runewords.push([Runeword.Rhyme, "minionskull"]);
- Config.Runewords.push([Runeword.Rhyme, "hellspawnskull"]);
- Config.Runewords.push([Runeword.Rhyme, "overseerskull"]);
- Config.Runewords.push([Runeword.Rhyme, "succubusskull"]);
- Config.Runewords.push([Runeword.Rhyme, "bloodlordskull"]);
+ Config.Runewords.push([Runeword.Rhyme, "preservedhead"]);
+ Config.Runewords.push([Runeword.Rhyme, "zombiehead"]);
+ Config.Runewords.push([Runeword.Rhyme, "unravellerhead"]);
+ Config.Runewords.push([Runeword.Rhyme, "gargoylehead"]);
+ Config.Runewords.push([Runeword.Rhyme, "demonhead"]);
+ Config.Runewords.push([Runeword.Rhyme, "mummifiedtrophy"]);
+ Config.Runewords.push([Runeword.Rhyme, "fetishtrophy"]);
+ Config.Runewords.push([Runeword.Rhyme, "sextontrophy"]);
+ Config.Runewords.push([Runeword.Rhyme, "cantortrophy"]);
+ Config.Runewords.push([Runeword.Rhyme, "hierophanttrophy"]);
+ Config.Runewords.push([Runeword.Rhyme, "minionskull"]);
+ Config.Runewords.push([Runeword.Rhyme, "hellspawnskull"]);
+ Config.Runewords.push([Runeword.Rhyme, "overseerskull"]);
+ Config.Runewords.push([Runeword.Rhyme, "succubusskull"]);
+ Config.Runewords.push([Runeword.Rhyme, "bloodlordskull"]);
- Config.Recipes.push([Recipe.Socket.Shield, "preservedhead"]);
- Config.Recipes.push([Recipe.Socket.Shield, "zombiehead"]);
- Config.Recipes.push([Recipe.Socket.Shield, "unravellerhead"]);
- Config.Recipes.push([Recipe.Socket.Shield, "gargoylehead"]);
- Config.Recipes.push([Recipe.Socket.Shield, "demonhead"]);
- Config.Recipes.push([Recipe.Socket.Shield, "mummifiedtrophy"]);
- Config.Recipes.push([Recipe.Socket.Shield, "fetishtrophy"]);
- Config.Recipes.push([Recipe.Socket.Shield, "cantortrophy"]);
- Config.Recipes.push([Recipe.Socket.Shield, "hierophanttrophy"]);
- Config.Recipes.push([Recipe.Socket.Shield, "minionskull"]);
- Config.Recipes.push([Recipe.Socket.Shield, "hellspawnskull"]);
- Config.Recipes.push([Recipe.Socket.Shield, "overseerskull"]);
- Config.Recipes.push([Recipe.Socket.Shield, "succubusskull"]);
- Config.Recipes.push([Recipe.Socket.Shield, "bloodlordskull"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "preservedhead"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "zombiehead"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "unravellerhead"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "gargoylehead"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "demonhead"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "mummifiedtrophy"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "fetishtrophy"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "cantortrophy"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "hierophanttrophy"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "minionskull"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "hellspawnskull"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "overseerskull"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "succubusskull"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "bloodlordskull"]);
- Config.KeepRunewords.push("[type] == voodooheads # [fireresist] >= 25 && [itemmagicbonus] >= 25");
+ Config.KeepRunewords.push("[type] == voodooheads # [fireresist] >= 25 && [itemmagicbonus] >= 25");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Sanctuary.js b/libs/SoloPlay/BuildFiles/Runewords/Sanctuary.js
index 6b3534ca..328ca464 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Sanctuary.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Sanctuary.js
@@ -1,29 +1,31 @@
-(function() {
- const Sanctuary = [
- "[name] == KoRune # # [maxquantity] == 2",
- "[name] == MalRune",
- "[name] == hyperion && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(Sanctuary);
+(function () {
+ const Sanctuary = [
+ "[name] == KoRune # # [maxquantity] == 2",
+ "[name] == MalRune",
+ "[name] == hyperion && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(Sanctuary);
- // Cube to Mal rune
- if (!me.getItem(sdk.items.runes.Mal)) {
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- }
+ // Cube to Mal rune
+ if (!me.getItem(sdk.items.runes.Mal)) {
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ }
- // Cube to Ko rune
- if (!me.getItem(sdk.items.runes.Ko)) {
- Config.Recipes.push([Recipe.Rune, "Hel Rune"]);
- Config.Recipes.push([Recipe.Rune, "Io Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
- }
+ // Cube to Ko rune
+ if (!me.getItem(sdk.items.runes.Ko)) {
+ Config.Recipes.push([Recipe.Rune, "Hel Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Io Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
+ }
- if (!Check.haveBase("shield", 3)) {
- NTIP.addLine("[name] == hyperion && [flag] != ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- }
+ if (!me.getOwned({ classid: sdk.items.Hyperion, sockets: 3 }).length) {
+ if (Storage.Stash.UsedSpacePercent() < 75) {
+ NTIP.addLine("[name] == hyperion && [flag] != ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ Config.Recipes.push([Recipe.Socket.Shield, "hyperion", Roll.NonEth]);
+ }
+ }
- Config.Recipes.push([Recipe.Socket.Shield, "hyperion", Roll.NonEth]);
- Config.Runewords.push([Runeword.Sanctuary, "hyperion"]);
+ Config.Runewords.push([Runeword.Sanctuary, "hyperion"]);
- Config.KeepRunewords.push("[type] == shield # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50");
+ Config.KeepRunewords.push("[type] == shield # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Silence.js b/libs/SoloPlay/BuildFiles/Runewords/Silence.js
index 585df05f..baec6fa9 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Silence.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Silence.js
@@ -1,41 +1,52 @@
-(function() {
- const Silence = [
- "[name] == EldRune # # [maxquantity] == 1",
- "[name] == TirRune # # [maxquantity] == 1",
- "[name] == DolRune # # [maxquantity] == 1",
- "[name] == HelRune # # [maxquantity] == 1",
- "[name] == IstRune",
- "[name] == VexRune",
- ];
- NTIP.arrayLooping(Silence);
+(function () {
+ const Silence = [
+ "[name] == EldRune # # [maxquantity] == 1",
+ "[name] == TirRune # # [maxquantity] == 1",
+ "[name] == DolRune # # [maxquantity] == 1",
+ "[name] == HelRune # # [maxquantity] == 1",
+ "[name] == IstRune",
+ "[name] == VexRune",
+ ];
+ NTIP.buildList(Silence);
- // Have Vex before collecting base
- if (me.getItem(sdk.items.runes.Vex)) {
- NTIP.addLine("[name] == phaseblade && [quality] <= superior && [flag] != ethereal # [sockets] == 6 # [maxquantity] == 1");
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.PhaseBlade,
+ mode: sdk.items.mode.inStorage,
+ sockets: 6,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
- // Have Ist+Vex rune but do not have a base yet
- if (me.getItem(sdk.items.runes.Ist) && !Check.haveBase("phaseblade", 6)) {
- NTIP.addLine("[name] == phaseblade && [quality] == normal && [flag] != ethereal # [sockets] == 0 # [maxquantity] == 1");
- Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade"]);
- }
- }
+ // Have Vex before collecting base
+ if (me.getItem(sdk.items.runes.Vex)) {
+ NTIP.addLine("[name] == phaseblade && [quality] <= superior && [flag] != ethereal # [sockets] == 6 # [maxquantity] == 1");
- // Cube to Ist rune
- if (!me.getItem(sdk.items.runes.Ist)) {
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- }
+ // Have Ist+Vex rune but do not have a base yet
+ if (me.getItem(sdk.items.runes.Ist) && !me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == phaseblade && [quality] == normal && [flag] != ethereal # [sockets] == 0 # [maxquantity] == 1");
+ Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade"]);
+ }
+ }
- // Cube to Vex rune
- if (!me.getItem(sdk.items.runes.Vex)) {
- Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
- Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- }
+ // Cube to Ist rune
+ if (!me.getItem(sdk.items.runes.Ist)) {
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ }
- Config.Runewords.push([Runeword.Silence, "phaseblade"]);
- Config.KeepRunewords.push("[type] == sword # [itemallskills] == 2 && [ias] == 20 && [fireresist] == 75");
+ // Cube to Vex rune
+ if (!me.getItem(sdk.items.runes.Vex)) {
+ Config.Recipes.push([Recipe.Rune, "Lem Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Pul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ }
+
+ Config.Runewords.push([Runeword.Silence, "phaseblade"]);
+ Config.KeepRunewords.push("[type] == sword # [itemallskills] == 2 && [ias] == 20 && [fireresist] == 75");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Smoke.js b/libs/SoloPlay/BuildFiles/Runewords/Smoke.js
index f50fbc24..0a6d42e0 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Smoke.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Smoke.js
@@ -1,30 +1,30 @@
-(function() {
- if (!Check.haveItem("armor", "runeword", "Smoke") && !me.hell) {
- // Cube to Lum Rune
- if (!me.getItem(sdk.items.runes.Lum)) {
- Config.Recipes.push([Recipe.Rune, "Io Rune"]);
- }
+(function () {
+ if (!me.checkItem({ name: sdk.locale.items.Smoke }).have && !me.hell) {
+ // Cube to Lum Rune
+ if (!me.getItem(sdk.items.runes.Lum)) {
+ Config.Recipes.push([Recipe.Rune, "Io Rune"]);
+ }
- const smokeRunes = [
- "[name] == NefRune # # [maxquantity] == 1",
- "[name] == LumRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(smokeRunes);
- }
+ const smokeRunes = [
+ "[name] == NefRune # # [maxquantity] == 1",
+ "[name] == LumRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(smokeRunes);
+ }
- // Have Lum rune before looking for base
- if (me.getItem(sdk.items.runes.Lum)) {
- NTIP.addLine("([name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == lightplate || [name] == mageplate || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1");
- }
+ // Have Lum rune before looking for base
+ if (me.getItem(sdk.items.runes.Lum)) {
+ NTIP.addLine("([name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == lightplate || [name] == mageplate || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1");
+ }
- Config.Runewords.push([Runeword.Smoke, "demonhidearmor"]);
- Config.Runewords.push([Runeword.Smoke, "duskshroud"]);
- Config.Runewords.push([Runeword.Smoke, "ghostarmor"]);
- Config.Runewords.push([Runeword.Smoke, "lightplate"]);
- Config.Runewords.push([Runeword.Smoke, "mageplate"]);
- Config.Runewords.push([Runeword.Smoke, "serpentskinarmor"]);
- Config.Runewords.push([Runeword.Smoke, "trellisedarmor"]);
- Config.Runewords.push([Runeword.Smoke, "wyrmhide"]);
+ Config.Runewords.push([Runeword.Smoke, "demonhidearmor"]);
+ Config.Runewords.push([Runeword.Smoke, "duskshroud"]);
+ Config.Runewords.push([Runeword.Smoke, "ghostarmor"]);
+ Config.Runewords.push([Runeword.Smoke, "lightplate"]);
+ Config.Runewords.push([Runeword.Smoke, "mageplate"]);
+ Config.Runewords.push([Runeword.Smoke, "serpentskinarmor"]);
+ Config.Runewords.push([Runeword.Smoke, "trellisedarmor"]);
+ Config.Runewords.push([Runeword.Smoke, "wyrmhide"]);
- Config.KeepRunewords.push("[type] == armor # [fireresist] == 50");
+ Config.KeepRunewords.push("[type] == armor # [fireresist] == 50");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/SpiritShield.js b/libs/SoloPlay/BuildFiles/Runewords/SpiritShield.js
index 09eceecf..9b33d9ab 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/SpiritShield.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/SpiritShield.js
@@ -1,47 +1,58 @@
-(function() {
- const SpiritRunes = [
- "[name] == TalRune # # [maxquantity] == 1",
- "[name] == ThulRune # # [maxquantity] == 1",
- "[name] == OrtRune # # [maxquantity] == 1",
- "[name] == AmnRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(SpiritRunes);
-
- if (me.paladin) {
- NTIP.addLine("([name] == targe || [name] == rondache || [name] == heraldicshield || [name] == aerinshield || [name] == akarantarge || [name] == akaranrondache || [name] == gildedshield ||[name] == protectorshield || [name] == sacredtarge) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [fireresist] > 0 && [sockets] == 4");
- NTIP.addLine("([name] == targe || [name] == rondache || [name] == heraldicshield || [name] == aerinshield || [name] == akarantarge || [name] == akaranrondache || [name] == gildedshield ||[name] == protectorshield || [name] == sacredtarge) && [flag] != ethereal && [quality] == normal # [fireresist] > 0 && [sockets] == 0");
-
- Config.Runewords.push([Runeword.Spirit, "targe"]);
- Config.Runewords.push([Runeword.Spirit, "rondache"]);
- Config.Runewords.push([Runeword.Spirit, "heraldicshield"]);
- Config.Runewords.push([Runeword.Spirit, "aerinshield"]);
- Config.Runewords.push([Runeword.Spirit, "akarantarge"]);
- Config.Runewords.push([Runeword.Spirit, "akaranrondache"]);
- Config.Runewords.push([Runeword.Spirit, "protectorshield"]);
- Config.Runewords.push([Runeword.Spirit, "gildedshield"]);
- Config.Runewords.push([Runeword.Spirit, "sacredtarge"]);
-
- Config.Recipes.push([Recipe.Socket.Shield, "targe"]);
- Config.Recipes.push([Recipe.Socket.Shield, "rondache"]);
- Config.Recipes.push([Recipe.Socket.Shield, "heraldicshield"]);
- Config.Recipes.push([Recipe.Socket.Shield, "aerinshield"]);
- Config.Recipes.push([Recipe.Socket.Shield, "akarantarge"]);
- Config.Recipes.push([Recipe.Socket.Shield, "akaranrondache"]);
- Config.Recipes.push([Recipe.Socket.Shield, "protectorshield"]);
- Config.Recipes.push([Recipe.Socket.Shield, "gildedshield"]);
- Config.Recipes.push([Recipe.Socket.Shield, "sacredtarge"]);
-
- Config.KeepRunewords.push("[type] == auricshields # [fcr] >= 25 && [maxmana] >= 89");
- } else {
- NTIP.addLine("[name] == monarch && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
-
- if (!Check.haveBase("shield", 4)) {
- NTIP.addLine("[name] == monarch && [flag] != ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
- }
-
- Config.Recipes.push([Recipe.Socket.Shield, "monarch", Roll.NonEth]);
- Config.Runewords.push([Runeword.Spirit, "monarch"]);
- }
-
- Config.KeepRunewords.push("[type] == shield # [fcr] >= 25 && [maxmana] >= 89");
+(function () {
+ const SpiritRunes = [
+ "[name] == TalRune # # [maxquantity] == 1",
+ "[name] == ThulRune # # [maxquantity] == 1",
+ "[name] == OrtRune # # [maxquantity] == 1",
+ "[name] == AmnRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(SpiritRunes);
+
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.Monarch,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+
+ if (me.paladin) {
+ NTIP.addLine("([name] == targe || [name] == rondache || [name] == heraldicshield || [name] == aerinshield || [name] == akarantarge || [name] == akaranrondache || [name] == gildedshield ||[name] == protectorshield || [name] == sacredtarge) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [fireresist] > 0 && [sockets] == 4");
+ NTIP.addLine("([name] == targe || [name] == rondache || [name] == heraldicshield || [name] == aerinshield || [name] == akarantarge || [name] == akaranrondache || [name] == gildedshield ||[name] == protectorshield || [name] == sacredtarge) && [flag] != ethereal && [quality] == normal # [fireresist] > 0 && [sockets] == 0");
+
+ Config.Runewords.push([Runeword.Spirit, "targe"]);
+ Config.Runewords.push([Runeword.Spirit, "rondache"]);
+ Config.Runewords.push([Runeword.Spirit, "heraldicshield"]);
+ Config.Runewords.push([Runeword.Spirit, "aerinshield"]);
+ Config.Runewords.push([Runeword.Spirit, "akarantarge"]);
+ Config.Runewords.push([Runeword.Spirit, "akaranrondache"]);
+ Config.Runewords.push([Runeword.Spirit, "protectorshield"]);
+ Config.Runewords.push([Runeword.Spirit, "gildedshield"]);
+ Config.Runewords.push([Runeword.Spirit, "sacredtarge"]);
+
+ Config.Recipes.push([Recipe.Socket.Shield, "targe"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "rondache"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "heraldicshield"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "aerinshield"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "akarantarge"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "akaranrondache"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "protectorshield"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "gildedshield"]);
+ Config.Recipes.push([Recipe.Socket.Shield, "sacredtarge"]);
+
+ Config.KeepRunewords.push("[type] == auricshields # [fcr] >= 25 && [maxmana] >= 89");
+ } else {
+ NTIP.addLine("[name] == monarch && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
+
+ if (!me.getOwned(wanted).length) {
+ NTIP.addLine("[name] == monarch && [flag] != ethereal && [quality] == normal # [sockets] == 0 # [maxquantity] == 1");
+ }
+
+ Config.Recipes.push([Recipe.Socket.Shield, "monarch", Roll.NonEth]);
+ Config.Runewords.push([Runeword.Spirit, "monarch"]);
+ }
+
+ Config.KeepRunewords.push("[type] == shield # [fcr] >= 25 && [maxmana] >= 89");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/SpiritSword.js b/libs/SoloPlay/BuildFiles/Runewords/SpiritSword.js
index 7af8dcf4..9a29b95e 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/SpiritSword.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/SpiritSword.js
@@ -1,44 +1,46 @@
-(function() {
- if (!Check.haveItem("sword", "runeword", "Spirit") && !me.hell) {
- const SpiritSword = [
- "[name] == TalRune # # [maxquantity] == 1",
- "[name] == ThulRune # # [maxquantity] == 1",
- "[name] == OrtRune # # [maxquantity] == 1",
- "[name] == AmnRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(SpiritSword);
+(function () {
+ if (!me.checkItem({ name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Sword }).have
+ && !me.hell) {
+ const SpiritSword = [
+ "[name] == TalRune # # [maxquantity] == 1",
+ "[name] == ThulRune # # [maxquantity] == 1",
+ "[name] == OrtRune # # [maxquantity] == 1",
+ "[name] == AmnRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(SpiritSword);
- // Cube to Amn Rune
- if (!me.getItem(sdk.items.runes.Amn)) {
- Config.Recipes.push([Recipe.Rune, "Ral Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ort Rune"]);
- Config.Recipes.push([Recipe.Rune, "Thul Rune"]);
- }
+ // Cube to Amn Rune
+ if (!me.getItem(sdk.items.runes.Amn)) {
+ Config.Recipes.push([Recipe.Rune, "Ral Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ort Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Thul Rune"]);
+ }
- if (!me.barbarian) {
- NTIP.addLine("([name] == broadsword || [name] == crystalsword) && [flag] != ethereal && [quality] == normal && [level] >= 26 && [level] <= 40 # ([sockets] == 0 || [sockets] == 4) # [maxquantity] == 1");
- } else {
- // Have Thul and Amn before looking for base
- if (me.getItem(sdk.items.runes.Thul) && me.getItem(sdk.items.runes.Amn)) {
- NTIP.addLine("([name] == broadsword || [name] == crystalsword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
- }
- }
-
- Config.Recipes.push([Recipe.Socket.Weapon, "crystalsword", Roll.NonEth]);
- Config.Recipes.push([Recipe.Socket.Weapon, "broadsword", Roll.NonEth]);
- } else {
- if (!me.barbarian) {
- NTIP.addLine("([name] == broadsword || [name] == crystalsword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
- } else {
- // Have Thul and Amn before looking for base
- if (me.getItem(sdk.items.runes.Thul) && me.getItem(sdk.items.runes.Amn)) {
- NTIP.addLine("([name] == broadsword || [name] == crystalsword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
- }
- }
- }
+ if (!me.barbarian) {
+ NTIP.addLine("([name] == broadsword || [name] == crystalsword) && [flag] != ethereal && [quality] == normal && [level] >= 26 && [level] <= 40 # ([sockets] == 0 || [sockets] == 4) # [maxquantity] == 1");
+ } else {
+ // Have Thul and Amn before looking for base
+ if (me.getItem(sdk.items.runes.Thul) && me.getItem(sdk.items.runes.Amn)) {
+ NTIP.addLine("([name] == broadsword || [name] == crystalsword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
+ }
+ }
+
+ Config.Recipes.push([Recipe.Socket.Weapon, "crystalsword", Roll.NonEth]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "broadsword", Roll.NonEth]);
+ } else {
+ if (!me.barbarian) {
+ NTIP.addLine("([name] == broadsword || [name] == crystalsword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
+ } else {
+ !me.getItem(sdk.items.runes.Amn) && Config.Recipes.push([Recipe.Rune, "Thul Rune"]);
+ // Have Thul and Amn before looking for base
+ if (me.getItem(sdk.items.runes.Thul) && me.getItem(sdk.items.runes.Amn)) {
+ NTIP.addLine("([name] == broadsword || [name] == crystalsword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4 # [maxquantity] == 1");
+ }
+ }
+ }
- Config.Runewords.push([Runeword.Spirit, "crystalsword"]);
- Config.Runewords.push([Runeword.Spirit, "broadsword"]);
+ Config.Runewords.push([Runeword.Spirit, "crystalsword"]);
+ Config.Runewords.push([Runeword.Spirit, "broadsword"]);
- Config.KeepRunewords.push("[type] == sword # [fcr] >= 25 && [maxmana] >= 89");
+ Config.KeepRunewords.push("[type] == sword # [fcr] >= 25 && [maxmana] >= 89");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Stealth.js b/libs/SoloPlay/BuildFiles/Runewords/Stealth.js
index bebb520c..54534522 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Stealth.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Stealth.js
@@ -1,28 +1,28 @@
-(function() {
- if (!Check.haveItem("armor", "runeword", "Stealth") && me.normal) {
- const stealthRunes = [
- "[name] == TalRune # # [maxquantity] == 1",
- "[name] == EthRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(stealthRunes);
- }
+(function () {
+ if (!me.checkItem({ name: sdk.locale.items.Stealth }).have && me.normal) {
+ const stealthRunes = [
+ "[name] == TalRune # # [maxquantity] == 1",
+ "[name] == EthRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(stealthRunes);
+ }
- const stealthArmor = [
- "!me.hell && ([name] == studdedleather || [name] == lightplate) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
- "([name] == ghostarmor || [name] == serpentskinarmor || [name] == mageplate) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(stealthArmor);
+ const stealthArmor = [
+ "!me.hell && ([name] == studdedleather || [name] == lightplate) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
+ "([name] == ghostarmor || [name] == serpentskinarmor || [name] == mageplate) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
+ ];
+ NTIP.buildList(stealthArmor);
- if (Item.getEquippedItem(sdk.body.Armor).tier < 200) {
- NTIP.addLine("[name] == breastplate && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1");
- }
+ if (me.equipped.get(sdk.body.Armor).tier < 200) {
+ NTIP.addLine("[name] == breastplate && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1");
+ }
- Config.Runewords.push([Runeword.Stealth, "mageplate"]);
- Config.Runewords.push([Runeword.Stealth, "serpentskinarmor"]);
- Config.Runewords.push([Runeword.Stealth, "ghostarmor"]);
- Config.Runewords.push([Runeword.Stealth, "lightplate"]);
- Config.Runewords.push([Runeword.Stealth, "breastplate"]);
- Config.Runewords.push([Runeword.Stealth, "studdedleather"]);
+ Config.Runewords.push([Runeword.Stealth, "mageplate"]);
+ Config.Runewords.push([Runeword.Stealth, "serpentskinarmor"]);
+ Config.Runewords.push([Runeword.Stealth, "ghostarmor"]);
+ Config.Runewords.push([Runeword.Stealth, "lightplate"]);
+ Config.Runewords.push([Runeword.Stealth, "breastplate"]);
+ Config.Runewords.push([Runeword.Stealth, "studdedleather"]);
- Config.KeepRunewords.push("[type] == armor # [frw] == 25");
+ Config.KeepRunewords.push("[type] == armor # [frw] == 25");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Steel.js b/libs/SoloPlay/BuildFiles/Runewords/Steel.js
index 1009ff1a..b2733b0b 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Steel.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Steel.js
@@ -1,44 +1,48 @@
-(function() {
- const Steel = [
- "[name] == TirRune # # [maxquantity] == 2",
- "[name] == ElRune # # [maxquantity] == 2",
- ];
- NTIP.arrayLooping(Steel);
+(function () {
+ const Steel = [
+ "[name] == TirRune # # [maxquantity] == 2",
+ "[name] == ElRune # # [maxquantity] == 2",
+ ];
+ NTIP.buildList(Steel);
+ const leftArmTier = me.equipped.get(sdk.body.LeftArm).tier;
+ const commonType = "[type] == sword && [flag] != ethereal";
+ const commonQuality = "[quality] >= normal && [quality] <= superior";
+ const commonStat = "[wsm] <= 10 && [strreq] <= 150";
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500 && Item.getEquippedItem(sdk.body.LeftArm).tier > 395) {
- NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 && [class] == elite # [sockets] == 2 # [maxquantity] == 1");
- } else if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500 && Item.getEquippedItem(sdk.body.LeftArm).tier > 278) {
- NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 && [class] > normal # [sockets] == 2 # [maxquantity] == 1");
- } else {
- NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] == superior && [wsm] <= 10 && [strreq] <= 150 # [enhanceddamage] >= 10 && [sockets] == 2 # [maxquantity] == 1");
- }
+ if (leftArmTier < 500 && leftArmTier > 395) {
+ NTIP.addLine(commonType + " && " + commonQuality + " && " + commonStat + " && [class] == elite # [sockets] == 2 # [maxquantity] == 1");
+ } else if (leftArmTier < 500 && leftArmTier > 278) {
+ NTIP.addLine(commonType + " && " + commonQuality + " && " + commonStat + " && [class] > normal # [sockets] == 2 # [maxquantity] == 1");
+ } else {
+ NTIP.addLine(commonType + " && [quality] == superior && " + commonStat + " # [enhanceddamage] >= 10 && [sockets] == 2 # [maxquantity] == 1");
+ }
- Config.Runewords.push([Runeword.Steel, "shortsword"]);
- Config.Runewords.push([Runeword.Steel, "scimitar"]);
- Config.Runewords.push([Runeword.Steel, "sabre"]);
- Config.Runewords.push([Runeword.Steel, "crystalsword"]);
- Config.Runewords.push([Runeword.Steel, "broadsword"]);
- Config.Runewords.push([Runeword.Steel, "longsword"]);
- Config.Runewords.push([Runeword.Steel, "warsword"]);
- Config.Runewords.push([Runeword.Steel, "giantsword"]);
- Config.Runewords.push([Runeword.Steel, "flamberge"]);
- Config.Runewords.push([Runeword.Steel, "gladius"]);
- Config.Runewords.push([Runeword.Steel, "cutlass"]);
- Config.Runewords.push([Runeword.Steel, "shamshir"]);
- Config.Runewords.push([Runeword.Steel, "dimensionalblade"]);
- Config.Runewords.push([Runeword.Steel, "battlesword"]);
- Config.Runewords.push([Runeword.Steel, "runesword"]);
- Config.Runewords.push([Runeword.Steel, "ancientsword"]);
- Config.Runewords.push([Runeword.Steel, "espandon"]);
- Config.Runewords.push([Runeword.Steel, "tusksword"]);
- Config.Runewords.push([Runeword.Steel, "zweihander"]);
- Config.Runewords.push([Runeword.Steel, "falcata"]);
- Config.Runewords.push([Runeword.Steel, "ataghan"]);
- Config.Runewords.push([Runeword.Steel, "elegantblade"]);
- Config.Runewords.push([Runeword.Steel, "phaseblade"]);
- Config.Runewords.push([Runeword.Steel, "conquestsword"]);
- Config.Runewords.push([Runeword.Steel, "crypticsword"]);
- Config.Runewords.push([Runeword.Steel, "mythicalsword"]);
+ Config.Runewords.push([Runeword.Steel, "shortsword"]);
+ Config.Runewords.push([Runeword.Steel, "scimitar"]);
+ Config.Runewords.push([Runeword.Steel, "sabre"]);
+ Config.Runewords.push([Runeword.Steel, "crystalsword"]);
+ Config.Runewords.push([Runeword.Steel, "broadsword"]);
+ Config.Runewords.push([Runeword.Steel, "longsword"]);
+ Config.Runewords.push([Runeword.Steel, "warsword"]);
+ Config.Runewords.push([Runeword.Steel, "giantsword"]);
+ Config.Runewords.push([Runeword.Steel, "flamberge"]);
+ Config.Runewords.push([Runeword.Steel, "gladius"]);
+ Config.Runewords.push([Runeword.Steel, "cutlass"]);
+ Config.Runewords.push([Runeword.Steel, "shamshir"]);
+ Config.Runewords.push([Runeword.Steel, "dimensionalblade"]);
+ Config.Runewords.push([Runeword.Steel, "battlesword"]);
+ Config.Runewords.push([Runeword.Steel, "runesword"]);
+ Config.Runewords.push([Runeword.Steel, "ancientsword"]);
+ Config.Runewords.push([Runeword.Steel, "espandon"]);
+ Config.Runewords.push([Runeword.Steel, "tusksword"]);
+ Config.Runewords.push([Runeword.Steel, "zweihander"]);
+ Config.Runewords.push([Runeword.Steel, "falcata"]);
+ Config.Runewords.push([Runeword.Steel, "ataghan"]);
+ Config.Runewords.push([Runeword.Steel, "elegantblade"]);
+ Config.Runewords.push([Runeword.Steel, "phaseblade"]);
+ Config.Runewords.push([Runeword.Steel, "conquestsword"]);
+ Config.Runewords.push([Runeword.Steel, "crypticsword"]);
+ Config.Runewords.push([Runeword.Steel, "mythicalsword"]);
- Config.KeepRunewords.push("[type] == sword # [enhanceddamage] >= 20 && [ias] >= 25");
+ Config.KeepRunewords.push("[type] == sword # [enhanceddamage] >= 20 && [ias] >= 25");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/Treachery.js b/libs/SoloPlay/BuildFiles/Runewords/Treachery.js
index 8450258e..c71b8eae 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/Treachery.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/Treachery.js
@@ -1,32 +1,32 @@
-(function() {
- const treach = [
- "[name] == ShaelRune # # [maxquantity] == 1",
- "[name] == ThulRune # # [maxquantity] == 1",
- "[name] == LemRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(treach);
+(function () {
+ const treach = [
+ "[name] == ShaelRune # # [maxquantity] == 1",
+ "[name] == ThulRune # # [maxquantity] == 1",
+ "[name] == LemRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(treach);
- // Cube to Lem rune
- if (!me.getItem(sdk.items.runes.Lem)) {
- Config.Recipes.push([Recipe.Rune, "Io Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
- Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
- }
+ // Cube to Lem rune
+ if (!me.getItem(sdk.items.runes.Lem)) {
+ Config.Recipes.push([Recipe.Rune, "Io Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
+ }
- // Have Shael and Lem before looking for base
- if (me.getItem(sdk.items.runes.Lem)) {
- NTIP.addLine("([name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == lightplate || [name] == mageplate || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
- }
+ // Have Shael and Lem before looking for base
+ if (me.getItem(sdk.items.runes.Lem)) {
+ NTIP.addLine("([name] == demonhidearmor || [name] == duskshroud || [name] == ghostarmor || [name] == lightplate || [name] == mageplate || [name] == serpentskinarmor || [name] == trellisedarmor || [name] == wyrmhide) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 3 # [maxquantity] == 1");
+ }
- Config.Runewords.push([Runeword.Treachery, "demonhidearmor"]);
- Config.Runewords.push([Runeword.Treachery, "duskshroud"]);
- Config.Runewords.push([Runeword.Treachery, "ghostarmor"]);
- Config.Runewords.push([Runeword.Treachery, "lightplate"]);
- Config.Runewords.push([Runeword.Treachery, "mageplate"]);
- Config.Runewords.push([Runeword.Treachery, "serpentskinarmor"]);
- Config.Runewords.push([Runeword.Treachery, "trellisedarmor"]);
- Config.Runewords.push([Runeword.Treachery, "wyrmhide"]);
+ Config.Runewords.push([Runeword.Treachery, "demonhidearmor"]);
+ Config.Runewords.push([Runeword.Treachery, "duskshroud"]);
+ Config.Runewords.push([Runeword.Treachery, "ghostarmor"]);
+ Config.Runewords.push([Runeword.Treachery, "lightplate"]);
+ Config.Runewords.push([Runeword.Treachery, "mageplate"]);
+ Config.Runewords.push([Runeword.Treachery, "serpentskinarmor"]);
+ Config.Runewords.push([Runeword.Treachery, "trellisedarmor"]);
+ Config.Runewords.push([Runeword.Treachery, "wyrmhide"]);
- Config.KeepRunewords.push("[type] == armor # [ias] == 45 && [coldresist] == 30");
+ Config.KeepRunewords.push("[type] == armor # [ias] == 45 && [coldresist] == 30");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/VoiceOfReason.js b/libs/SoloPlay/BuildFiles/Runewords/VoiceOfReason.js
index f5bc95db..d1a4bd73 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/VoiceOfReason.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/VoiceOfReason.js
@@ -1,47 +1,47 @@
-(function() {
- const VoiceofReason = [
- "[name] == LemRune",
- "[name] == KoRune",
- "[name] == ElRune # # [maxquantity] == 1",
- "[name] == EldRune # # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(VoiceofReason);
+(function () {
+ const VoiceofReason = [
+ "[name] == LemRune",
+ "[name] == KoRune",
+ "[name] == ElRune # # [maxquantity] == 1",
+ "[name] == EldRune # # [maxquantity] == 1",
+ ];
+ NTIP.buildList(VoiceofReason);
- if (me.barbarian) {
- // Have Lem and Ko runes before looking for normal base
- if (me.getItem(sdk.items.runes.Lem) && me.getItem(sdk.items.runes.Ko)) {
- NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 4");
- NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4");
- } else {
- NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] == superior # [enhanceddamage] >= 5 && [sockets] == 4");
- }
+ if (me.barbarian) {
+ // Have Lem and Ko runes before looking for normal base
+ if (me.getItem(sdk.items.runes.Lem) && me.getItem(sdk.items.runes.Ko)) {
+ NTIP.addLine("[type] == sword && [flag] != ethereal && [quality] >= normal && [quality] <= superior && [wsm] <= 10 && [strreq] <= 150 # [sockets] == 4");
+ NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] >= normal && [quality] <= superior # [sockets] == 4");
+ } else {
+ NTIP.addLine("([name] == legendsword || [name] == highlandblade || [name] == balrogblade || [name] == championsword || [name] == colossussword) && [flag] != ethereal && [quality] == superior # [enhanceddamage] >= 5 && [sockets] == 4");
+ }
- Config.Runewords.push([Runeword.VoiceofReason, "dimensionalblade"]);
- Config.Runewords.push([Runeword.VoiceofReason, "battlesword"]);
- Config.Runewords.push([Runeword.VoiceofReason, "runesword"]);
- Config.Runewords.push([Runeword.VoiceofReason, "conquestsword"]);
- Config.Runewords.push([Runeword.VoiceofReason, "crypticsword"]);
- Config.Runewords.push([Runeword.VoiceofReason, "phaseblade"]);
- Config.Runewords.push([Runeword.VoiceofReason, "tusksword"]);
- Config.Runewords.push([Runeword.VoiceofReason, "zweihander"]);
- Config.Runewords.push([Runeword.VoiceofReason, "legendsword"]);
- Config.Runewords.push([Runeword.VoiceofReason, "highlandblade"]);
- Config.Runewords.push([Runeword.VoiceofReason, "balrogblade"]);
- Config.Runewords.push([Runeword.VoiceofReason, "championsword"]);
- Config.Runewords.push([Runeword.VoiceofReason, "colossussword"]);
- }
+ Config.Runewords.push([Runeword.VoiceofReason, "dimensionalblade"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "battlesword"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "runesword"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "conquestsword"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "crypticsword"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "phaseblade"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "tusksword"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "zweihander"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "legendsword"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "highlandblade"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "balrogblade"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "championsword"]);
+ Config.Runewords.push([Runeword.VoiceofReason, "colossussword"]);
+ }
- if (me.paladin) {
- NTIP.addLine("[name] == phaseblade && [quality] == normal # ([sockets] == 0 || [sockets] == 4) # [maxquantity] == 1");
+ if (me.paladin) {
+ NTIP.addLine("[name] == phaseblade && [quality] == normal # ([sockets] == 0 || [sockets] == 4) # [maxquantity] == 1");
- // Cube to Lem rune
- if (!me.getItem(sdk.items.runes.Lem)) {
- Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
- }
+ // Cube to Lem rune
+ if (!me.getItem(sdk.items.runes.Lem)) {
+ Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
+ }
- Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade", Roll.NonEth]);
- Config.Runewords.push([Runeword.VoiceofReason, "phaseblade"]);
- }
+ Config.Recipes.push([Recipe.Socket.Weapon, "phaseblade", Roll.NonEth]);
+ Config.Runewords.push([Runeword.VoiceofReason, "phaseblade"]);
+ }
- Config.KeepRunewords.push("[type] == sword # [passivecoldpierce] >= 24");
+ Config.KeepRunewords.push("[type] == sword # [passivecoldpierce] >= 24");
})();
diff --git a/libs/SoloPlay/BuildFiles/Runewords/White.js b/libs/SoloPlay/BuildFiles/Runewords/White.js
index 9fa1813a..d705f33e 100644
--- a/libs/SoloPlay/BuildFiles/Runewords/White.js
+++ b/libs/SoloPlay/BuildFiles/Runewords/White.js
@@ -1,35 +1,41 @@
-(function() {
- const white = [
- "[name] == DolRune # # [maxquantity] == 1",
- "[name] == IoRune # # [maxquantity] == 1",
- "[type] == wand && ([name] != wand && [name] != yewwand && [name] != burntwand) && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1",
- "[type] == wand && ([name] != wand && [name] != yewwand && [name] != burntwand) && [quality] == normal # ([necromancerskills]+[poisonandboneskilltab]+[skillbonespear]+[skillbonespirit]+[skillteeth]+[skillbonewall]+[skillboneprison]+[skillamplifydamage]) >= 1 && [sockets] == 0 # [maxquantity] == 1",
- ];
- NTIP.arrayLooping(white);
+(function () {
+ const _wands = ("[type] == wand && ([name] != wand && [name] != yewwand && [name] != burntwand)");
+ const _skills = ("[necromancerskills]+[poisonandboneskilltab]+[skillbonespear]+[skillbonespirit]+[skillteeth]+[skillbonewall]+[skillboneprison]+[skillamplifydamage]");
+ const white = [
+ "[name] == DolRune # # [maxquantity] == 1",
+ "[name] == IoRune # # [maxquantity] == 1",
+ (_wands + " && [quality] >= normal && [quality] <= superior # [sockets] == 2"),
+ (_wands + " && [quality] == normal # (" + _skills + ") >= 1 && [sockets] == 0 # [maxquantity] == 1"),
+ ];
+ NTIP.buildList(white);
- // Cube to Io rune
- if (!me.getItem(sdk.items.runes.Io)) {
- Config.Recipes.push([Recipe.Rune, "Hel Rune"]);
- }
+ // if (me.equipped.get(sdk.body.RightArm).tier < NTIP.MAX_TIER) {
+ // NTIP.addLine((_wands + " && [quality] >= normal && [quality] <= superior # [sockets] == 2 # [maxquantity] == 1"));
+ // }
- Config.Runewords.push([Runeword.White, "bonewand"]);
- Config.Runewords.push([Runeword.White, "grimwand"]);
- Config.Runewords.push([Runeword.White, "petrifiedwand"]);
- Config.Runewords.push([Runeword.White, "tombwand"]);
- Config.Runewords.push([Runeword.White, "gravewand"]);
- Config.Runewords.push([Runeword.White, "polishedwand"]);
- Config.Runewords.push([Runeword.White, "ghostwand"]);
- Config.Runewords.push([Runeword.White, "lichwand"]);
- Config.Runewords.push([Runeword.White, "unearthedwand"]);
+ // Cube to Io rune
+ if (!me.getItem(sdk.items.runes.Io)) {
+ Config.Recipes.push([Recipe.Rune, "Hel Rune"]);
+ }
+
+ Config.Runewords.push([Runeword.White, "bonewand"]);
+ Config.Runewords.push([Runeword.White, "grimwand"]);
+ Config.Runewords.push([Runeword.White, "petrifiedwand"]);
+ Config.Runewords.push([Runeword.White, "tombwand"]);
+ Config.Runewords.push([Runeword.White, "gravewand"]);
+ Config.Runewords.push([Runeword.White, "polishedwand"]);
+ Config.Runewords.push([Runeword.White, "ghostwand"]);
+ Config.Runewords.push([Runeword.White, "lichwand"]);
+ Config.Runewords.push([Runeword.White, "unearthedwand"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "bonewand"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "petrifiedwand"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "tombwand"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "Grave wand"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "polishedwand"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "ghostwand"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "lichwand"]);
- Config.Recipes.push([Recipe.Socket.Weapon, "unearthedwand"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "bonewand"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "petrifiedwand"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "tombwand"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "Grave wand"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "polishedwand"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "ghostwand"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "lichwand"]);
+ Config.Recipes.push([Recipe.Socket.Weapon, "unearthedwand"]);
- Config.KeepRunewords.push("[type] == wand # [fcr] >= 20");
+ Config.KeepRunewords.push("[type] == wand # [fcr] >= 20");
})();
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.FaithbowzonBuild.js b/libs/SoloPlay/BuildFiles/amazon/amazon.FaithbowzonBuild.js
index 586fb61f..86abc663 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.FaithbowzonBuild.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.FaithbowzonBuild.js
@@ -6,140 +6,179 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BowandCrossbow,
- wantedskills: [sdk.skills.Strafe],
- usefulskills: [sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
- precastSkills: [sdk.skills.Valkyrie],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 103], ["dexterity", 152], ["vitality", 150], ["dexterity", "all"]
- ],
- skills: [
- [sdk.skills.Valkyrie, 1],
- [sdk.skills.Strafe, 20],
- [sdk.skills.Pierce, 19],
- [sdk.skills.Penetrate, 3],
- [sdk.skills.CriticalStrike, 19],
- [sdk.skills.Valkyrie, 20],
- [sdk.skills.Dodge, 4],
- [sdk.skills.Avoid, 4],
- [sdk.skills.Evade, 4],
- [sdk.skills.Dodge, 9],
- [sdk.skills.Evade, 9],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - Faith
- "([type] == amazonbow || [type] == bow) && [flag] == runeword # [fanaticismaura] >= 12 && [itemallskills] >= 1 # [tier] == 100000 + tierscore(item)",
- // Weapon - WitchWild String up'd
- "[name] == diamondbow && [quality] == unique # [fireresist] == 40 # [tier] == 90000 + tierscore(item)",
- // Helmet - Vampz Gaze
- "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [manaleech] >= 6 && [lifeleech] >= 6 && [damageresist] >= 15 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 35 # [tier] == 100000 + tierscore(item)",
- // Belt - Nosferatu's Coil
- "[name] == vampirefangbelt && [quality] == unique && [flag] != ethereal # [lifeleech] >= 5 # [tier] == 100000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
- // Gloves - Lava Gout
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [ias] == 20 && [enhanceddefense] >= 150 # [tier] == 3000 + tierscore(item)",
- // Final Amulet - Atma's Scarab
- "[type] == amulet && [quality] == unique # [poisonresist] == 75 # [tier] == 110000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch Final Weapon - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Temporary Weapon - Life Tap charged wand
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 82 # [secondarytier] == 75000 + chargeditemscore(item, 82)",
- // Switch Shield - Any 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 100000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- ],
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BowandCrossbow,
+ wantedskills: [sdk.skills.Strafe],
+ usefulskills: [sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
+ precastSkills: [sdk.skills.Valkyrie],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 103], ["dexterity", 152], ["vitality", 150], ["dexterity", "all"]
+ ],
+ skills: [
+ [sdk.skills.Valkyrie, 1],
+ [sdk.skills.Strafe, 20],
+ [sdk.skills.Pierce, 19],
+ [sdk.skills.Penetrate, 3],
+ [sdk.skills.CriticalStrike, 19],
+ [sdk.skills.Valkyrie, 20],
+ [sdk.skills.Dodge, 4],
+ [sdk.skills.Avoid, 4],
+ [sdk.skills.Evade, 4],
+ [sdk.skills.Dodge, 9],
+ [sdk.skills.Evade, 9],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- SkillerCrossbow: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BowandCrossbow) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerPassive: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PassiveandMagic) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ SkillerCrossbow: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BowandCrossbow) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Strafe, -1, sdk.skills.Strafe, -1, sdk.skills.MagicArrow, -1];
- Config.LowManaSkill = [0, -1];
- Config.HPBuffer = 1;
- Config.MPBuffer = 1;
- }
- },
- },
+ SkillerPassive: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PassiveandMagic) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return (me.checkItem({name: sdk.locale.items.Faith}).have || Check.haveItem("diamondbow", "unique", "Witchwild String"));
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.Strafe, -1, sdk.skills.Strafe, -1, sdk.skills.MagicArrow, -1];
+ Config.LowManaSkill = [0, -1];
+ Config.HPBuffer = 1;
+ Config.MPBuffer = 1;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.checkSkill(sdk.skills.Strafe, sdk.skills.subindex.HardPoints);
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return (
+ me.checkItem({ name: sdk.locale.items.Faith }).have
+ || me.checkItem({ name: sdk.locale.items.WitchwildString, classid: sdk.items.DiamondBow }).have
+ );
+ },
+
+ active: function () {
+ return this.respec() && me.checkSkill(sdk.skills.Strafe, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Final Weapon - Faith
+ "([type] == amazonbow || [type] == bow) && [flag] == runeword # [fanaticismaura] >= 12 && [itemallskills] >= 1 # [tier] == tierscore(item, 100000)",
+ // Weapon - WitchWild String up'd
+ "[name] == diamondbow && [quality] == unique # [fireresist] == 40 # [tier] == tierscore(item, 100000)",
+ // Helmet - Vampz Gaze
+ "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [manaleech] >= 6 && [lifeleech] >= 6 && [damageresist] >= 15 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 35 # [tier] == tierscore(item, 100000)",
+ // Belt - Nosferatu's Coil
+ "[name] == vampirefangbelt && [quality] == unique && [flag] != ethereal # [lifeleech] >= 5 # [tier] == tierscore(item, 100000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
+ // Gloves - Lava Gout
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [ias] == 20 && [enhanceddefense] >= 150 # [tier] == tierscore(item, 3000)",
+ // Final Amulet - Atma's Scarab
+ "[type] == amulet && [quality] == unique # [poisonresist] == 75 # [tier] == 110000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch Final Weapon - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Temporary Weapon - Life Tap charged wand
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 82 # [secondarytier] == 75000 + chargeditemscore(item, 82)",
+ // Switch Shield - Any 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 100000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.FrostmaidenBuild.js b/libs/SoloPlay/BuildFiles/amazon/amazon.FrostmaidenBuild.js
index c9c389f0..4940acc5 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.FrostmaidenBuild.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.FrostmaidenBuild.js
@@ -5,125 +5,176 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BowandCrossbow,
- wantedskills: [sdk.skills.Strafe, sdk.skills.FreezingArrow, sdk.skills.ExplodingArrow],
- usefulskills: [sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce, sdk.skills.IceArrow, sdk.skills.ColdArrow],
- precastSkills: [sdk.skills.Valkyrie],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 156], ["dexterity", 152], ["vitality", 150], ["dexterity", "all"]
- ],
- skills: [
- [sdk.skills.Valkyrie, 1],
- [sdk.skills.Strafe, 1],
- [sdk.skills.Pierce, 1],
- [sdk.skills.FreezingArrow, 20],
- [sdk.skills.IceArrow, 20],
- [sdk.skills.ColdArrow, 20],
- [sdk.skills.Strafe, 20],
- [sdk.skills.Valkyrie, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Brand
- "[name] == grandmatronbow && [flag] == runeword # [enhanceddamage] >= 260 && [itemknockback] == 1 && [itemdeadlystrike] == 20 # [tier] == 100000 + tierscore(item)",
- // Helmet - Dream
- "[type] == helm && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 30 # [tier] == 100000",
- // Belt - Nosferatu's Coil
- "[name] == sharkskinbelt && [quality] == unique && [flag] != ethereal # [dexterity] == 15 # [tier] == 100000 + tierscore(item)",
- // Armor - Chains of Honor
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 # [tier] == 100000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 110000",
- // Rings - Ravenfrost & Carrion Wind
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[name] == ring && [quality] == unique # [poisonresist] == 55 && [lifeleech] >= 6 # [tier] == 110000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Temporary Weapon - Life Tap charged wand
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 82 # [secondarytier] == 75000 + chargeditemscore(item, 82)",
- // Switch Shield - Any 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 100000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BowandCrossbow,
+ wantedskills: [sdk.skills.Strafe, sdk.skills.FreezingArrow, sdk.skills.ExplodingArrow],
+ usefulskills: [
+ sdk.skills.Penetrate,
+ sdk.skills.Valkyrie,
+ sdk.skills.Pierce,
+ sdk.skills.IceArrow,
+ sdk.skills.ColdArrow
+ ],
+ precastSkills: [sdk.skills.Valkyrie],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 156], ["dexterity", 152], ["vitality", 150], ["dexterity", "all"]
+ ],
+ skills: [
+ [sdk.skills.Valkyrie, 1],
+ [sdk.skills.Strafe, 1],
+ [sdk.skills.Pierce, 1],
+ [sdk.skills.FreezingArrow, 20],
+ [sdk.skills.IceArrow, 20],
+ [sdk.skills.ColdArrow, 20],
+ [sdk.skills.Strafe, 20],
+ [sdk.skills.Valkyrie, 20],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- SkillerCrossbow: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BowandCrossbow) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerPassive: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PassiveandMagic) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ SkillerCrossbow: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BowandCrossbow) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.FreezingArrow, -1, sdk.skills.FreezingArrow, -1, sdk.skills.Strafe, -1];
- Config.LowManaSkill = [0, -1];
- }
- },
- },
+ SkillerPassive: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PassiveandMagic) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return me.haveAll([{name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}, {name: sdk.locale.items.Brand}, {name: sdk.locale.items.ChainsofHonor}]);
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.FreezingArrow, -1,
+ sdk.skills.FreezingArrow, -1,
+ sdk.skills.Strafe, -1
+ ];
+ Config.LowManaSkill = [0, -1];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.checkSkill(sdk.skills.FreezingArrow, sdk.skills.subindex.HardPoints);
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return me.haveAll([
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm },
+ { name: sdk.locale.items.Brand },
+ { name: sdk.locale.items.ChainsofHonor }
+ ]);
+ },
+
+ active: function () {
+ return this.respec() && me.checkSkill(sdk.skills.FreezingArrow, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - Brand
+ "[name] == grandmatronbow && [flag] == runeword # [enhanceddamage] >= 260 && [itemknockback] == 1 && [itemdeadlystrike] == 20 # [tier] == tierscore(item, 100000)",
+ // Helmet - Dream
+ "[type] == helm && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 30 # [tier] == 100000",
+ // Belt - Nosferatu's Coil
+ "[name] == sharkskinbelt && [quality] == unique && [flag] != ethereal # [dexterity] == 15 # [tier] == tierscore(item, 100000)",
+ // Armor - Chains of Honor
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 # [tier] == 100000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 110000",
+ // Rings - Ravenfrost & Carrion Wind
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[name] == ring && [quality] == unique # [poisonresist] == 55 && [lifeleech] >= 6 # [tier] == 110000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Temporary Weapon - Life Tap charged wand
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 82 # [secondarytier] == 75000 + chargeditemscore(item, 82)",
+ // Switch Shield - Any 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 100000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.JavazonBuild.js b/libs/SoloPlay/BuildFiles/amazon/amazon.JavazonBuild.js
index cb571924..03038acd 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.JavazonBuild.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.JavazonBuild.js
@@ -5,167 +5,179 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.JavelinandSpear,
- wantedskills: [sdk.skills.ChargedStrike, sdk.skills.LightningStrike],
- usefulskills: [sdk.skills.CriticalStrike, sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
- precastSkills: [sdk.skills.Valkyrie],
- usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 65], ["strength", 75], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 34], ["vitality", 30], ["dexterity", 47],
- ["vitality", 45], ["strength", 47], ["dexterity", 65],
- ["vitality", 65], ["strength", 53], ["dexterity", 118],
- ["vitality", 100], ["strength", 118], ["dexterity", 151],
- ["strength", 156], ["vitality", "all"],
- ],
- classicSkills: [
- [sdk.skills.Valkyrie, 1],
- [sdk.skills.LightningFury, 1],
- [sdk.skills.LightningStrike, 1],
- [sdk.skills.Pierce, 1],
- [sdk.skills.PlagueJavelin, 20],
- [sdk.skills.ChargedStrike, 10],
- [sdk.skills.LightningStrike, 10],
- [sdk.skills.Decoy, 5],
- [sdk.skills.LightningStrike, 17],
- [sdk.skills.ChargedStrike, 15],
- [sdk.skills.LightningStrike, 20, false],
- [sdk.skills.ChargedStrike, 20, false],
- [sdk.skills.PoisonJavelin, 20, false],
- [sdk.skills.Valkyrie, 12, false],
- [sdk.skills.LightningFury, 20, false],
- ],
- expansionSkills: [
- [sdk.skills.Valkyrie, 1],
- [sdk.skills.Pierce, 1],
- [sdk.skills.LightningStrike, 20],
- [sdk.skills.ChargedStrike, 20],
- [sdk.skills.LightningFury, 20],
- [sdk.skills.Decoy, 5, false],
- [sdk.skills.Valkyrie, 17, false],
- [sdk.skills.PowerStrike, 20, false],
- [sdk.skills.Pierce, 5, false],
- ],
- classicTiers: [
- // Helm - Tarnhelm
- "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == 100000 + tierscore(item)",
- // Armor - Twitchthroe
- "[name] == studdedleather && [quality] == unique # [ias] == 20 && [fhr] == 20 # [tier] == 100000",
- // Belt - Death's Guard Sash
- "[name] == sash && [quality] == set # [itemcannotbefrozen] == 1 # [tier] == 100000",
- // Gloves - Death's Hand Leather Gloves
- "[name] == leathergloves && [quality] == set # [poisonresist] >= 50 # [tier] == 100000",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- ],
- expansionTiers: [
- // Weapon - Titan's Revenge
- "[name] == ceremonialjavelin && [quality] == unique # [itemchargedskill] >= 0 # [tier] == 100000 + tierscore(item)",
- // Helmet - Harlequin's Crest
- "[name] == shako && [quality] == unique && [flag] != ethereal # [itemallskills] == 2 # [tier] == 100000 + tierscore(item)",
- // Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Belt - Thundergod's Vigor
- "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
- // Shield - Spirit
- "[type] == shield # [fcr] >= 25 && [maxmana] >= 89 # [tier] == 110000 + tierscore(item)",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 110000",
- // Final Rings - Perfect Raven Frost & Wisp
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == 110000",
- // Rings - Raven Frost & Wisp
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- skills: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 5,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.JavelinandSpear,
+ wantedskills: [sdk.skills.ChargedStrike, sdk.skills.LightningStrike],
+ usefulskills: [sdk.skills.CriticalStrike, sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
+ precastSkills: [sdk.skills.Valkyrie],
+ usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [],
+ skills: [],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 5,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.JavelinandSpear) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
-
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.ChargedStrike, -1, sdk.skills.LightningStrike, -1, -1, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- Config.HPBuffer = me.expansion ? 2 : 4;
- Config.MPBuffer = me.expansion ? 4 : 6;
- }
- },
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return (Attack.checkInfinity() || (myData.merc.gear.includes(sdk.locale.items.Infinity) && !Misc.poll(() => me.getMerc(), 200, 50)));
- }
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.JavelinandSpear) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.ChargedStrike, -1, sdk.skills.LightningStrike, -1, -1, -1];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ Config.HPBuffer = me.expansion ? 2 : 4;
+ Config.MPBuffer = me.expansion ? 4 : 6;
+ }
+ },
+ },
- active: function () {
- return this.respec() && (me.expansion ? me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) > 1 && me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) < 5 : me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) === 20);
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return (Attack.checkInfinity() || (me.data.merc.gear.includes(sdk.locale.items.Infinity) && !Misc.poll(() => me.getMerc(), 200, 50)));
+ }
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.skills = me.classic ? finalBuild.classicSkills : finalBuild.expansionSkills;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
-me.classic && finalBuild.usefulStats.push(sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce, sdk.stats.PiercePois);
+ active: function () {
+ return this.respec() && (me.expansion ? me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) > 1 && me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) < 5 : me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) === 20);
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["dexterity", 65], ["strength", 75], ["vitality", "all"]
+ ]
+ : [
+ ["strength", 34], ["vitality", 30], ["dexterity", 47],
+ ["vitality", 45], ["strength", 47], ["dexterity", 65],
+ ["vitality", 65], ["strength", 53], ["dexterity", 118],
+ ["vitality", 100], ["strength", 118], ["dexterity", 151],
+ ["strength", 156], ["vitality", "all"],
+ ];
+
+ build.skills = me.classic
+ ? [
+ [sdk.skills.Valkyrie, 1],
+ [sdk.skills.LightningFury, 1],
+ [sdk.skills.LightningStrike, 1],
+ [sdk.skills.Pierce, 1],
+ [sdk.skills.PlagueJavelin, 20],
+ [sdk.skills.ChargedStrike, 10],
+ [sdk.skills.LightningStrike, 10],
+ [sdk.skills.Decoy, 5],
+ [sdk.skills.LightningStrike, 17],
+ [sdk.skills.ChargedStrike, 15],
+ [sdk.skills.LightningStrike, 20, false],
+ [sdk.skills.ChargedStrike, 20, false],
+ [sdk.skills.PoisonJavelin, 20, false],
+ [sdk.skills.Valkyrie, 12, false],
+ [sdk.skills.LightningFury, 20, false],
+ ]
+ : [
+ [sdk.skills.Valkyrie, 1],
+ [sdk.skills.Pierce, 1],
+ [sdk.skills.LightningStrike, 20],
+ [sdk.skills.ChargedStrike, 20],
+ [sdk.skills.LightningFury, 20],
+ [sdk.skills.Decoy, 5, false],
+ [sdk.skills.Valkyrie, 17, false],
+ [sdk.skills.PowerStrike, 20, false],
+ [sdk.skills.Pierce, 5, false],
+ ];
+
+ me.classic && build.usefulStats.push(sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce, sdk.stats.PiercePois);
+
+ let finalGear = me.classic
+ ? [
+ // Helm - Tarnhelm
+ "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == tierscore(item, 100000)",
+ // Armor - Twitchthroe
+ "[name] == studdedleather && [quality] == unique # [ias] == 20 && [fhr] == 20 # [tier] == 100000",
+ // Belt - Death's Guard Sash
+ "[name] == sash && [quality] == set # [itemcannotbefrozen] == 1 # [tier] == 100000",
+ // Gloves - Death's Hand Leather Gloves
+ "[name] == leathergloves && [quality] == set # [poisonresist] >= 50 # [tier] == 100000",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ ] : [
+ // Weapon - Titan's Revenge
+ "[name] == ceremonialjavelin && [quality] == unique # [itemchargedskill] >= 0 # [tier] == tierscore(item, 100000)",
+ // Helmet - Harlequin's Crest
+ "[name] == shako && [quality] == unique && [flag] != ethereal # [itemallskills] == 2 # [tier] == tierscore(item, 100000)",
+ // Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Belt - Thundergod's Vigor
+ "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
+ // Shield - Spirit
+ "[type] == shield # [fcr] >= 25 && [maxmana] >= 89 # [tier] == tierscore(item, 110000)",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 110000",
+ // Final Rings - Perfect Raven Frost & Wisp
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == 110000",
+ // Rings - Raven Frost & Wisp
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.LevelingBuild.js b/libs/SoloPlay/BuildFiles/amazon/amazon.LevelingBuild.js
index dd473630..5b5f65d3 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.LevelingBuild.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.LevelingBuild.js
@@ -5,75 +5,82 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: false,
- skillstab: sdk.skills.tabs.JavelinandSpear,
- wantedskills: [sdk.skills.ChargedStrike, sdk.skills.LightningStrike],
- usefulskills: [sdk.skills.CriticalStrike, sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
- usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 65], ["strength", 75], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 34], ["vitality", 30], ["dexterity", 47],
- ["vitality", 45], ["strength", 47], ["dexterity", 65],
- ["vitality", 65], ["strength", 53], ["dexterity", 118],
- ["vitality", 100], ["strength", 118], ["dexterity", 151],
- ["strength", 156], ["vitality", "all"],
- ],
- classicSkills: [
- // Points at respec 71
- [sdk.skills.Valkyrie, 1], // points left 64
- [sdk.skills.Pierce, 1], // points left 61
- [sdk.skills.LightningFury, 1], // points left 57
- [sdk.skills.LightningStrike, 17], // points left 37
- [sdk.skills.PlagueJavelin, 20], // points left 18
- [sdk.skills.ChargedStrike, 15], // points left 4
- [sdk.skills.Decoy, 5], // points left 0
- [sdk.skills.LightningStrike, 20, false],
- [sdk.skills.ChargedStrike, 20, false],
- [sdk.skills.PoisonJavelin, 20, false], // synergy for PlagueJavelin
- [sdk.skills.Valkyrie, 12, false],
- [sdk.skills.LightningFury, 20, false],
- ],
- expansionSkills: [
- // Points at respec 71
- [sdk.skills.Valkyrie, 1], // points left 64
- [sdk.skills.Pierce, 1], // points left 61
- [sdk.skills.LightningFury, 1], // points left 57
- [sdk.skills.LightningStrike, 15], // points left 39
- [sdk.skills.ChargedStrike, 17], // points left 23
- [sdk.skills.PlagueJavelin, 20], // points left 4
- [sdk.skills.Decoy, 5], // points left 0
- [sdk.skills.LightningStrike, 20, false],
- [sdk.skills.ChargedStrike, 20, false],
- [sdk.skills.LightningFury, 20, false],
- [sdk.skills.Valkyrie, 17, false],
- [sdk.skills.PowerStrike, 20, false],
- ],
- stats: undefined,
- skills: undefined,
- active: function () {
- return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) === 20 && !Check.finalBuild().active());
- },
-};
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.JavelinandSpear,
+ wantedskills: [sdk.skills.ChargedStrike, sdk.skills.LightningStrike],
+ usefulskills: [sdk.skills.CriticalStrike, sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
+ usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [],
+ skills: [],
-// Has to be set after its loaded
-build.stats = me.classic ? build.classicStats : build.expansionStats;
-build.skills = me.classic ? build.classicSkills : build.expansionSkills;
-me.classic && build.usefulStats.push(sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce, sdk.stats.PiercePois);
+ active: function () {
+ return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) === 20 && !Check.finalBuild().active());
+ },
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- let mainSkill = Skill.canUse(sdk.skills.LightningStrike) ? sdk.skills.LightningStrike : sdk.skills.ChargedStrike;
- Config.AttackSkill = [-1, sdk.skills.ChargedStrike, 0, mainSkill, 0, -1, -1];
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = me.expansion ? 2 : 4;
- Config.MPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
- SetUp.belt();
-});
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ let mainSkill = Skill.canUse(sdk.skills.LightningStrike) ? sdk.skills.LightningStrike : sdk.skills.ChargedStrike;
+ Config.AttackSkill = [-1, sdk.skills.ChargedStrike, 0, mainSkill, 0, -1, -1];
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = me.expansion ? 2 : 4;
+ Config.MPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
+ SetUp.belt();
+ }
+ }
+ },
+ };
+
+ // Has to be set after its loaded
+ me.classic && build.usefulStats.push(sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce, sdk.stats.PiercePois);
+ build.stats = me.classic
+ ? [
+ ["dexterity", 65], ["strength", 75], ["vitality", "all"]
+ ] : [
+ ["strength", 34], ["vitality", 30], ["dexterity", 47],
+ ["vitality", 45], ["strength", 47], ["dexterity", 65],
+ ["vitality", 65], ["strength", 53], ["dexterity", 118],
+ ["vitality", 100], ["strength", 118], ["dexterity", 151],
+ ["strength", 156], ["vitality", "all"],
+ ];
+
+ build.skills = me.classic
+ ? [
+ // Points at respec 71
+ [sdk.skills.Valkyrie, 1], // points left 64
+ [sdk.skills.Pierce, 1], // points left 61
+ [sdk.skills.LightningFury, 1], // points left 57
+ [sdk.skills.LightningStrike, 17], // points left 37
+ [sdk.skills.PlagueJavelin, 20], // points left 18
+ [sdk.skills.ChargedStrike, 15], // points left 4
+ [sdk.skills.Decoy, 5], // points left 0
+ [sdk.skills.LightningStrike, 20, false],
+ [sdk.skills.ChargedStrike, 20, false],
+ [sdk.skills.PoisonJavelin, 20, false], // synergy for PlagueJavelin
+ [sdk.skills.Valkyrie, 12, false],
+ [sdk.skills.LightningFury, 20, false],
+ ] : [
+ // Points at respec 71
+ [sdk.skills.Valkyrie, 1], // points left 64
+ [sdk.skills.Pierce, 1], // points left 61
+ [sdk.skills.LightningFury, 1], // points left 57
+ [sdk.skills.LightningStrike, 15], // points left 39
+ [sdk.skills.ChargedStrike, 17], // points left 23
+ [sdk.skills.PlagueJavelin, 20], // points left 4
+ [sdk.skills.Decoy, 5], // points left 0
+ [sdk.skills.LightningStrike, 20, false],
+ [sdk.skills.ChargedStrike, 20, false],
+ [sdk.skills.LightningFury, 20, false],
+ [sdk.skills.Valkyrie, 17, false],
+ [sdk.skills.PowerStrike, 20, false],
+ ];
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.StartBuild.js b/libs/SoloPlay/BuildFiles/amazon/amazon.StartBuild.js
index ae134ac1..bcb8ea2d 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.StartBuild.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.StartBuild.js
@@ -5,76 +5,84 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: false,
- skillstab: sdk.skills.tabs.JavelinandSpear,
- wantedskills: [sdk.skills.ChargedStrike, sdk.skills.LightningStrike],
- usefulskills: [sdk.skills.CriticalStrike, sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
- usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 34], ["vitality", 30], ["dexterity", 47],
- ["vitality", 45], ["strength", 47], ["dexterity", 65],
- ["vitality", "all"],
- ],
- skills: [
- [sdk.skills.Jab, 1, false], // charlvl 2
- [sdk.skills.InnerSight, 1, false], // charlvl 3
- [sdk.skills.CriticalStrike, 2, false], // charlvl 4
- [sdk.skills.PowerStrike, 1, false], // charlvl 6
- [sdk.skills.Dodge, 1, false], // charlvl 6
- [sdk.skills.PowerStrike, 5, false], // charlvl 10
- [sdk.skills.SlowMissiles, 1, false], // charlvl 12
- [sdk.skills.Avoid, 1, false], // charlvl 12
- [sdk.skills.PowerStrike, 8, false], // charlvl 15
- [sdk.skills.ChargedStrike, 1, false], // charlvl 18
- [sdk.skills.Penetrate, 1, false], // charlvl 18
- [sdk.skills.ChargedStrike, 5, false], // charlvl 22
- [sdk.skills.Evade, 1, false], // charLvl 24
- [sdk.skills.Decoy, 1, false], // charlvl 24
- [sdk.skills.ChargedStrike, 20, false], // respec at 30
- ],
- active: function () {
- return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.LightningStrike, sdk.skills.subindex.HardPoints);
- },
-};
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.BeltColumn = ["hp", "hp", "hp", "hp"];
- Config.HPBuffer = 8;
- Config.MPBuffer = 4;
- Config.AttackSkill = [-1, 0, 0, 0, 0, 0, 0];
- Config.LowManaSkill = [0, 0];
- SetUp.belt();
-});
-build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.HPBuffer = 8;
- Config.MPBuffer = 4;
- Config.AttackSkill = [-1, sdk.skills.Jab, 0, sdk.skills.Jab, 0, 0, 0];
-});
-build.AutoBuildTemplate[6] = buildAutoBuildTempObj(() => {
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = me.expansion ? 4 : 6;
- Config.MPBuffer = 6;
- Config.AttackSkill = [-1, sdk.skills.PowerStrike, 0, sdk.skills.PowerStrike, 0, -1, -1];
- SetUp.belt();
-});
-build.AutoBuildTemplate[7] = buildAutoBuildTempObj(() => {
- Config.HPBuffer = me.expansion ? 4 : 6;
- Config.MPBuffer = 6;
- Config.AttackSkill = [-1, sdk.skills.PowerStrike, 0, sdk.skills.PowerStrike, 0, -1, -1];
-});
-build.AutoBuildTemplate[18] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.ChargedStrike, 0, sdk.skills.ChargedStrike, 0, -1, -1];
-});
-build.AutoBuildTemplate[50] = buildAutoBuildTempObj(() => {
- let mainSkill = Skill.canUse(sdk.skills.LightningStrike) ? sdk.skills.LightningStrike : sdk.skills.ChargedStrike;
- Config.AttackSkill = [-1, sdk.skills.ChargedStrike, 0, mainSkill, 0, -1, -1];
-});
+(function (module, require) {
+ module.exports = (function () {
+ const build = {
+ AutoBuildTemplate: {},
+ caster: false,
+ skillstab: sdk.skills.tabs.JavelinandSpear,
+ wantedskills: [sdk.skills.ChargedStrike, sdk.skills.LightningStrike],
+ usefulskills: [sdk.skills.CriticalStrike, sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
+ usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 34], ["vitality", 30], ["dexterity", 47],
+ ["vitality", 45], ["strength", 47], ["dexterity", 65],
+ ["vitality", "all"],
+ ],
+ skills: [
+ [sdk.skills.Jab, 1, false], // charlvl 2
+ [sdk.skills.InnerSight, 1, false], // charlvl 3
+ [sdk.skills.CriticalStrike, 2, false], // charlvl 4
+ [sdk.skills.PowerStrike, 1, false], // charlvl 6
+ [sdk.skills.Dodge, 1, false], // charlvl 6
+ [sdk.skills.PowerStrike, 5, false], // charlvl 10
+ [sdk.skills.SlowMissiles, 1, false], // charlvl 12
+ [sdk.skills.Avoid, 1, false], // charlvl 12
+ [sdk.skills.PowerStrike, 8, false], // charlvl 15
+ [sdk.skills.ChargedStrike, 1, false], // charlvl 18
+ [sdk.skills.Penetrate, 1, false], // charlvl 18
+ [sdk.skills.ChargedStrike, 5, false], // charlvl 22
+ [sdk.skills.Evade, 1, false], // charLvl 24
+ [sdk.skills.Decoy, 1, false], // charlvl 24
+ [sdk.skills.ChargedStrike, 20, false], // respec at 30
+ ],
+
+ active: function () {
+ return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.LightningStrike, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ const { buildAutoBuildTempObj } = require("../../Utils/General");
+
+ build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.BeltColumn = ["hp", "hp", "hp", "hp"];
+ Config.HPBuffer = 8;
+ Config.MPBuffer = 4;
+ Config.AttackSkill = [-1, 0, 0, 0, 0, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.HPBuffer = 8;
+ Config.MPBuffer = 4;
+ Config.AttackSkill = [-1, sdk.skills.Jab, 0, sdk.skills.Jab, 0, 0, 0];
+ });
+ build.AutoBuildTemplate[6] = buildAutoBuildTempObj(() => {
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = me.expansion ? 4 : 6;
+ Config.MPBuffer = 6;
+ Config.AttackSkill = [-1, sdk.skills.PowerStrike, 0, sdk.skills.PowerStrike, 0, -1, -1];
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[7] = buildAutoBuildTempObj(() => {
+ Config.HPBuffer = me.expansion ? 4 : 6;
+ Config.MPBuffer = 6;
+ Config.AttackSkill = [-1, sdk.skills.PowerStrike, 0, sdk.skills.PowerStrike, 0, -1, -1];
+ });
+ build.AutoBuildTemplate[18] = buildAutoBuildTempObj(() => {
+ Config.AttackSkill = [-1, sdk.skills.ChargedStrike, 0, sdk.skills.ChargedStrike, 0, -1, -1];
+ });
+ build.AutoBuildTemplate[50] = buildAutoBuildTempObj(() => {
+ let mainSkill = Skill.canUse(sdk.skills.LightningStrike) ? sdk.skills.LightningStrike : sdk.skills.ChargedStrike;
+ Config.AttackSkill = [-1, sdk.skills.ChargedStrike, 0, mainSkill, 0, -1, -1];
+ });
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.SteppingBuild.js b/libs/SoloPlay/BuildFiles/amazon/amazon.SteppingBuild.js
index 8156d25b..60e6f1f4 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.SteppingBuild.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.SteppingBuild.js
@@ -5,55 +5,61 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: false,
- skillstab: sdk.skills.tabs.JavelinandSpear,
- wantedskills: [sdk.skills.ChargedStrike, sdk.skills.LightningStrike],
- usefulskills: [sdk.skills.CriticalStrike, sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
- usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 65], ["strength", 75], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 34], ["vitality", 30], ["dexterity", 47],
- ["vitality", 45], ["strength", 47], ["dexterity", 65],
- ["vitality", 65], ["strength", 53], ["dexterity", 118],
- ["vitality", 100], ["strength", 118], ["dexterity", 151],
- ["strength", 156], ["vitality", "all"],
- ],
- skills: [
- // points at respec 33
- [sdk.skills.LightningStrike, 1], // points left 27
- [sdk.skills.Valkyrie, 1], // points left 20
- [sdk.skills.Penetrate, 1], // points left 18
- [sdk.skills.ChargedStrike, 13], // points left 6
- [sdk.skills.PowerStrike, 5], // points left 2
- [sdk.skills.LightningFury, 1], // points left 0
- [sdk.skills.LightningStrike, 20, false], // charlvl ?
- [sdk.skills.ChargedStrike, 20, false], // charlvl 52
- [sdk.skills.LightningFury, 20, false], // respec at 64
- ],
- stats: undefined,
- active: function () {
- return me.charlvl > CharInfo.respecOne && me.charlvl < CharInfo.respecTwo && me.checkSkill(sdk.skills.LightningStrike, sdk.skills.subindex.HardPoints) && me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) <= 5;
- },
-};
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.JavelinandSpear,
+ wantedskills: [sdk.skills.ChargedStrike, sdk.skills.LightningStrike],
+ usefulskills: [sdk.skills.CriticalStrike, sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
+ usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [],
+ skills: [
+ // points at respec 33
+ [sdk.skills.LightningStrike, 1], // points left 27
+ [sdk.skills.Valkyrie, 1], // points left 20
+ [sdk.skills.Penetrate, 1], // points left 18
+ [sdk.skills.ChargedStrike, 13], // points left 6
+ [sdk.skills.PowerStrike, 5], // points left 2
+ [sdk.skills.LightningFury, 1], // points left 0
+ [sdk.skills.LightningStrike, 20, false], // charlvl ?
+ [sdk.skills.ChargedStrike, 20, false], // charlvl 52
+ [sdk.skills.LightningFury, 20, false], // respec at 64
+ ],
-// Has to be set after its loaded
-build.stats = me.classic ? build.classicStats : build.expansionStats;
-me.classic && build.usefulStats.push(sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce, sdk.stats.PiercePois);
-
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- let mainSkill = Skill.canUse(sdk.skills.LightningStrike) ? sdk.skills.LightningStrike : sdk.skills.ChargedStrike;
- Config.AttackSkill = [-1, sdk.skills.ChargedStrike, 0, mainSkill, 0, -1, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = me.expansion ? 2 : 4;
- Config.MPBuffer = 6;
- SetUp.belt();
-});
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ let mainSkill = Skill.canUse(sdk.skills.LightningStrike) ? sdk.skills.LightningStrike : sdk.skills.ChargedStrike;
+ Config.AttackSkill = [-1, sdk.skills.ChargedStrike, 0, mainSkill, 0, -1, -1];
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = me.expansion ? 2 : 4;
+ Config.MPBuffer = 6;
+ SetUp.belt();
+ }
+ }
+ },
+ active: function () {
+ return me.charlvl > CharInfo.respecOne && me.charlvl < CharInfo.respecTwo && me.checkSkill(sdk.skills.LightningStrike, sdk.skills.subindex.HardPoints) && me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.HardPoints) <= 5;
+ },
+ };
+
+ // Has to be set after its loaded
+ me.classic && build.usefulStats.push(sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce, sdk.stats.PiercePois);
+ build.stats = me.classic
+ ? [
+ ["dexterity", 65], ["strength", 75], ["vitality", "all"]
+ ] : [
+ ["strength", 34], ["vitality", 30], ["dexterity", 47],
+ ["vitality", 45], ["strength", 47], ["dexterity", 65],
+ ["vitality", 65], ["strength", 53], ["dexterity", 118],
+ ["vitality", 100], ["strength", 118], ["dexterity", 151],
+ ["strength", 156], ["vitality", "all"],
+ ];
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.WfzonBuild.js b/libs/SoloPlay/BuildFiles/amazon/amazon.WfzonBuild.js
index 92a9220e..a9212fe3 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.WfzonBuild.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.WfzonBuild.js
@@ -6,136 +6,175 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BowandCrossbow,
- wantedskills: [sdk.skills.Strafe],
- usefulskills: [sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
- precastSkills: [sdk.skills.Valkyrie],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 134], ["dexterity", 167], ["vitality", 150], ["dexterity", "all"]
- ],
- skills: [
- [sdk.skills.Valkyrie, 1],
- [sdk.skills.Strafe, 20],
- [sdk.skills.Pierce, 13],
- [sdk.skills.Penetrate, 20],
- [sdk.skills.CriticalStrike, 13],
- [sdk.skills.Valkyrie, 16],
- [sdk.skills.Dodge, 9],
- [sdk.skills.Avoid, 4],
- [sdk.skills.Evade, 8],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - Windforce
- "[name] == hydrabow && [quality] == unique # [manaleech] >= 6 # [tier] == 100000 + tierscore(item)",
- // Weapon - WitchWild String up'd
- "[name] == diamondbow && [quality] == unique # [fireresist] == 40 # [tier] == 50000 + tierscore(item)",
- // Helmet - Vampz Gaze
- "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [manaleech] >= 6 && [lifeleech] >= 6 && [damageresist] >= 15 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 30 # [tier] == 100000",
- // Belt - Nosferatu's Coil
- "[name] == vampirefangbelt && [quality] == unique && [flag] != ethereal # [lifeleech] >= 5 # [tier] == 100000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
- // Gloves - Lava Gout
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [ias] == 20 && [enhanceddefense] >= 150 # [tier] == 3000 + tierscore(item)",
- // Final Amulet - Atma's Scarab
- "[type] == amulet && [quality] == unique # [poisonresist] == 75 # [tier] == 110000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Perfect Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000", // Switch Final Weapon - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Temporary Weapon - Life Tap charged wand
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 82 # [secondarytier] == 75000 + chargeditemscore(item, 82)",
- // Switch Final Shield - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Switch Temporary Shield - Any 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 100000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BowandCrossbow,
+ wantedskills: [sdk.skills.Strafe],
+ usefulskills: [sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
+ precastSkills: [sdk.skills.Valkyrie],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 134], ["dexterity", 167], ["vitality", 150], ["dexterity", "all"]
+ ],
+ skills: [
+ [sdk.skills.Valkyrie, 1],
+ [sdk.skills.Strafe, 20],
+ [sdk.skills.Pierce, 13],
+ [sdk.skills.Penetrate, 20],
+ [sdk.skills.CriticalStrike, 13],
+ [sdk.skills.Valkyrie, 16],
+ [sdk.skills.Dodge, 9],
+ [sdk.skills.Avoid, 4],
+ [sdk.skills.Evade, 8],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- SkillerCrossbow: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BowandCrossbow) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerPassive: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PassiveandMagic) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ SkillerCrossbow: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BowandCrossbow) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Strafe, -1, sdk.skills.Strafe, -1, sdk.skills.MagicArrow, -1];
- Config.LowManaSkill = [0, -1];
- }
- },
- },
+ SkillerPassive: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PassiveandMagic) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return (Check.haveItem("hydrabow", "unique", "Windforce") || Check.haveItem("diamondbow", "unique", "Witchwild String"));
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.Strafe, -1, sdk.skills.Strafe, -1, sdk.skills.MagicArrow, -1];
+ Config.LowManaSkill = [0, -1];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.checkSkill(sdk.skills.Strafe, sdk.skills.subindex.HardPoints);
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return (
+ me.checkItem({ name: sdk.locale.items.Windforce, classid: sdk.items.HydraBow }).have
+ || me.checkItem({ name: sdk.locale.items.WitchwildString, classid: sdk.items.DiamondBow }).have
+ );
+ },
+
+ active: function () {
+ return this.respec() && me.checkSkill(sdk.skills.Strafe, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Final Weapon - Windforce
+ "[name] == hydrabow && [quality] == unique # [manaleech] >= 6 # [tier] == tierscore(item, 100000)",
+ // Weapon - WitchWild String up'd
+ "[name] == diamondbow && [quality] == unique # [fireresist] == 40 # [tier] == tierscore(item, 50000)",
+ // Helmet - Vampz Gaze
+ "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [manaleech] >= 6 && [lifeleech] >= 6 && [damageresist] >= 15 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 30 # [tier] == 100000",
+ // Belt - Nosferatu's Coil
+ "[name] == vampirefangbelt && [quality] == unique && [flag] != ethereal # [lifeleech] >= 5 # [tier] == tierscore(item, 100000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
+ // Gloves - Lava Gout
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [ias] == 20 && [enhanceddefense] >= 150 # [tier] == tierscore(item, 3000)",
+ // Final Amulet - Atma's Scarab
+ "[type] == amulet && [quality] == unique # [poisonresist] == 75 # [tier] == 110000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Perfect Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000", // Switch Final Weapon - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Temporary Weapon - Life Tap charged wand
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 82 # [secondarytier] == 75000 + chargeditemscore(item, 82)",
+ // Switch Final Shield - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
+ // Switch Temporary Shield - Any 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 100000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.WitchyzonBuild.js b/libs/SoloPlay/BuildFiles/amazon/amazon.WitchyzonBuild.js
index 1242746c..72d08cd1 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.WitchyzonBuild.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.WitchyzonBuild.js
@@ -6,131 +6,170 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BowandCrossbow,
- wantedskills: [sdk.skills.Strafe],
- usefulskills: [sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
- precastSkills: [sdk.skills.Valkyrie],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 90], ["dexterity", 132], ["vitality", 150], ["dexterity", "all"]
- ],
- skills: [
- [sdk.skills.Strafe, 1],
- [sdk.skills.Valkyrie, 1],
- [sdk.skills.Pierce, 1],
- [sdk.skills.Strafe, 20],
- [sdk.skills.Pierce, 10],
- [sdk.skills.Penetrate, 20],
- [sdk.skills.Valkyrie, 20],
- [sdk.skills.Dodge, 12],
- [sdk.skills.Avoid, 7],
- [sdk.skills.Evade, 12],
- [sdk.skills.Decoy, 2],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - WitchWild String up'd
- "[name] == diamondbow && [quality] == unique # [fireresist] == 40 # [tier] == 100000 + tierscore(item)",
- // Helmet - Vampz Gaze
- "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [manaleech] >= 6 && [lifeleech] >= 6 && [damageresist] >= 15 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 35 # [tier] == 100000 + tierscore(item)",
- // Belt - Nosferatu's Coil
- "[name] == vampirefangbelt && [quality] == unique && [flag] != ethereal # [lifeleech] >= 5 # [tier] == 100000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
- // Amulet - Cat's Eye
- "[type] == amulet && [quality] == unique # [dexterity] == 25 # [tier] == 110000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch Final Weapon - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Temporary Weapon - Life Tap charged wand
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 82 # [secondarytier] == 75000 + chargeditemscore(item, 82)",
- // Switch Shield - Any 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 100000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BowandCrossbow,
+ wantedskills: [sdk.skills.Strafe],
+ usefulskills: [sdk.skills.Penetrate, sdk.skills.Valkyrie, sdk.skills.Pierce],
+ precastSkills: [sdk.skills.Valkyrie],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 90], ["dexterity", 132], ["vitality", 150], ["dexterity", "all"]
+ ],
+ skills: [
+ [sdk.skills.Strafe, 1],
+ [sdk.skills.Valkyrie, 1],
+ [sdk.skills.Pierce, 1],
+ [sdk.skills.Strafe, 20],
+ [sdk.skills.Pierce, 10],
+ [sdk.skills.Penetrate, 20],
+ [sdk.skills.Valkyrie, 20],
+ [sdk.skills.Dodge, 12],
+ [sdk.skills.Avoid, 7],
+ [sdk.skills.Evade, 12],
+ [sdk.skills.Decoy, 2],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- SkillerCrossbow: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BowandCrossbow) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerPassive: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PassiveandMagic) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ SkillerCrossbow: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BowandCrossbow) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Strafe, -1, sdk.skills.Strafe, -1, sdk.skills.MagicArrow, -1];
- Config.LowManaSkill = [0, -1];
- }
- },
- },
+ SkillerPassive: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PassiveandMagic) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return Check.haveItem("diamondbow", "unique", "Witchwild String") && Check.haveItem("vampirefangbelt", "unique", "Nosferatu's Coil");
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.Strafe, -1, sdk.skills.Strafe, -1, sdk.skills.MagicArrow, -1];
+ Config.LowManaSkill = [0, -1];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.checkSkill(sdk.skills.Strafe, sdk.skills.subindex.HardPoints);
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return (
+ me.checkItem({ name: sdk.locale.items.WitchwildString, classid: sdk.items.DiamondBow }).have
+ && me.checkItem({ name: sdk.locale.items.NosferatusCoil }).have
+ );
+ },
+
+ active: function () {
+ return this.respec() && me.checkSkill(sdk.skills.Strafe, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - WitchWild String up'd
+ "[name] == diamondbow && [quality] == unique # [fireresist] == 40 # [tier] == tierscore(item, 100000)",
+ // Helmet - Vampz Gaze
+ "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [manaleech] >= 6 && [lifeleech] >= 6 && [damageresist] >= 15 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 35 # [tier] == tierscore(item, 100000)",
+ // Belt - Nosferatu's Coil
+ "[name] == vampirefangbelt && [quality] == unique && [flag] != ethereal # [lifeleech] >= 5 # [tier] == tierscore(item, 100000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
+ // Amulet - Cat's Eye
+ "[type] == amulet && [quality] == unique # [dexterity] == 25 # [tier] == 110000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch Final Weapon - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Temporary Weapon - Life Tap charged wand
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 82 # [secondarytier] == 75000 + chargeditemscore(item, 82)",
+ // Switch Shield - Any 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 100000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/amazon/amazon.js b/libs/SoloPlay/BuildFiles/amazon/amazon.js
index 141aeda7..267fc591 100644
--- a/libs/SoloPlay/BuildFiles/amazon/amazon.js
+++ b/libs/SoloPlay/BuildFiles/amazon/amazon.js
@@ -6,40 +6,40 @@
*/
const CharInfo = {
- respecOne: me.expansion ? 30 : 30,
- respecTwo: me.expansion ? 64 : 64,
- levelCap: (function() {
- const currentDiff = sdk.difficulty.nameOf(me.diff);
- const softcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 70 : 70,
- "Hell": 100,
- };
- const hardcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 65 : 65,
- "Hell": 100,
- };
+ respecOne: me.expansion ? 30 : 30,
+ respecTwo: me.expansion ? 64 : 64,
+ levelCap: (function() {
+ const currentDiff = sdk.difficulty.nameOf(me.diff);
+ const softcoreMode = {
+ "Normal": me.expansion ? 33 : 33,
+ "Nightmare": me.expansion ? 70 : 70,
+ "Hell": 100,
+ };
+ const hardcoreMode = {
+ "Normal": me.expansion ? 36 : 33,
+ "Nightmare": me.expansion ? 71 : 65,
+ "Hell": 100,
+ };
- return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
- })(),
+ return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
+ })(),
- getActiveBuild: function () {
- const nSkills = me.getStat(sdk.stats.NewSkills);
- const currLevel = me.charlvl;
- const justRepeced = (nSkills >= currLevel);
+ getActiveBuild: function () {
+ const nSkills = me.getStat(sdk.stats.NewSkills);
+ const currLevel = me.charlvl;
+ const justRepeced = (nSkills >= currLevel);
- switch (true) {
- case currLevel < this.respecOne && !me.checkSkill(sdk.skills.LightningStrike, sdk.skills.subindex.HardPoints):
- return "Start";
- case currLevel >= this.respecOne && currLevel < this.respecTwo && justRepeced:
- case (currLevel >= this.respecOne && currLevel < this.respecTwo && me.checkSkill(sdk.skills.LightningStrike, sdk.skills.subindex.HardPoints)):
- return "Stepping";
- case Check.finalBuild().respec() && justRepeced:
- case Check.finalBuild().active():
- return SetUp.finalBuild;
- default:
- return "Leveling";
- }
- },
+ switch (true) {
+ case currLevel < this.respecOne && !me.checkSkill(sdk.skills.LightningStrike, sdk.skills.subindex.HardPoints):
+ return "Start";
+ case currLevel >= this.respecOne && currLevel < this.respecTwo && justRepeced:
+ case (currLevel >= this.respecOne && currLevel < this.respecTwo && me.checkSkill(sdk.skills.LightningStrike, sdk.skills.subindex.HardPoints)):
+ return "Stepping";
+ case Check.finalBuild().respec() && justRepeced:
+ case Check.finalBuild().active():
+ return SetUp.finalBuild;
+ default:
+ return "Leveling";
+ }
+ },
};
diff --git a/libs/SoloPlay/BuildFiles/assassin/assassin.LevelingBuild.js b/libs/SoloPlay/BuildFiles/assassin/assassin.LevelingBuild.js
index 87fa79aa..a9a52d0c 100644
--- a/libs/SoloPlay/BuildFiles/assassin/assassin.LevelingBuild.js
+++ b/libs/SoloPlay/BuildFiles/assassin/assassin.LevelingBuild.js
@@ -5,74 +5,82 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: sdk.skills.tabs.Traps,
- wantedskills: [sdk.skills.FireBlast, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.ShadowMaster],
- usefulskills: [sdk.skills.ChargedBoltSentry, sdk.skills.BladeShield, sdk.skills.Fade],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 47], ["dexterity", 46], ["vitality", 166],
- ["strength", 61], ["vitality", 241], ["strength", 79],
- ["dexterity", 79], ["strength", 156], ["vitality", "all"]
- ],
- skills: [
- // Skills points at respec 33
- [sdk.skills.Fade, 1], // points left 30
- [sdk.skills.ShadowMaster, 1], // points left 25
- [sdk.skills.MindBlast, 1], // points left 20
- [sdk.skills.DeathSentry, 1], // points left 19
- [sdk.skills.LightningSentry, 7], // points left 13
- [sdk.skills.FireBlast, 6], // points left 8
- [sdk.skills.ShockWeb, 8], // points left 1
- [sdk.skills.WakeofFire, 1], // points left 0
- [sdk.skills.LightningSentry, 20, false],
- [sdk.skills.DeathSentry, 10],
- [sdk.skills.ShockWeb, 9],
- [sdk.skills.FireBlast, 8],
- [sdk.skills.DeathSentry, 12],
- [sdk.skills.ShockWeb, 11],
- [sdk.skills.FireBlast, 11],
- [sdk.skills.DeathSentry, 13],
- [sdk.skills.ShockWeb, 13],
- [sdk.skills.FireBlast, 12],
- [sdk.skills.DeathSentry, 14],
- [sdk.skills.ShockWeb, 15],
- [sdk.skills.FireBlast, 14],
- [sdk.skills.DeathSentry, 15],
- [sdk.skills.ShockWeb, 16],
- [sdk.skills.FireBlast, 15],
- [sdk.skills.DeathSentry, 16],
- [sdk.skills.ShockWeb, 18],
- [sdk.skills.FireBlast, 16],
- [sdk.skills.DeathSentry, 17],
- [sdk.skills.ShockWeb, 20],
- [sdk.skills.FireBlast, 18],
- [sdk.skills.DeathSentry, 20],
- [sdk.skills.ShockWeb, 20],
- [sdk.skills.FireBlast, 20],
- [sdk.skills.ChargedBoltSentry, 20],
- ],
- active: function () {
- return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.getSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints) === 20 && !Check.finalBuild().active());
- },
-};
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Traps,
+ wantedskills: [sdk.skills.FireBlast, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.ShadowMaster],
+ usefulskills: [sdk.skills.ChargedBoltSentry, sdk.skills.BladeShield, sdk.skills.Fade],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 47], ["dexterity", 46], ["vitality", 166],
+ ["strength", 61], ["vitality", 241], ["strength", 79],
+ ["dexterity", 79], ["strength", 156], ["vitality", "all"]
+ ],
+ skills: [
+ // Skills points at respec 33
+ [sdk.skills.Fade, 1], // points left 30
+ [sdk.skills.ShadowMaster, 1], // points left 25
+ [sdk.skills.MindBlast, 1], // points left 20
+ [sdk.skills.DeathSentry, 1], // points left 19
+ [sdk.skills.LightningSentry, 7], // points left 13
+ [sdk.skills.FireBlast, 6], // points left 8
+ [sdk.skills.ShockWeb, 8], // points left 1
+ [sdk.skills.WakeofFire, 1], // points left 0
+ [sdk.skills.LightningSentry, 20, false],
+ [sdk.skills.DeathSentry, 10],
+ [sdk.skills.ShockWeb, 9],
+ [sdk.skills.FireBlast, 8],
+ [sdk.skills.DeathSentry, 12],
+ [sdk.skills.ShockWeb, 11],
+ [sdk.skills.FireBlast, 11],
+ [sdk.skills.DeathSentry, 13],
+ [sdk.skills.ShockWeb, 13],
+ [sdk.skills.FireBlast, 12],
+ [sdk.skills.DeathSentry, 14],
+ [sdk.skills.ShockWeb, 15],
+ [sdk.skills.FireBlast, 14],
+ [sdk.skills.DeathSentry, 15],
+ [sdk.skills.ShockWeb, 16],
+ [sdk.skills.FireBlast, 15],
+ [sdk.skills.DeathSentry, 16],
+ [sdk.skills.ShockWeb, 18],
+ [sdk.skills.FireBlast, 16],
+ [sdk.skills.DeathSentry, 17],
+ [sdk.skills.ShockWeb, 20],
+ [sdk.skills.FireBlast, 18],
+ [sdk.skills.DeathSentry, 20],
+ [sdk.skills.ShockWeb, 20],
+ [sdk.skills.FireBlast, 20],
+ [sdk.skills.ChargedBoltSentry, 20],
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.ShockWeb, sdk.skills.FireBlast, sdk.skills.ShockWeb, sdk.skills.FireBlast, -1, -1];
- Config.LowManaSkill = [-1, -1];
- Config.UseTraps = true;
- Config.UseFade = true;
- Config.Traps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry];
- Config.BossTraps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry];
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = 2;
- Config.MPBuffer = me.charlvl < 80 ? 6 : 2;
- Config.DodgeHP = 75;
- SetUp.belt();
-});
+ active: function () {
+ return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.getSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints) === 20 && !Check.finalBuild().active());
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.ShockWeb, sdk.skills.FireBlast, sdk.skills.ShockWeb, sdk.skills.FireBlast, -1, -1];
+ Config.LowManaSkill = [-1, -1];
+ Config.UseTraps = true;
+ Config.UseFade = true;
+ Config.Traps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry];
+ Config.BossTraps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry];
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = 2;
+ Config.MPBuffer = me.charlvl < 80 ? 6 : 2;
+ Config.DodgeHP = 75;
+ SetUp.belt();
+ }
+ }
+ },
+ };
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/assassin/assassin.StartBuild.js b/libs/SoloPlay/BuildFiles/assassin/assassin.StartBuild.js
index 43c1a325..6566cb0b 100644
--- a/libs/SoloPlay/BuildFiles/assassin/assassin.StartBuild.js
+++ b/libs/SoloPlay/BuildFiles/assassin/assassin.StartBuild.js
@@ -5,60 +5,67 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: sdk.skills.tabs.Traps,
- wantedskills: [sdk.skills.FireBlast, sdk.skills.WakeofFire],
- usefulskills: [sdk.skills.CloakofShadows, sdk.skills.ShadowWarrior],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["vitality", 35], ["energy", 35], ["strength", 33], ["dexterity", 33],
- ["vitality", 50], ["strength", 46], ["dexterity", 46],
- ["vitality", 70], ["strength", 50], ["dexterity", 50],
- ["energy", 50], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.FireBlast, 4, false], // level 4
- [sdk.skills.ClawMastery, 1], // level 5 (den)
- [sdk.skills.PsychicHammer, 1], // level 6
- [sdk.skills.BurstofSpeed, 5], // level 11
- [sdk.skills.WakeofFire, 1, false], // level 12
- [sdk.skills.CloakofShadows, 1, true], // level 13
- [sdk.skills.WakeofFire, 10, false], // level 24
- [sdk.skills.FireBlast, 6, false], // level 26
- [sdk.skills.WakeofFire, 20, false], // level 36
- [sdk.skills.FireBlast, 10], // level 42
- ],
- active: function () {
- return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints);
- },
-};
+(function (module, require) {
+ module.exports = (function () {
+ const build = {
+ AutoBuildTemplate: {},
+ caster: true,
+ skillstab: sdk.skills.tabs.Traps,
+ wantedskills: [sdk.skills.FireBlast, sdk.skills.WakeofFire],
+ usefulskills: [sdk.skills.CloakofShadows, sdk.skills.ShadowWarrior],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["vitality", 35], ["energy", 35], ["strength", 33], ["dexterity", 33],
+ ["vitality", 50], ["strength", 46], ["dexterity", 46],
+ ["vitality", 70], ["strength", 50], ["dexterity", 50],
+ ["energy", 50], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.FireBlast, 4, false], // level 4
+ [sdk.skills.ClawMastery, 1], // level 5 (den)
+ [sdk.skills.PsychicHammer, 1], // level 6
+ [sdk.skills.BurstofSpeed, 5], // level 11
+ [sdk.skills.WakeofFire, 1, false], // level 12
+ [sdk.skills.CloakofShadows, 1, true], // level 13
+ [sdk.skills.WakeofFire, 10, false], // level 24
+ [sdk.skills.FireBlast, 6, false], // level 26
+ [sdk.skills.WakeofFire, 20, false], // level 36
+ [sdk.skills.FireBlast, 10], // level 42
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.BeltColumn = ["hp", "hp", "hp", "hp"];
- Config.HPBuffer = 4;
- Config.MPBuffer = 2;
- Config.AttackSkill = [-1, 0, 0, 0, 0, 0, 0];
- Config.LowManaSkill = [0, 0];
- SetUp.belt();
-});
-build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = 2;
- Config.MPBuffer = 6;
- Config.AttackSkill = [-1, sdk.skills.FireBlast, -1, sdk.skills.FireBlast, -1, (me.checkSkill(sdk.skills.PsychicHammer, sdk.skills.subindex.SoftPoints) ? sdk.skills.PsychicHammer : 0), 0];
- Config.UseBoS = true;
- SetUp.belt();
-});
-build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.FireBlast, -1, sdk.skills.FireBlast, -1, (me.checkSkill(sdk.skills.PsychicHammer, sdk.skills.subindex.SoftPoints) ? sdk.skills.PsychicHammer : 0), 0];
- Config.UseTraps = true;
- Config.Traps = [sdk.skills.WakeofFire, sdk.skills.WakeofFire, sdk.skills.WakeofFire, -1, -1]; // Skill IDs for traps to be cast on all mosters except act bosses.
- Config.BossTraps = [sdk.skills.WakeofFire, sdk.skills.WakeofFire, sdk.skills.WakeofFire, sdk.skills.WakeofFire, sdk.skills.WakeofFire]; // Skill IDs for traps to be cast on act bosses.
- SetUp.belt();
-});
+ active: function () {
+ return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ const { buildAutoBuildTempObj } = require("../../Utils/General");
+
+ build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.BeltColumn = ["hp", "hp", "hp", "hp"];
+ Config.HPBuffer = 4;
+ Config.MPBuffer = 2;
+ Config.AttackSkill = [-1, 0, 0, 0, 0, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = 2;
+ Config.MPBuffer = 6;
+ Config.AttackSkill = [-1, sdk.skills.FireBlast, -1, sdk.skills.FireBlast, -1, (me.checkSkill(sdk.skills.PsychicHammer, sdk.skills.subindex.SoftPoints) ? sdk.skills.PsychicHammer : 0), 0];
+ Config.UseBoS = true;
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
+ Config.AttackSkill = [-1, sdk.skills.FireBlast, -1, sdk.skills.FireBlast, -1, (me.checkSkill(sdk.skills.PsychicHammer, sdk.skills.subindex.SoftPoints) ? sdk.skills.PsychicHammer : 0), 0];
+ Config.UseTraps = true;
+ Config.Traps = [sdk.skills.WakeofFire, sdk.skills.WakeofFire, sdk.skills.WakeofFire, -1, -1]; // Skill IDs for traps to be cast on all mosters except act bosses.
+ Config.BossTraps = [sdk.skills.WakeofFire, sdk.skills.WakeofFire, sdk.skills.WakeofFire, sdk.skills.WakeofFire, sdk.skills.WakeofFire]; // Skill IDs for traps to be cast on act bosses.
+ SetUp.belt();
+ });
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/assassin/assassin.TrapsinBuild.js b/libs/SoloPlay/BuildFiles/assassin/assassin.TrapsinBuild.js
index 90c67b4c..ecccaa8a 100644
--- a/libs/SoloPlay/BuildFiles/assassin/assassin.TrapsinBuild.js
+++ b/libs/SoloPlay/BuildFiles/assassin/assassin.TrapsinBuild.js
@@ -5,141 +5,155 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Traps,
- wantedskills: [sdk.skills.FireBlast, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.ShadowMaster],
- usefulskills: [sdk.skills.ChargedBoltSentry, sdk.skills.BladeShield, sdk.skills.Fade],
- precastSkills: [sdk.skills.Fade, sdk.skills.ShadowMaster],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 156], ["dexterity", 79], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.MindBlast, 1],
- [sdk.skills.ShadowMaster, 1],
- [sdk.skills.Fade, 1],
- [sdk.skills.LightningSentry, 20],
- [sdk.skills.ShockWeb, 15],
- [sdk.skills.FireBlast, 14],
- [sdk.skills.DeathSentry, 15], // lvl 74 w/o quest skills pts
- [sdk.skills.ShockWeb, 16],
- [sdk.skills.FireBlast, 15],
- [sdk.skills.DeathSentry, 16],
- [sdk.skills.ShockWeb, 18],
- [sdk.skills.FireBlast, 16],
- [sdk.skills.DeathSentry, 17],
- [sdk.skills.ShockWeb, 20],
- [sdk.skills.FireBlast, 18],
- [sdk.skills.DeathSentry, 20],
- [sdk.skills.ShockWeb, 20],
- [sdk.skills.FireBlast, 20],
- [sdk.skills.ChargedBoltSentry, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - Silence
- "[type] == sword && [flag] == runeword # [itemallskills] == 2 && [ias] == 20 && [fireresist] == 75 # [tier] == 200000",
- // Temporary Weapon - HotO
- "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
- // Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Boots - Waterwalks
- "[name] == sharkskinboots && [quality] == unique && [flag] != ethereal # [maxhp] >= 65 # [tier] == 100000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Shield - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [tier] == 110000 + tierscore(item)",
- // Gloves - Lava Gout
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [ias] == 20 # [tier] == 100000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Final Rings - SoJ & Perfect Raven Frost
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [dexterity] == 20 # [tier] == 110000",
- // Rings - Raven Frost
- "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 100000",
- // Switch Final Weapon - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Final Shield - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Switch Temporary Shield - Any 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 50000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Traps,
+ wantedskills: [sdk.skills.FireBlast, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.ShadowMaster],
+ usefulskills: [sdk.skills.ChargedBoltSentry, sdk.skills.BladeShield, sdk.skills.Fade],
+ precastSkills: [sdk.skills.Fade, sdk.skills.ShadowMaster],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 156], ["dexterity", 79], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.MindBlast, 1],
+ [sdk.skills.ShadowMaster, 1],
+ [sdk.skills.Fade, 1],
+ [sdk.skills.LightningSentry, 20],
+ [sdk.skills.ShockWeb, 15],
+ [sdk.skills.FireBlast, 14],
+ [sdk.skills.DeathSentry, 15], // lvl 74 w/o quest skills pts
+ [sdk.skills.ShockWeb, 16],
+ [sdk.skills.FireBlast, 15],
+ [sdk.skills.DeathSentry, 16],
+ [sdk.skills.ShockWeb, 18],
+ [sdk.skills.FireBlast, 16],
+ [sdk.skills.DeathSentry, 17],
+ [sdk.skills.ShockWeb, 20],
+ [sdk.skills.FireBlast, 18],
+ [sdk.skills.DeathSentry, 20],
+ [sdk.skills.ShockWeb, 20],
+ [sdk.skills.FireBlast, 20],
+ [sdk.skills.ChargedBoltSentry, 20],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- LifeMana: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.MaxHp) === 20 && check.getStat(sdk.stats.MaxMana) === 17);
- }
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Traps) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ LifeMana: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.MaxHp) === 20 && check.getStat(sdk.stats.MaxMana) === 17);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.UseTraps = true;
- Config.AttackSkill = [-1, sdk.skills.ShockWeb, sdk.skills.FireBlast, sdk.skills.ShockWeb, sdk.skills.FireBlast, -1, -1];
- Config.Traps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry];
- Config.BossTraps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry];
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Traps) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- return (Attack.checkInfinity() || (myData.merc.gear.includes(sdk.locale.items.Infinity) && !Misc.poll(() => me.getMerc(), 200, 50))) && Check.haveItem("armor", "runeword", "Enigma");
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.UseTraps = true;
+ Config.AttackSkill = [-1, sdk.skills.ShockWeb, sdk.skills.FireBlast, sdk.skills.ShockWeb, sdk.skills.FireBlast, -1, -1];
+ Config.Traps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry];
+ Config.BossTraps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ return (Attack.checkInfinity() || (me.data.merc.gear.includes(sdk.locale.items.Infinity) && !Misc.poll(() => me.getMerc(), 200, 50))) && me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have;
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Final Weapon - Silence
+ "[type] == sword && [flag] == runeword # [itemallskills] == 2 && [ias] == 20 && [fireresist] == 75 # [tier] == 200000",
+ // Temporary Weapon - HotO
+ "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
+ // Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Boots - Waterwalks
+ "[name] == sharkskinboots && [quality] == unique && [flag] != ethereal # [maxhp] >= 65 # [tier] == tierscore(item, 100000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Shield - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [tier] == tierscore(item, 110000)",
+ // Gloves - Lava Gout
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [ias] == 20 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Final Rings - SoJ & Perfect Raven Frost
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [dexterity] == 20 # [tier] == 110000",
+ // Rings - Raven Frost
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 100000",
+ // Switch Final Weapon - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Final Shield - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
+ // Switch Temporary Shield - Any 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 50000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/assassin/assassin.WhirlsinBuild.js b/libs/SoloPlay/BuildFiles/assassin/assassin.WhirlsinBuild.js
index c674a2ee..eb19cef8 100644
--- a/libs/SoloPlay/BuildFiles/assassin/assassin.WhirlsinBuild.js
+++ b/libs/SoloPlay/BuildFiles/assassin/assassin.WhirlsinBuild.js
@@ -5,132 +5,146 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.ShadowDisciplines,
- wantedskills: [sdk.skills.FireBlast, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.ShadowMaster],
- usefulskills: [sdk.skills.ChargedBoltSentry, sdk.skills.BladeShield, sdk.skills.Fade],
- precastSkills: [sdk.skills.Fade, sdk.skills.ShadowMaster],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 130], ["dexterity", 99], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Fade, 1],
- [sdk.skills.Venom, 1],
- [sdk.skills.MindBlast, 1],
- [sdk.skills.BladeShield, 1],
- [sdk.skills.ClawMastery, 20],
- [sdk.skills.DeathSentry, 1],
- [sdk.skills.ShadowMaster, 20, false],
- [sdk.skills.Venom, 20, false], // lvl 77 w/o quest skill pts
- [sdk.skills.Fade, 20, false],
- [sdk.skills.BladeShield, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - Chaos Claw & Fury
- "[type] == assassinclaw && [flag] == runeword # [plusskillwhirlwind] == 1 # [tier] == 100000",
- "[type] == assassinclaw && [flag] == runeword # [itemallskills] == 2 && [ias] == 40 && [itemdeadlystrike] == 33 # [tier] == 200000",
- // Helmet - GFace
- "[name] == wingedhelm && [quality] == set && [flag] != ethereal # [fhr] >= 30 # [tier] == 100000 + tierscore(item)",
- // Belt - Verdungos
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] == 15 # [tier] == 110000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [tier] == 100000",
- // Gloves - Trang-Ouls
- "[name] == heavybracers && [quality] == set && [flag] != ethereal # [fcr] == 20 # [tier] == 100000", //
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch Final Weapon - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Final Shield - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Switch Temporary Shield - Any 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 50000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.ShadowDisciplines,
+ wantedskills: [sdk.skills.FireBlast, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.ShadowMaster],
+ usefulskills: [sdk.skills.ChargedBoltSentry, sdk.skills.BladeShield, sdk.skills.Fade],
+ precastSkills: [sdk.skills.Fade, sdk.skills.ShadowMaster],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 130], ["dexterity", 99], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Fade, 1],
+ [sdk.skills.Venom, 1],
+ [sdk.skills.MindBlast, 1],
+ [sdk.skills.BladeShield, 1],
+ [sdk.skills.ClawMastery, 20],
+ [sdk.skills.DeathSentry, 1],
+ [sdk.skills.ShadowMaster, 20, false],
+ [sdk.skills.Venom, 20, false], // lvl 77 w/o quest skill pts
+ [sdk.skills.Fade, 20, false],
+ [sdk.skills.BladeShield, 20],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- LifeMana: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.MaxHp) === 20 && check.getStat(sdk.stats.MaxMana) === 17);
- }
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- Skiller: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShadowDisciplines) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ LifeMana: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.MaxHp) === 20 && check.getStat(sdk.stats.MaxMana) === 17);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.Dodge = false;
- Config.UseVenom = true;
- Config.UseTraps = true;
- Config.AttackSkill = [-1, sdk.skills.Whirlwind, -1, sdk.skills.Whirlwind, -1, -1, -1];
- Config.Traps = [sdk.skills.DeathSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry];
- Config.BossTraps = [-1, -1, -1, -1, -1];
- }
- },
- },
+ Skiller: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShadowDisciplines) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- return me.haveAll([{name: sdk.locale.items.Chaos}, {name: sdk.locale.items.Fury}]);
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.Dodge = false;
+ Config.UseVenom = true;
+ Config.UseTraps = true;
+ Config.AttackSkill = [-1, sdk.skills.Whirlwind, -1, sdk.skills.Whirlwind, -1, -1, -1];
+ Config.Traps = [sdk.skills.DeathSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry];
+ Config.BossTraps = [-1, -1, -1, -1, -1];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.ClawMastery, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ return me.haveAll([{ name: sdk.locale.items.Chaos }, { name: sdk.locale.items.Fury }]);
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.ClawMastery, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Final Weapon - Chaos Claw & Fury
+ "[type] == assassinclaw && [flag] == runeword # [plusskillwhirlwind] == 1 # [tier] == 100000",
+ "[type] == assassinclaw && [flag] == runeword # [itemallskills] == 2 && [ias] == 40 && [itemdeadlystrike] == 33 # [tier] == 200000",
+ // Helmet - GFace
+ "[name] == wingedhelm && [quality] == set && [flag] != ethereal # [fhr] >= 30 # [tier] == tierscore(item, 100000)",
+ // Belt - Verdungos
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] == 15 # [tier] == tierscore(item, 110000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [tier] == 100000",
+ // Gloves - Trang-Ouls
+ "[name] == heavybracers && [quality] == set && [flag] != ethereal # [fcr] == 20 # [tier] == 100000", //
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch Final Weapon - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Final Shield - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
+ // Switch Temporary Shield - Any 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 50000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/assassin/assassin.js b/libs/SoloPlay/BuildFiles/assassin/assassin.js
index a0b4ced2..3ba768d2 100644
--- a/libs/SoloPlay/BuildFiles/assassin/assassin.js
+++ b/libs/SoloPlay/BuildFiles/assassin/assassin.js
@@ -6,37 +6,37 @@
*/
const CharInfo = {
- respecOne: 32,
- respecTwo: 0,
- levelCap: (function() {
- const currentDiff = sdk.difficulty.nameOf(me.diff);
- const softcoreMode = {
- "Normal": 33,
- "Nightmare": 65,
- "Hell": 100,
- };
- const hardcoreMode = {
- "Normal": 33,
- "Nightmare": 65,
- "Hell": 100,
- };
+ respecOne: 32,
+ respecTwo: 0,
+ levelCap: (function () {
+ const currentDiff = sdk.difficulty.nameOf(me.diff);
+ const softcoreMode = {
+ "Normal": 33,
+ "Nightmare": 65,
+ "Hell": 100,
+ };
+ const hardcoreMode = {
+ "Normal": 36,
+ "Nightmare": 65,
+ "Hell": 100,
+ };
- return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
- })(),
+ return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
+ })(),
- getActiveBuild: function () {
- const nSkills = me.getStat(sdk.stats.NewSkills);
- const currLevel = me.charlvl;
- const justRepeced = (nSkills >= currLevel);
+ getActiveBuild: function () {
+ const nSkills = me.getStat(sdk.stats.NewSkills);
+ const currLevel = me.charlvl;
+ const justRepeced = (nSkills >= currLevel);
- switch (true) {
- case currLevel < this.respecOne && !me.checkSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints):
- return "Start";
- case Check.finalBuild().respec() && justRepeced:
- case Check.finalBuild().active():
- return SetUp.finalBuild;
- default:
- return "Leveling";
- }
- },
+ switch (true) {
+ case currLevel < this.respecOne && !me.checkSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints):
+ return "Start";
+ case Check.finalBuild().respec() && justRepeced:
+ case Check.finalBuild().active():
+ return SetUp.finalBuild;
+ default:
+ return "Leveling";
+ }
+ },
};
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.FrenzyBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.FrenzyBuild.js
index 6b777691..55992edf 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.FrenzyBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.FrenzyBuild.js
@@ -5,133 +5,172 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BarbCombat,
- wantedskills: [sdk.skills.BattleOrders, sdk.skills.Frenzy, sdk.skills.DoubleSwing],
- usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed],
- precastSkills: [sdk.skills.BattleOrders, sdk.skills.WarCry],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 103], ["dexterity", 79], ["vitality", 90],
- ["dexterity", 136], ["strength", 150], ["vitality", "all"],
- ],
- skills: [
- [sdk.skills.DoubleSwing, 9, true],
- [sdk.skills.SwordMastery, 6, false],
- [sdk.skills.BattleCommand, 1, true],
- [sdk.skills.WarCry, 1, true],
- [sdk.skills.NaturalResistance, 5, true],
- [sdk.skills.Berserk, 5, true],
- [sdk.skills.Frenzy, 20, false],
- [sdk.skills.BattleOrders, 20, false], // lvl 77 w/o quest skill pts
- [sdk.skills.SwordMastery, 20, false],
- [sdk.skills.DoubleSwing, 20, false],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - Grief & BoTD
- "[name] == phaseblade && [flag] == runeword # [ias] >= 30 # [tier] == 100000",
- "[name] == colossusblade && [flag] == runeword # [ias] >= 60 && [enhanceddamage] >= 350 # [tier] == 100000",
- // Helmet - Arreat's Face
- "[name] == slayerguard && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 # [tier] == 100000 + tierscore(item)",
- // Belt - Dungo's
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] >= 10 && [vitality] >= 30 # [tier] == 100000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 100000 + tierscore(item)",
- // Armor - Fortitude
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 30 # [tier] == 100000",
- // Gloves - Laying of Hands
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] == 20 # [tier] == 100000",
- // Amulet - Atma's
- "[type] == amulet && [quality] == unique # [poisonresist] == 75 # [tier] == 100000 + tierscore(item)",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - BO Sticks
- "([type] == club || [type] == sword || [type] == knife || [type] == throwingknife || [type] == mace) && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BarbCombat,
+ wantedskills: [sdk.skills.BattleOrders, sdk.skills.Frenzy, sdk.skills.DoubleSwing],
+ usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed],
+ precastSkills: [sdk.skills.BattleOrders, sdk.skills.WarCry],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 103], ["dexterity", 79], ["vitality", 90],
+ ["dexterity", 136], ["strength", 150], ["vitality", "all"],
+ ],
+ skills: [
+ [sdk.skills.DoubleSwing, 9, true],
+ [sdk.skills.SwordMastery, 6, false],
+ [sdk.skills.BattleCommand, 1, true],
+ [sdk.skills.WarCry, 1, true],
+ [sdk.skills.NaturalResistance, 5, true],
+ [sdk.skills.Berserk, 5, true],
+ [sdk.skills.Frenzy, 20, false],
+ [sdk.skills.BattleOrders, 20, false], // lvl 77 w/o quest skill pts
+ [sdk.skills.SwordMastery, 20, false],
+ [sdk.skills.DoubleSwing, 20, false],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- SkillerCombat: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BarbCombat) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerMasteries: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Masteries) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ SkillerCombat: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BarbCombat) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [sdk.skills.WarCry, sdk.skills.Frenzy, -1, sdk.skills.Frenzy, -1];
- Config.LowManaSkill = me.getSkill(sdk.skills.DoubleSwing, sdk.skills.subindex.SoftPoints) >= 9 ? [sdk.skills.DoubleSwing, 0] : [0, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- Config.MPBuffer = 2;
- Config.HPBuffer = 2;
- }
- },
- },
+ SkillerMasteries: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Masteries) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.haveAll([{name: sdk.locale.items.Grief}, {name: sdk.locale.items.BreathoftheDying}]);
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [sdk.skills.WarCry, sdk.skills.Frenzy, -1, sdk.skills.Frenzy, -1];
+ Config.LowManaSkill = me.getSkill(sdk.skills.DoubleSwing, sdk.skills.subindex.SoftPoints) >= 9
+ ? [sdk.skills.DoubleSwing, 0]
+ : [0, -1];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ Config.MPBuffer = 2;
+ Config.HPBuffer = 2;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Frenzy, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.haveAll([{ name: sdk.locale.items.Grief }, { name: sdk.locale.items.BreathoftheDying }]);
+ }
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Frenzy, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Final Weapon - Grief & BoTD
+ "[name] == phaseblade && [flag] == runeword # [ias] >= 30 # [tier] == 100000",
+ "[name] == colossusblade && [flag] == runeword # [ias] >= 60 && [enhanceddamage] >= 350 # [tier] == 100000",
+ // Helmet - Arreat's Face
+ "[name] == slayerguard && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 # [tier] == tierscore(item, 100000)",
+ // Belt - Dungo's
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] >= 10 && [vitality] >= 30 # [tier] == tierscore(item, 100000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 100000)",
+ // Armor - Fortitude
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 30 # [tier] == 100000",
+ // Gloves - Laying of Hands
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] == 20 # [tier] == 100000",
+ // Amulet - Atma's
+ "[type] == amulet && [quality] == unique # [poisonresist] == 75 # [tier] == tierscore(item, 100000)",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - BO Sticks
+ "[type] >= 1 && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.ImmortalwhirlBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.ImmortalwhirlBuild.js
index 65331d9a..8309b4b8 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.ImmortalwhirlBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.ImmortalwhirlBuild.js
@@ -5,126 +5,157 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BarbCombat,
- wantedskills: [sdk.skills.Bash, sdk.skills.Whirlwind],
- usefulskills: [sdk.skills.Howl, sdk.skills.Shout],
- precastSkills: [sdk.skills.BattleOrders, sdk.skills.WarCry], // Battle orders, War Cry
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 232], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.MaceMastery, 20],
- [sdk.skills.Whirlwind, 20],
- [sdk.skills.Shout, 20],
- [sdk.skills.BattleCry, 1],
- [sdk.skills.BattleCommand, 1],
- [sdk.skills.NaturalResistance, 1],
- [sdk.skills.IncreasedSpeed, 1],
- [sdk.skills.BattleOrders, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - IK Maul
- "[name] == ogremaul && [quality] == set # [enhanceddamage] >= 200 && [ias] >= 40 # [tier] == 110000",
- // Helmet - IK Helm
- "[name] == avengerguard && [quality] == set && [flag] != ethereal # [warcriesskilltab] == 2 # [tier] == 110000",
- // Belt - IK Belt
- "[name] == warbelt && [quality] == set && [flag] != ethereal # [strength] >= 25 && [fireresist] >= 28 # [tier] == 110000",
- // Boots - IK Boots
- "[name] == warboots && [quality] == set && [flag] != ethereal # [frw] >= 40 && [tohit] >= 110 # [tier] == 110000",
- // Armor - IK Armor
- "[name] == sacredarmor && [quality] == set && [flag] != ethereal # [barbcombatskilltab] == 2 # [tier] == 110000",
- // Gloves - IK Gauntlets
- "[name] == wargauntlets && [quality] == set && [flag] != ethereal # [strength] >= 20 && [dexterity] >= 20 # [tier] == 110000",
- // Amulet - Metalgrid
- "[type] == amulet && [quality] == unique # [defense] >= 300 # [tier] == 110000 + tierscore(item)",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - BO sticks
- "([type] == club || [type] == sword || [type] == knife || [type] == throwingknife || [type] == mace) && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BarbCombat,
+ wantedskills: [sdk.skills.Bash, sdk.skills.Whirlwind],
+ usefulskills: [sdk.skills.Howl, sdk.skills.Shout],
+ precastSkills: [sdk.skills.BattleOrders, sdk.skills.WarCry], // Battle orders, War Cry
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 232], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.MaceMastery, 20],
+ [sdk.skills.Whirlwind, 20],
+ [sdk.skills.Shout, 20],
+ [sdk.skills.BattleCry, 1],
+ [sdk.skills.BattleCommand, 1],
+ [sdk.skills.NaturalResistance, 1],
+ [sdk.skills.IncreasedSpeed, 1],
+ [sdk.skills.BattleOrders, 20],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Masteries) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [sdk.skills.BattleCry, sdk.skills.Whirlwind, -1, sdk.skills.Whirlwind, -1];
- Config.LowManaSkill = [0, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- Config.MPBuffer = 2;
- Config.HPBuffer = 2;
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Masteries) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return me.haveAll([
- { name: sdk.locale.items.ImmortalKingsMaul, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.ImmortalKingsBoots, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.ImmortalKingsGloves, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.ImmortalKingsBelt, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.ImmortalKingsArmor, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.ImmortalKingsHelmet, quality: sdk.items.quality.Set },
- ]);
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [sdk.skills.BattleCry, sdk.skills.Whirlwind, -1, sdk.skills.Whirlwind, -1];
+ Config.LowManaSkill = [0, -1];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ Config.MPBuffer = 2;
+ Config.HPBuffer = 2;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.MaceMastery, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return me.haveAll([
+ { name: sdk.locale.items.ImmortalKingsMaul, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.ImmortalKingsBoots, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.ImmortalKingsGloves, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.ImmortalKingsBelt, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.ImmortalKingsArmor, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.ImmortalKingsHelmet, quality: sdk.items.quality.Set },
+ ]);
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.MaceMastery, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - IK Maul
+ "[name] == ogremaul && [quality] == set # [enhanceddamage] >= 200 && [ias] >= 40 # [tier] == 110000",
+ // Helmet - IK Helm
+ "[name] == avengerguard && [quality] == set && [flag] != ethereal # [warcriesskilltab] == 2 # [tier] == 110000",
+ // Belt - IK Belt
+ "[name] == warbelt && [quality] == set && [flag] != ethereal # [strength] >= 25 && [fireresist] >= 28 # [tier] == 110000",
+ // Boots - IK Boots
+ "[name] == warboots && [quality] == set && [flag] != ethereal # [frw] >= 40 && [tohit] >= 110 # [tier] == 110000",
+ // Armor - IK Armor
+ "[name] == sacredarmor && [quality] == set && [flag] != ethereal # [barbcombatskilltab] == 2 # [tier] == 110000",
+ // Gloves - IK Gauntlets
+ "[name] == wargauntlets && [quality] == set && [flag] != ethereal # [strength] >= 20 && [dexterity] >= 20 # [tier] == 110000",
+ // Amulet - Metalgrid
+ "[type] == amulet && [quality] == unique # [defense] >= 300 # [tier] == tierscore(item, 110000)",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - BO sticks
+ "[type] >= 1 && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.LevelingBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.LevelingBuild.js
index db266c8a..3c891df9 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.LevelingBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.LevelingBuild.js
@@ -6,50 +6,58 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: false,
- skillstab: sdk.skills.tabs.BarbCombat,
- wantedskills: [sdk.skills.BattleOrders, sdk.skills.Frenzy, sdk.skills.DoubleSwing, sdk.skills.SwordMastery],
- usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed, sdk.skills.Shout, sdk.skills.FindItem],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["dexterity", 136], ["strength", 150], ["vitality", 125],
- ["strength", 185], ["vitality", "all"],
- ],
- skills: [
- // Total points at time of respec 79
- [sdk.skills.SwordMastery, 11, true], // total left 68
- [sdk.skills.FindItem, 1, true], // total left 66
- [sdk.skills.DoubleSwing, 9, true], // total left 56
- [sdk.skills.NaturalResistance, 5, true], // total left 51
- [sdk.skills.Frenzy, 9, true], // total left 42
- [sdk.skills.Berserk, 5, true], // total left 35
- [sdk.skills.WarCry, 5, true], // total left 25
- [sdk.skills.BattleCommand, 1, true], // total left 24
- [sdk.skills.BattleOrders, 8, true], // total left 16
- [sdk.skills.Taunt, 16, true], // total left 0
- // End of respec points, Start of Leveling build - total points left to use 31
- [sdk.skills.Taunt, 20, false], // charlvl 75 -> total left 27
- [sdk.skills.BattleOrders, 10, false], // charlvl 77 -> total left 25
- [sdk.skills.SwordMastery, 20, false], // charlvl 84 -> total left 18
- [sdk.skills.Frenzy, 20, false], // total left 7
- [sdk.skills.BattleOrders, 15, false], // total left 0
- ],
- active: function () {
- return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.getSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints) >= 5 && !Check.finalBuild().active());
- },
-};
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BarbCombat,
+ wantedskills: [sdk.skills.BattleOrders, sdk.skills.Frenzy, sdk.skills.DoubleSwing, sdk.skills.SwordMastery],
+ usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed, sdk.skills.Shout, sdk.skills.FindItem],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["dexterity", 136], ["strength", 150], ["vitality", 125],
+ ["strength", 185], ["vitality", "all"],
+ ],
+ skills: [
+ // Total points at time of respec 79
+ [sdk.skills.SwordMastery, 11, true], // total left 68
+ [sdk.skills.FindItem, 1, true], // total left 66
+ [sdk.skills.DoubleSwing, 9, true], // total left 56
+ [sdk.skills.NaturalResistance, 5, true], // total left 51
+ [sdk.skills.Frenzy, 9, true], // total left 42
+ [sdk.skills.Berserk, 5, true], // total left 35
+ [sdk.skills.WarCry, 5, true], // total left 25
+ [sdk.skills.BattleCommand, 1, true], // total left 24
+ [sdk.skills.BattleOrders, 8, true], // total left 16
+ [sdk.skills.Taunt, 16, true], // total left 0
+ // End of respec points, Start of Leveling build - total points left to use 31
+ [sdk.skills.Taunt, 20, false], // charlvl 75 -> total left 27
+ [sdk.skills.BattleOrders, 10, false], // charlvl 77 -> total left 25
+ [sdk.skills.SwordMastery, 20, false], // charlvl 84 -> total left 18
+ [sdk.skills.Frenzy, 20, false], // total left 7
+ [sdk.skills.BattleOrders, 15, false], // total left 0
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.Frenzy, sdk.skills.Berserk, sdk.skills.Frenzy, sdk.skills.Berserk];
- Config.LowManaSkill = [sdk.skills.DoubleSwing, 0];
- Config.BeltColumn = ["hp", "hp", "hp", "mp"];
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.MPBuffer = me.expansion ? 2 : 4;
- Config.HPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
- SetUp.belt();
-});
+ active: function () {
+ return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.getSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints) >= 5 && !Check.finalBuild().active());
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.Frenzy, sdk.skills.Berserk, sdk.skills.Frenzy, sdk.skills.Berserk];
+ Config.LowManaSkill = [sdk.skills.DoubleSwing, 0];
+ Config.BeltColumn = ["hp", "hp", "hp", "mp"];
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.MPBuffer = me.expansion ? 2 : 4;
+ Config.HPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
+ SetUp.belt();
+ }
+ }
+ },
+ };
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.SingerBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.SingerBuild.js
index 624337fc..69e2f908 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.SingerBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.SingerBuild.js
@@ -6,114 +6,127 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Warcries,
- wantedskills: [sdk.skills.WarCry, sdk.skills.Shout],
- usefulskills: [sdk.skills.IncreasedSpeed, sdk.skills.NaturalResistance],
- precastSkills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["dexterity", 35], ["strength", 103], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.WarCry, 20, true],
- [sdk.skills.NaturalResistance, 4, true],
- [sdk.skills.BattleCry, 20, true],
- [sdk.skills.BattleCommand, 1, true],
- [sdk.skills.BattleOrders, 20, true],
- [sdk.skills.Taunt, 20, true],
- [sdk.skills.Shout, 11, false],
- [sdk.skills.Howl, 15, false],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - HotO x2 dual wield
- "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
- // Helmet - Harlequin's Crest
- "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Boots - Silkweave
- "[name] == meshboots && [quality] == unique && [flag] != ethereal # [frw] >= 30 # [tier] == 100000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Gloves - Frostburns
- "[name] == gauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 10 # [tier] == 100000",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Switch - BO sticks
- "([type] == club || [type] == sword || [type] == knife || [type] == throwingknife || [type] == mace) && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Warcries,
+ wantedskills: [sdk.skills.WarCry, sdk.skills.Shout],
+ usefulskills: [sdk.skills.IncreasedSpeed, sdk.skills.NaturalResistance],
+ precastSkills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["dexterity", 35], ["strength", 103], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.WarCry, 20, true],
+ [sdk.skills.NaturalResistance, 4, true],
+ [sdk.skills.BattleCry, 20, true],
+ [sdk.skills.BattleCommand, 1, true],
+ [sdk.skills.BattleOrders, 20, true],
+ [sdk.skills.Taunt, 20, true],
+ [sdk.skills.Shout, 11, false],
+ [sdk.skills.Howl, 15, false],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Warcries) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [sdk.skills.BattleCry, sdk.skills.WarCry, -1, sdk.skills.WarCry, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- Config.MPBuffer = 4;
- Config.HPBuffer = 2;
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Warcries) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.haveAll([{name: sdk.locale.items.Enigma}, {name: sdk.locale.items.HeartoftheOak}]);
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [sdk.skills.BattleCry, sdk.skills.WarCry, -1, sdk.skills.WarCry, -1];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ Config.MPBuffer = 4;
+ Config.HPBuffer = 2;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.haveAll([{ name: sdk.locale.items.Enigma }, { name: sdk.locale.items.HeartoftheOak }]);
+ }
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - HotO x2 dual wield
+ "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
+ // Helmet - Harlequin's Crest
+ "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Boots - Silkweave
+ "[name] == meshboots && [quality] == unique && [flag] != ethereal # [frw] >= 30 # [tier] == tierscore(item, 100000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Gloves - Frostburns
+ "[name] == gauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 10 # [tier] == 100000",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Switch - BO sticks
+ "[type] >= 1 && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.StartBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.StartBuild.js
index 6da9b6cf..33b59044 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.StartBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.StartBuild.js
@@ -6,79 +6,86 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: false,
- skillstab: sdk.skills.tabs.BarbCombat,
- wantedskills: [sdk.skills.BattleOrders, sdk.skills.Frenzy, sdk.skills.DoubleSwing, sdk.skills.SwordMastery],
- usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed, sdk.skills.Shout],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 35], ["dexterity", 27], ["vitality", 45],
- ["strength", 48], ["dexterity", 30], ["vitality", 55],
- ["strength", 55], ["dexterity", 39], ["vitality", 65],
- ["strength", 60], ["dexterity", 40], ["vitality", 75],
- ["strength", 71], ["dexterity", 49], ["vitality", "all"],
- ],
- skills: [
- [sdk.skills.Bash, 1], // charlevel 2
- [sdk.skills.Howl, 1], // charlevel 3
- [sdk.skills.DoubleSwing, 6, false], // charlevel 9
- [sdk.skills.SwordMastery, 5], // charlevel 13
- [sdk.skills.Taunt, 1], // charlevel 14
- [sdk.skills.SwordMastery, 6], // charlevel 15
- [sdk.skills.IronSkin, 1], // charlevel 18
- [sdk.skills.BattleCry, 1], // charlevel 18
- [sdk.skills.SwordMastery, 9],
- [sdk.skills.DoubleThrow, 1],
- [sdk.skills.Shout, 1],
- [sdk.skills.Taunt, 3, false],
- [sdk.skills.Frenzy, 1],
- [sdk.skills.BattleOrders, 4, false],
- [sdk.skills.Taunt, 20],
- ],
- active: function () {
- return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints);
- },
-};
+(function (module, require) {
+ module.exports = (function () {
+ const build = {
+ AutoBuildTemplate: {},
+ caster: false,
+ skillstab: sdk.skills.tabs.BarbCombat,
+ wantedskills: [sdk.skills.BattleOrders, sdk.skills.Frenzy, sdk.skills.DoubleSwing, sdk.skills.SwordMastery],
+ usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed, sdk.skills.Shout],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 35], ["dexterity", 27], ["vitality", 45],
+ ["strength", 48], ["dexterity", 30], ["vitality", 55],
+ ["strength", 55], ["dexterity", 39], ["vitality", 65],
+ ["strength", 60], ["dexterity", 40], ["vitality", 75],
+ ["strength", 71], ["dexterity", 49], ["vitality", "all"],
+ ],
+ skills: [
+ [sdk.skills.Bash, 1], // charlevel 2
+ [sdk.skills.Howl, 1], // charlevel 3
+ [sdk.skills.DoubleSwing, 6, false], // charlevel 9
+ [sdk.skills.SwordMastery, 5], // charlevel 13
+ [sdk.skills.Taunt, 1], // charlevel 14
+ [sdk.skills.SwordMastery, 6], // charlevel 15
+ [sdk.skills.IronSkin, 1], // charlevel 18
+ [sdk.skills.BattleCry, 1], // charlevel 18
+ [sdk.skills.SwordMastery, 9],
+ [sdk.skills.DoubleThrow, 1],
+ [sdk.skills.Shout, 1],
+ [sdk.skills.Taunt, 3, false],
+ [sdk.skills.Frenzy, 1],
+ [sdk.skills.BattleOrders, 4, false],
+ [sdk.skills.Taunt, 20],
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
- Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
- Config.FieldID.UsedSpace = 0;
+ active: function () {
+ return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints);
+ },
+ };
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.BeltColumn = ["hp", "hp", "hp", "hp"];
- Config.MPBuffer = 4;
- Config.HPBuffer = 6;
- Config.AttackSkill = [-1, 0, 0, 0, 0];
- Config.LowManaSkill = [0, 0];
- SetUp.belt();
-});
-build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
- if (me.checkSkill(sdk.skills.Bash, sdk.skills.subindex.HardPoints)) {
- Config.AttackSkill = [-1, sdk.skills.Bash, -1, sdk.skills.Attack, -1];
- }
-});
-build.AutoBuildTemplate[6] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.Bash, 0, 0, 0];
-});
-build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
- Config.BeltColumn = me.charlvl < 13 ? ["hp", "hp", "hp", "mp"] : ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = me.expansion ? 2 : 4;
- Config.MPBuffer = 6;
- Config.AttackSkill = [-1, sdk.skills.DoubleSwing, -1, sdk.skills.DoubleSwing, -1];
+ const { buildAutoBuildTempObj } = require("../../Utils/General");
+
+ build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
+ Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
+ Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
+ Config.FieldID.UsedSpace = 0;
- if (me.getSkill(sdk.skills.DoubleSwing, sdk.skills.subindex.SoftPoints) >= 9) {
- Config.LowManaSkill = [sdk.skills.DoubleSwing, 0];
- }
- SetUp.belt();
-});
-build.AutoBuildTemplate[24] = buildAutoBuildTempObj(() => {
- if (me.checkSkill(sdk.skills.Frenzy, sdk.skills.subindex.HardPoints)) {
- Config.AttackSkill = [-1, sdk.skills.Frenzy, -1, sdk.skills.Frenzy, -1];
- }
-});
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.BeltColumn = ["hp", "hp", "hp", "hp"];
+ Config.MPBuffer = 4;
+ Config.HPBuffer = 6;
+ Config.AttackSkill = [-1, 0, 0, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
+ if (me.checkSkill(sdk.skills.Bash, sdk.skills.subindex.HardPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.Bash, -1, sdk.skills.Attack, -1];
+ }
+ });
+ build.AutoBuildTemplate[6] = buildAutoBuildTempObj(() => {
+ Config.AttackSkill = [-1, sdk.skills.Bash, 0, 0, 0];
+ });
+ build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
+ Config.BeltColumn = me.charlvl < 13 ? ["hp", "hp", "hp", "mp"] : ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = me.expansion ? 2 : 4;
+ Config.MPBuffer = 6;
+ Config.AttackSkill = [-1, sdk.skills.DoubleSwing, -1, sdk.skills.DoubleSwing, -1];
+
+ if (me.getSkill(sdk.skills.DoubleSwing, sdk.skills.subindex.SoftPoints) >= 9) {
+ Config.LowManaSkill = [sdk.skills.DoubleSwing, 0];
+ }
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[24] = buildAutoBuildTempObj(() => {
+ if (me.checkSkill(sdk.skills.Frenzy, sdk.skills.subindex.HardPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.Frenzy, -1, sdk.skills.Frenzy, -1];
+ }
+ });
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.SteppingBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.SteppingBuild.js
index 84faa37e..c2cbf39a 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.SteppingBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.SteppingBuild.js
@@ -6,69 +6,76 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: false,
- skillstab: sdk.skills.tabs.BarbCombat,
- wantedskills: [sdk.skills.BattleOrders, sdk.skills.Frenzy, sdk.skills.DoubleSwing, sdk.skills.SwordMastery],
- usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed, sdk.skills.Shout, sdk.skills.FindItem],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 71], ["dexterity", 50], ["vitality", 100],
- ["strength", 85], ["dexterity", 60], ["vitality", 110],
- ["strength", 103], ["dexterity", 79], ["vitality", 125],
- ["dexterity", 94], ["strength", 125], ["vitality", 130],
- ["strength", 140], ["vitality", 135], ["strength", 150],
- ["vitality", "all"],
- ],
- skills: [
- // Total points at time of respec 33
- [sdk.skills.SwordMastery, 9, true], // total left 24
- [sdk.skills.FindItem, 1, true], // total left 22
- [sdk.skills.BattleOrders, 4, true], // total left 16
- [sdk.skills.BattleCommand, 1, true], // total left 15
- [sdk.skills.NaturalResistance, 1, true], // total left 13
- [sdk.skills.Frenzy, 2, true], // total left 8
- [sdk.skills.WarCry, 1, true], // total left 5
- [sdk.skills.DoubleSwing, 5, true], // total left 1
- // End of respec points, Start of Stepping build
- [sdk.skills.NaturalResistance, 2, false], // charlvl 31
- [sdk.skills.WarCry, 2, false], // charlvl 32
- [sdk.skills.NaturalResistance, 3, false], // charlvl 33
- [sdk.skills.WarCry, 3, false], // charlvl 34
- [sdk.skills.Taunt, 11, false], // charlvl 45
- [sdk.skills.NaturalResistance, 4, false], // charlvl 46
- [sdk.skills.Frenzy, 6, false], // charlvl 50
- [sdk.skills.WarCry, 5, false], // charlvl 52
- [sdk.skills.Frenzy, 9, false], // charlvl 53
- [sdk.skills.BattleOrders, 6, false], // charlvl 54
- [sdk.skills.NaturalResistance, 5, false], // charlvl 56
- [sdk.skills.WarCry, 6, false], // charlvl 59
- [sdk.skills.SwordMastery, 20, false], // charlvl 67
- [sdk.skills.Taunt, 20, false], // charlvl 76
- ],
- active: function () {
- return me.charlvl > CharInfo.respecOne && me.charlvl < CharInfo.respecTwo && me.checkSkill(sdk.skills.NaturalResistance, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.Berserk, sdk.skills.subindex.HardPoints);
- },
-};
+(function (module, require) {
+ module.exports = (function () {
+ const build = {
+ AutoBuildTemplate: {},
+ caster: false,
+ skillstab: sdk.skills.tabs.BarbCombat,
+ wantedskills: [sdk.skills.BattleOrders, sdk.skills.Frenzy, sdk.skills.DoubleSwing, sdk.skills.SwordMastery],
+ usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed, sdk.skills.Shout, sdk.skills.FindItem],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 71], ["dexterity", 50], ["vitality", 100],
+ ["strength", 85], ["dexterity", 60], ["vitality", 110],
+ ["strength", 103], ["dexterity", 79], ["vitality", 125],
+ ["dexterity", 94], ["strength", 125], ["vitality", 130],
+ ["strength", 140], ["vitality", 135], ["strength", 150],
+ ["vitality", "all"],
+ ],
+ skills: [
+ // Total points at time of respec 33
+ [sdk.skills.SwordMastery, 9, true], // total left 24
+ [sdk.skills.FindItem, 1, true], // total left 22
+ [sdk.skills.BattleOrders, 4, true], // total left 16
+ [sdk.skills.BattleCommand, 1, true], // total left 15
+ [sdk.skills.NaturalResistance, 1, true], // total left 13
+ [sdk.skills.Frenzy, 2, true], // total left 8
+ [sdk.skills.WarCry, 1, true], // total left 5
+ [sdk.skills.DoubleSwing, 5, true], // total left 1
+ // End of respec points, Start of Stepping build
+ [sdk.skills.NaturalResistance, 2, false], // charlvl 31
+ [sdk.skills.WarCry, 2, false], // charlvl 32
+ [sdk.skills.NaturalResistance, 3, false], // charlvl 33
+ [sdk.skills.WarCry, 3, false], // charlvl 34
+ [sdk.skills.Taunt, 11, false], // charlvl 45
+ [sdk.skills.NaturalResistance, 4, false], // charlvl 46
+ [sdk.skills.Frenzy, 6, false], // charlvl 50
+ [sdk.skills.WarCry, 5, false], // charlvl 52
+ [sdk.skills.Frenzy, 9, false], // charlvl 53
+ [sdk.skills.BattleOrders, 6, false], // charlvl 54
+ [sdk.skills.NaturalResistance, 5, false], // charlvl 56
+ [sdk.skills.WarCry, 6, false], // charlvl 59
+ [sdk.skills.SwordMastery, 20, false], // charlvl 67
+ [sdk.skills.Taunt, 20, false], // charlvl 76
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.DoubleSwing, -1, sdk.skills.DoubleSwing, -1];
- if (me.getSkill(sdk.skills.DoubleSwing, sdk.skills.subindex.SoftPoints) >= 9) {
- Config.LowManaSkill = [sdk.skills.DoubleSwing, 0];
- }
- SetUp.belt();
-});
-build.AutoBuildTemplate[24] = buildAutoBuildTempObj(() => {
- if (me.checkSkill(sdk.skills.Frenzy, sdk.skills.subindex.HardPoints)) {
- Config.AttackSkill = [-1, sdk.skills.Frenzy, -1, sdk.skills.Frenzy, -1];
- }
- Config.BeltColumn = ["hp", "hp", "hp", "mp"];
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.MPBuffer = me.expansion ? 2 : 4;
- Config.HPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
- SetUp.belt();
-});
+ active: function () {
+ return me.charlvl > CharInfo.respecOne && me.charlvl < CharInfo.respecTwo && me.checkSkill(sdk.skills.NaturalResistance, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.Berserk, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ const { buildAutoBuildTempObj } = require("../../Utils/General");
+
+ build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
+ Config.AttackSkill = [-1, sdk.skills.DoubleSwing, -1, sdk.skills.DoubleSwing, -1];
+ if (me.getSkill(sdk.skills.DoubleSwing, sdk.skills.subindex.SoftPoints) >= 9) {
+ Config.LowManaSkill = [sdk.skills.DoubleSwing, 0];
+ }
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[24] = buildAutoBuildTempObj(() => {
+ if (me.checkSkill(sdk.skills.Frenzy, sdk.skills.subindex.HardPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.Frenzy, -1, sdk.skills.Frenzy, -1];
+ }
+ Config.BeltColumn = ["hp", "hp", "hp", "mp"];
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.MPBuffer = me.expansion ? 2 : 4;
+ Config.HPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
+ SetUp.belt();
+ });
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.ThrowBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.ThrowBuild.js
index 2bcf5100..38c0ea82 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.ThrowBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.ThrowBuild.js
@@ -5,125 +5,164 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BarbCombat,
- wantedskills: [sdk.skills.BattleOrders, sdk.skills.DoubleThrow, sdk.skills.DoubleSwing],
- usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed],
- precastSkills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 103], ["dexterity", 79], ["vitality", 90],
- ["dexterity", 136], ["strength", 150], ["vitality", "all"],
- ],
- skills: [
- [sdk.skills.DoubleThrow, 20],
- [sdk.skills.Howl, 9],
- [sdk.skills.BattleOrders, 20],
- [sdk.skills.BattleCommand, 1],
- [sdk.skills.ThrowMastery, 20],
- [sdk.skills.NaturalResistance, 5],
- [sdk.skills.DoubleSwing, 20],
- [sdk.skills.Frenzy, 1],
- [sdk.skills.Berserk, 1],
- [sdk.skills.Howl, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - Perfect Eth Lacerator & Warshrike
- "[name] == wingedknife && [quality] == unique && [flag] == ethereal # [ias] == 30 && [enhanceddamage] == 250 # [tier] == 110000",
- "[name] == wingedaxe && [quality] == unique && [flag] == ethereal # [ias] == 30 && [enhanceddamage] == 210 # [tier] == 110000",
- // Weapon - Eth Lacerator & Warshrike
- "[name] == wingedknife && [quality] == unique && [flag] == ethereal # [ias] == 30 && [enhanceddamage] >= 200 # [tier] == 100000",
- "[name] == wingedaxe && [quality] == unique && [flag] == ethereal # [ias] == 30 && [enhanceddamage] >= 150 # [tier] == 100000",
- // Helmet - Arreat's Face
- "[name] == slayerguard && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 # [tier] == 100000",
- // Belt- Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000",
- // Boots - IK Boots
- "[name] == warboots && [quality] == set && [flag] != ethereal # [frw] >= 40 && [tohit] >= 110 # [tier] == 110000",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Gloves - Laying of Hands
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] == 20 # [tier] == 100000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - BO sticks
- "([type] == club || [type] == sword || [type] == knife || [type] == throwingknife || [type] == mace) && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BarbCombat,
+ wantedskills: [sdk.skills.BattleOrders, sdk.skills.DoubleThrow, sdk.skills.DoubleSwing],
+ usefulskills: [sdk.skills.NaturalResistance, sdk.skills.IronSkin, sdk.skills.IncreasedSpeed],
+ precastSkills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 103], ["dexterity", 79], ["vitality", 90],
+ ["dexterity", 136], ["strength", 150], ["vitality", "all"],
+ ],
+ skills: [
+ [sdk.skills.DoubleThrow, 20],
+ [sdk.skills.Howl, 9],
+ [sdk.skills.BattleOrders, 20],
+ [sdk.skills.BattleCommand, 1],
+ [sdk.skills.ThrowingMastery, 20],
+ [sdk.skills.NaturalResistance, 5],
+ [sdk.skills.DoubleSwing, 20],
+ [sdk.skills.Frenzy, 1],
+ [sdk.skills.Berserk, 1],
+ [sdk.skills.Howl, 20],
+ ],
- ResFHR: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ charms: {
+ ResLife: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- SkillerCombat: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BarbCombat) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerMasteries: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Masteries) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ SkillerCombat: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BarbCombat) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.DoubleThrow, sdk.skills.Frenzy, sdk.skills.DoubleThrow, sdk.skills.Berserk];
- Config.LowManaSkill = [sdk.skills.DoubleSwing, sdk.skills.DoubleSwing];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- }
- },
- },
+ SkillerMasteries: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Masteries) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return Check.haveItem("throwingknife", "unique", "Warshrike") && Check.haveItem("throwingaxe", "unique", "Lacerator");
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.DoubleThrow,
+ sdk.skills.Frenzy,
+ sdk.skills.DoubleThrow,
+ sdk.skills.Berserk
+ ];
+ Config.LowManaSkill = [sdk.skills.DoubleSwing, sdk.skills.DoubleSwing];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.DoubleThrow, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return (
+ me.checkItem({ name: sdk.locale.items.Warshrike, }).have
+ && me.checkItem({ name: sdk.locale.items.Lacerator, }).have
+ );
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.DoubleThrow, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Final Weapon - Perfect Eth Lacerator & Warshrike
+ "[name] == wingedknife && [quality] == unique && [flag] == ethereal # [ias] == 30 && [enhanceddamage] == 250 # [tier] == 110000",
+ "[name] == wingedaxe && [quality] == unique && [flag] == ethereal # [ias] == 30 && [enhanceddamage] == 210 # [tier] == 110000",
+ // Weapon - Eth Lacerator & Warshrike
+ "[name] == wingedknife && [quality] == unique && [flag] == ethereal # [ias] == 30 && [enhanceddamage] >= 200 # [tier] == 100000",
+ "[name] == wingedaxe && [quality] == unique && [flag] == ethereal # [ias] == 30 && [enhanceddamage] >= 150 # [tier] == 100000",
+ // Helmet - Arreat's Face
+ "[name] == slayerguard && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 # [tier] == 100000",
+ // Belt- Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000",
+ // Boots - IK Boots
+ "[name] == warboots && [quality] == set && [flag] != ethereal # [frw] >= 40 && [tohit] >= 110 # [tier] == 110000",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Gloves - Laying of Hands
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] == 20 # [tier] == 100000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - BO sticks
+ "[type] >= 1 && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.UberconcBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.UberconcBuild.js
index 000430a1..37e5678e 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.UberconcBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.UberconcBuild.js
@@ -5,111 +5,146 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BarbCombat,
- wantedskills: [sdk.skills.BattleOrders, sdk.skills.Concentrate],
- usefulskills: [sdk.skills.SwordMastery, sdk.skills.Bash],
- precastSkills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 196], ["dexterity", "block"], ["vitality", "all"],
- ],
- skills: [
- [sdk.skills.BattleCommand, 1],
- [sdk.skills.NaturalResistance, 1],
- [sdk.skills.WarCry, 1],
- [sdk.skills.Berserk, 1],
- [sdk.skills.BattleOrders, 20, false],
- [sdk.skills.SwordMastery, 10, false],
- [sdk.skills.Concentrate, 20, false],
- [sdk.skills.Shout, 20, false],
- [sdk.skills.LeapAttack, 1, false],
- [sdk.skills.Bash, 20, false],
- [sdk.skills.SwordMastery, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Grief
- "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 100000",
- // Helmet - Arreat's Face
- "[name] == slayerguard && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 # [tier] == 100000 + tierscore(item)",
- // Belt - Dungo's
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] >= 10 && [vitality] >= 30 # [tier] == 100000 + tierscore(item)",
- // Boots - Gore Rider's
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 100000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
- // Gloves - Drac's
- "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 && [strength] >= 12 && [lifeleech] >= 9 # [tier] == 100000 + tierscore(item)",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - BO sticks
- "([type] == club || [type] == sword || [type] == knife || [type] == throwingknife || [type] == mace) && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+// eslint-disable-next-line no-unused-vars
+(function (module, require) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BarbCombat,
+ wantedskills: [sdk.skills.BattleOrders, sdk.skills.Concentrate],
+ usefulskills: [sdk.skills.SwordMastery, sdk.skills.Bash],
+ precastSkills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 196], ["dexterity", "block"], ["vitality", "all"],
+ ],
+ skills: [
+ [sdk.skills.BattleCommand, 1],
+ [sdk.skills.NaturalResistance, 1],
+ [sdk.skills.WarCry, 1],
+ [sdk.skills.Berserk, 1],
+ [sdk.skills.BattleOrders, 20, false],
+ [sdk.skills.SwordMastery, 10, false],
+ [sdk.skills.Concentrate, 20, false],
+ [sdk.skills.Shout, 20, false],
+ [sdk.skills.LeapAttack, 1, false],
+ [sdk.skills.Bash, 20, false],
+ [sdk.skills.SwordMastery, 20],
+ ],
- ResFHR: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ charms: {
+ ResLife: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BarbCombat) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResFHR: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Concentrate, sdk.skills.Berserk, sdk.skills.Concentrate, sdk.skills.Berserk];
- Config.LowManaSkill = [0, 0];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.BarbCombat) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.checkItem({name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword}).have && Check.haveItem("monarch", "unique", "Stormshield");
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Concentrate,
+ sdk.skills.Berserk,
+ sdk.skills.Concentrate,
+ sdk.skills.Berserk
+ ];
+ Config.LowManaSkill = [0, 0];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Concentrate, sdk.skills.subindex.HardPoints) >= 5;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ }
+ return (
+ me.checkItem({ name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword }).have
+ && me.checkItem({ name: sdk.locale.items.Stormshield }).have
+ );
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Concentrate, sdk.skills.subindex.HardPoints) >= 5;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - Grief
+ "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 100000",
+ // Helmet - Arreat's Face
+ "[name] == slayerguard && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 # [tier] == tierscore(item, 100000)",
+ // Belt - Dungo's
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] >= 10 && [vitality] >= 30 # [tier] == tierscore(item, 100000)",
+ // Boots - Gore Rider's
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 100000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
+ // Gloves - Drac's
+ "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 && [strength] >= 12 && [lifeleech] >= 9 # [tier] == tierscore(item, 100000)",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - BO sticks
+ "[type] >= 1 && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.WhirlwindBuild.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.WhirlwindBuild.js
index de6df9ec..cd94e04b 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.WhirlwindBuild.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.WhirlwindBuild.js
@@ -5,124 +5,137 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.BarbCombat,
- wantedskills: [sdk.skills.Bash, sdk.skills.Whirlwind],
- usefulskills: [sdk.skills.Howl, sdk.skills.Shout],
- precastSkills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 118], ["dexterity", 136], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Whirlwind, 20, true],
- [sdk.skills.SwordMastery, 20, true],
- [sdk.skills.NaturalResistance, 5, true],
- [sdk.skills.BattleCommand, 1, true],
- [sdk.skills.Berserk, 5, true],
- [sdk.skills.IncreasedSpeed, 1, true],
- [sdk.skills.WarCry, 5, true],
- [sdk.skills.BattleOrders, 20, true],
- [sdk.skills.Shout, 20, true],
- [sdk.skills.IronSkin, 3, true],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Grief x2 dual wield
- "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 100000",
- // Final Helmet - Upp'ed Arreat's Face
- "[name] == guardiancrown && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 && [fhr] >= 30 # [tier] == 150000 + tierscore(item)",
- // Helmet - Arreat's Face
- "[name] == slayerguard && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 && [fhr] >= 30 # [tier] == 100000 + tierscore(item)",
- // Belt - Dungo's
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] >= 10 && [vitality] >= 30 # [tier] == 100000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 100000 + tierscore(item)",
- // Armor - Fortitude
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 30 # [tier] == 100000",
- // Gloves - Laying of Hands
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] == 20 # [tier] == 100000",
- // Amulet - Atma's
- "[type] == amulet && [quality] == unique # [poisonresist] == 75 # [tier] == 100000 + tierscore(item)",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - BO sticks
- "([type] == club || [type] == sword || [type] == knife || [type] == throwingknife || [type] == mace) && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.BarbCombat,
+ wantedskills: [sdk.skills.Bash, sdk.skills.Whirlwind],
+ usefulskills: [sdk.skills.Howl, sdk.skills.Shout],
+ precastSkills: [sdk.skills.BattleOrders, sdk.skills.BattleCommand],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 118], ["dexterity", 136], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Whirlwind, 20, true],
+ [sdk.skills.SwordMastery, 20, true],
+ [sdk.skills.NaturalResistance, 5, true],
+ [sdk.skills.BattleCommand, 1, true],
+ [sdk.skills.Berserk, 5, true],
+ [sdk.skills.IncreasedSpeed, 1, true],
+ [sdk.skills.WarCry, 5, true],
+ [sdk.skills.BattleOrders, 20, true],
+ [sdk.skills.Shout, 20, true],
+ [sdk.skills.IronSkin, 3, true],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Masteries) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResFHR: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [sdk.skills.BattleCry, sdk.skills.Whirlwind, -1, sdk.skills.Whirlwind, -1];
- Config.LowManaSkill = [0, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- Config.MPBuffer = 2;
- Config.HPBuffer = 2;
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Masteries) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- // TODO: figure out how to make sure we have two, or determine if that even matters
- return me.checkItem({name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword}).have;
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [sdk.skills.BattleCry, sdk.skills.Whirlwind, -1, sdk.skills.Whirlwind, -1];
+ Config.LowManaSkill = [0, -1];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ Config.MPBuffer = 2;
+ Config.HPBuffer = 2;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Whirlwind, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ // TODO: figure out how to make sure we have two, or determine if that even matters
+ return me.checkItem({ name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword }).have;
+ }
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Whirlwind, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - Grief x2 dual wield
+ "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 100000",
+ // Final Helmet - Upp'ed Arreat's Face
+ "[name] == guardiancrown && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 && [fhr] >= 30 # [tier] == tierscore(item, 150000)",
+ // Helmet - Arreat's Face
+ "[name] == slayerguard && [quality] == unique && [flag] != ethereal # [barbarianskills] == 2 && [fhr] >= 30 # [tier] == tierscore(item, 100000)",
+ // Belt - Dungo's
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] >= 10 && [vitality] >= 30 # [tier] == tierscore(item, 100000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 100000)",
+ // Armor - Fortitude
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 30 # [tier] == 100000",
+ // Gloves - Laying of Hands
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] == 20 # [tier] == 100000",
+ // Amulet - Atma's
+ "[type] == amulet && [quality] == unique # [poisonresist] == 75 # [tier] == tierscore(item, 100000)",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - BO sticks
+ "[type] >= 1 && ([quality] == magic || [flag] == runeword) && [2handed] == 0 # [itemallskills]+[warcriesskilltab]+[barbarianskills] >= 1 # [secondarytier] == 100000 + secondaryscore(item)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/barbarian/barbarian.js b/libs/SoloPlay/BuildFiles/barbarian/barbarian.js
index f4a39cc7..152ba70e 100644
--- a/libs/SoloPlay/BuildFiles/barbarian/barbarian.js
+++ b/libs/SoloPlay/BuildFiles/barbarian/barbarian.js
@@ -6,40 +6,46 @@
*/
const CharInfo = {
- respecOne: me.expansion ? 30 : 30,
- respecTwo: me.expansion ? 74 : 74,
- levelCap: (function() {
- const currentDiff = sdk.difficulty.nameOf(me.diff);
- const softcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 75 : 75,
- "Hell": 100,
- };
- const hardcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 75 : 75,
- "Hell": 100,
- };
+ respecOne: me.expansion ? 30 : 30,
+ respecTwo: me.expansion ? 74 : 74,
+ levelCap: (function () {
+ const currentDiff = sdk.difficulty.nameOf(me.diff);
+ const softcoreMode = {
+ "Normal": me.expansion ? 33 : 33,
+ "Nightmare": me.expansion ? 75 : 75,
+ "Hell": 100,
+ };
+ const hardcoreMode = {
+ "Normal": me.expansion ? 36 : 33,
+ "Nightmare": me.expansion ? 75 : 75,
+ "Hell": 100,
+ };
- return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
- })(),
+ return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
+ })(),
- getActiveBuild: function () {
- const nSkills = me.getStat(sdk.stats.NewSkills);
- const currLevel = me.charlvl;
- const justRepeced = (nSkills >= currLevel);
+ getActiveBuild: function () {
+ const nSkills = me.getStat(sdk.stats.NewSkills);
+ const currLevel = me.charlvl;
+ const justRepeced = (nSkills >= currLevel);
+ const { respecOne, respecTwo } = this;
- switch (true) {
- case currLevel < this.respecOne && !me.checkSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints):
- return "Start";
- case currLevel >= this.respecOne && currLevel < this.respecTwo && justRepeced:
- case currLevel >= this.respecOne && currLevel < this.respecTwo && me.checkSkill(sdk.skills.NaturalResistance, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.Berserk, sdk.skills.subindex.HardPoints):
- return "Stepping";
- case Check.finalBuild().respec() && justRepeced:
- case Check.finalBuild().active():
- return SetUp.finalBuild;
- default:
- return "Leveling";
- }
- },
+ switch (true) {
+ case currLevel < respecOne && !me.checkSkill(sdk.skills.WarCry, sdk.skills.subindex.HardPoints):
+ return "Start";
+ case currLevel >= respecOne && currLevel < respecTwo && justRepeced:
+ case (
+ currLevel >= respecOne
+ && currLevel < respecTwo
+ && me.checkSkill(sdk.skills.NaturalResistance, sdk.skills.subindex.HardPoints)
+ && !me.checkSkill(sdk.skills.Berserk, sdk.skills.subindex.HardPoints)
+ ):
+ return "Stepping";
+ case Check.finalBuild().respec() && justRepeced:
+ case Check.finalBuild().active():
+ return SetUp.finalBuild;
+ default:
+ return "Leveling";
+ }
+ },
};
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.ElementalBuild.js b/libs/SoloPlay/BuildFiles/druid/druid.ElementalBuild.js
index ee193074..fd77ea35 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.ElementalBuild.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.ElementalBuild.js
@@ -5,124 +5,173 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Elemental,
- wantedskills: [sdk.skills.Firestorm, sdk.skills.Fissure],
- usefulskills: [sdk.skills.CycloneArmor],
- precastSkills: [sdk.skills.CycloneArmor],
- usefulStats: [sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["dexterity", 35], ["strength", 48], ["vitality", 165],
- ["strength", 61], ["vitality", 252], ["strength", 156], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.OakSage, 6, false],
- [sdk.skills.Fissure, 11, false],
- [sdk.skills.Grizzly, 1, false],
- [sdk.skills.Volcano, 1, false],
- [sdk.skills.Fissure, 20, false],
- [sdk.skills.CycloneArmor, 1, false],
- [sdk.skills.Firestorm, 20, false],
- [sdk.skills.Volcano, 20, false],
- [sdk.skills.OakSage, 20, false],
- [sdk.skills.CycloneArmor, 20, false],
- [sdk.skills.Grizzly, 5, false],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - HotO
- "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
- // Helmet - Ravenlore
- "[name] == skyspirit && [quality] == unique # [passivefirepierce] >= 10 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Shield - Phoenix
- "[name] == monarch && [flag] != ethereal && [flag] == runeword # [passivefirepierce] >= 28 # [tier] == 100000 + tierscore(item)",
- // Final Gloves - Perfect 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Elemental,
+ wantedskills: [sdk.skills.Firestorm, sdk.skills.Fissure],
+ usefulskills: [sdk.skills.CycloneArmor],
+ precastSkills: [sdk.skills.CycloneArmor],
+ usefulStats: [sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["dexterity", 35], ["strength", 48], ["vitality", 165],
+ ["strength", 61], ["vitality", 252], ["strength", 156], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.OakSage, 6, false],
+ [sdk.skills.Fissure, 11, false],
+ [sdk.skills.Grizzly, 1, false],
+ [sdk.skills.Volcano, 1, false],
+ [sdk.skills.Fissure, 20, false],
+ [sdk.skills.CycloneArmor, 1, false],
+ [sdk.skills.Firestorm, 20, false],
+ [sdk.skills.Volcano, 20, false],
+ [sdk.skills.OakSage, 20, false],
+ [sdk.skills.CycloneArmor, 20, false],
+ [sdk.skills.Grizzly, 5, false],
+ ],
+ autoEquipTiers: [ // autoequip final gear
+ // Weapon - HotO
+ "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
+ // Helmet - Ravenlore
+ "[name] == skyspirit && [quality] == unique # [passivefirepierce] >= 10 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Shield - Phoenix
+ "[name] == monarch && [flag] != ethereal && [flag] == runeword # [passivefirepierce] >= 28 # [tier] == tierscore(item, 100000)",
+ // Final Gloves - Perfect 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
+ // Gloves - 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Elemental) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Fissure, sdk.skills.Firestorm, sdk.skills.Fissure, sdk.skills.Firestorm, sdk.skills.ArticBlast, -1];
- Config.SummonAnimal = "Grizzly";
- Config.SummonSpirit = "Oak Sage";
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Elemental) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- return Check.haveItem("armor", "runeword", "Enigma");
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.Fissure, sdk.skills.Firestorm, sdk.skills.Fissure, sdk.skills.Firestorm, sdk.skills.ArticBlast, -1];
+ Config.SummonAnimal = "Grizzly";
+ Config.SummonSpirit = "Oak Sage";
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.checkSkill(sdk.skills.Volcano, sdk.skills.subindex.HardPoints);
- },
-};
+ respec: function () {
+ return me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have;
+ },
+
+ active: function () {
+ return this.respec() && me.checkSkill(sdk.skills.Volcano, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - HotO
+ "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
+ // Helmet - Ravenlore
+ "[name] == skyspirit && [quality] == unique # [passivefirepierce] >= 10 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Shield - Phoenix
+ "[name] == monarch && [flag] != ethereal && [flag] == runeword # [passivefirepierce] >= 28 # [tier] == tierscore(item, 100000)",
+ // Final Gloves - Perfect 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
+ // Gloves - 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.FirewolfBuild.js b/libs/SoloPlay/BuildFiles/druid/druid.FirewolfBuild.js
index c5ac9c49..1de30932 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.FirewolfBuild.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.FirewolfBuild.js
@@ -5,122 +5,132 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.ShapeShifting,
- wantedskills: [sdk.skills.Werewolf, sdk.skills.Lycanthropy, sdk.skills.FireClaws],
- usefulskills: [sdk.skills.HeartofWolverine, sdk.skills.Grizzly, sdk.skills.Rabies, sdk.skills.Volcano, sdk.skills.Fissure, sdk.skills.Firestorm],
- precastSkills: [sdk.skills.Werewolf, sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
- usefulStats: [sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce, sdk.stats.PierceFire],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 156], ["dexterity", 136], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Werewolf, 1, false],
- [sdk.skills.Lycanthropy, 20, false],
- [sdk.skills.PoisonCreeper, 1, false],
- [sdk.skills.Grizzly, 1, false],
- [sdk.skills.HeartofWolverine, 1, false],
- [sdk.skills.Volcano, 1, false],
- [sdk.skills.Fury, 1, false],
- [sdk.skills.FireClaws, 20, false],
- [sdk.skills.Rabies, 20, false],
- [sdk.skills.Volcano, 20, false],
- [sdk.skills.HeartofWolverine, 20, false],
- ],
- // AutoEquip Final Gear
- autoEquipTiers: [
- // Weapon - Ice
- "[name] == demoncrossbow && [flag] == runeword # [holyfreezeaura] == 18 # [tier] == 110000",
- // Helmet - Jalal's Mane
- "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == 110000 + tierscore(item)",
- // Belt - Verdungo's
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 110000 + tierscore(item)",
- // Armor - Chains of Honor
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
- // Gloves - Dracul's Grasp
- "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 110000 + tierscore(item)",
- // Boots - Goblin Toes
- "[name] == lightplatedboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 50 # [tier] == 100000 + tierscore(item)",
- // Amulet - Metalgrid
- "[type] == amulet && [quality] == unique # [tohit] >= 400 && [coldresist] >= 25 # [tier] == 110000 + tierscore(item)",
- // Ring 1 - Ravenfrost
- "[type] == ring && [quality] == unique # [tohit] >= 180 && [dexterity] >= 15 # [tier] == 100000",
- // Ring 2 - Carrion Wind
- "[name] == ring && [quality] == unique # [poisonresist] == 55 && [lifeleech] >= 6 # [tier] == 110000 + tierscore(item)",
- // Merc
- // Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Temporary Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Final Helm - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Temporary Helm - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- // Final Weapon - Infinity
- "[type] == polearm && [flag] == runeword # [convictionaura] >= 13 # [merctier] == 100000 + mercscore(item)",
- // Temporary Weapon - Reaper's Toll
- "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 50000 + mercscore(item)",
- ],
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.ShapeShifting,
+ wantedskills: [sdk.skills.Werewolf, sdk.skills.Lycanthropy, sdk.skills.FireClaws],
+ usefulskills: [sdk.skills.HeartofWolverine, sdk.skills.Grizzly, sdk.skills.Rabies, sdk.skills.Volcano, sdk.skills.Fissure, sdk.skills.Firestorm],
+ precastSkills: [sdk.skills.Werewolf, sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
+ usefulStats: [sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce, sdk.stats.PierceFire],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 156], ["dexterity", 136], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Werewolf, 1, false],
+ [sdk.skills.Lycanthropy, 20, false],
+ [sdk.skills.PoisonCreeper, 1, false],
+ [sdk.skills.Grizzly, 1, false],
+ [sdk.skills.HeartofWolverine, 1, false],
+ [sdk.skills.Volcano, 1, false],
+ [sdk.skills.Fury, 1, false],
+ [sdk.skills.FireClaws, 20, false],
+ [sdk.skills.Rabies, 20, false],
+ [sdk.skills.Volcano, 20, false],
+ [sdk.skills.HeartofWolverine, 20, false],
+ ],
- charms: {
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Poison: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid
- && ((check.getStat(sdk.stats.PoisonLength) * check.getStat(sdk.stats.PoisonMaxDamage)) / 256) >= 141);
- }
- },
+ Poison: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && ((check.getStat(sdk.stats.PoisonLength) * check.getStat(sdk.stats.PoisonMaxDamage)) / 256) >= 141);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShapeShifting) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShapeShifting) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- Config.AttackSkill = [
- sdk.skills.FeralRage,
- sdk.skills.FireClaws, sdk.skills.Rabies,
- sdk.skills.FireClaws, sdk.skills.Rabies,
- sdk.skills.Fury, sdk.skills.Rabies
- ];
- Config.LowManaSkill = [0, 0];
- Config.Wereform = "Werewolf";
- Config.SummonAnimal = "Grizzly";
- Config.SummonSpirit = "Heart of Wolverine";
- }
- },
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ Config.AttackSkill = [
+ sdk.skills.FeralRage,
+ sdk.skills.FireClaws, sdk.skills.Rabies,
+ sdk.skills.FireClaws, sdk.skills.Rabies,
+ sdk.skills.Fury, sdk.skills.Rabies
+ ];
+ Config.LowManaSkill = [0, 0];
+ Config.Wereform = "Werewolf";
+ Config.SummonAnimal = "Grizzly";
+ Config.SummonSpirit = "Heart of Wolverine";
+ }
+ },
+ },
- respec: function () {
- return me.haveAll([{name: sdk.locale.items.Ice}, {name: sdk.locale.items.ChainsofHonor}]);
- },
+ respec: function () {
+ return me.haveAll([{ name: sdk.locale.items.Ice }, { name: sdk.locale.items.ChainsofHonor }]);
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.FireClaws, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.FireClaws, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [
+ // Weapon - Ice
+ "[name] == demoncrossbow && [flag] == runeword # [holyfreezeaura] == 18 # [tier] == 110000",
+ // Helmet - Jalal's Mane
+ "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == tierscore(item, 110000)",
+ // Belt - Verdungo's
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 110000)",
+ // Armor - Chains of Honor
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
+ // Gloves - Dracul's Grasp
+ "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 110000)",
+ // Boots - Goblin Toes
+ "[name] == lightplatedboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 50 # [tier] == tierscore(item, 100000)",
+ // Amulet - Metalgrid
+ "[type] == amulet && [quality] == unique # [tohit] >= 400 && [coldresist] >= 25 # [tier] == tierscore(item, 110000)",
+ // Ring 1 - Ravenfrost
+ "[type] == ring && [quality] == unique # [tohit] >= 180 && [dexterity] >= 15 # [tier] == 100000",
+ // Ring 2 - Carrion Wind
+ "[name] == ring && [quality] == unique # [poisonresist] == 55 && [lifeleech] >= 6 # [tier] == tierscore(item, 110000)",
+ // Merc
+ // Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Temporary Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Final Helm - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Temporary Helm - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ // Final Weapon - Infinity
+ "[type] == polearm && [flag] == runeword # [convictionaura] >= 13 # [merctier] == 100000 + mercscore(item)",
+ // Temporary Weapon - Reaper's Toll
+ "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 50000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.LevelingBuild.js b/libs/SoloPlay/BuildFiles/druid/druid.LevelingBuild.js
index 2ac04817..dc1189e7 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.LevelingBuild.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.LevelingBuild.js
@@ -5,52 +5,61 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: 42, // elemental
- wantedskills: [sdk.skills.Tornado, sdk.skills.Hurricane, sdk.skills.Twister],
- usefulskills: [sdk.skills.CycloneArmor],
- mercAuraName: "Blessed Aim",
- mercAuraWanted: sdk.skills.BlessedAim,
- mercDiff: sdk.difficulty.Normal,
- stats: [
- ["strength", 48], ["dexterity", 35], ["vitality", 165],
- ["strength", 61], ["vitality", 252], ["strength", 156], ["vitality", "all"]
- ],
- skills: [
- // Total skills at respec = 25 (assume hasn't killed izual yet)
- [sdk.skills.Tornado, 1], // points left 21
- [sdk.skills.OakSage, 6], // points left 15
- [sdk.skills.SummonDireWolf, 1], // points left 12
- [sdk.skills.CycloneArmor, 13], // points left 0
- // Start
- [sdk.skills.Tornado, 13, false],
- [sdk.skills.Hurricane, 6, false],
- [sdk.skills.Tornado, 14, false],
- [sdk.skills.Hurricane, 7, false],
- [sdk.skills.Grizzly, 1, false],
- [sdk.skills.Tornado, 15, false],
- [sdk.skills.Hurricane, 8, false],
- [sdk.skills.Tornado, 20, false],
- [sdk.skills.Hurricane, 20, false],
- [sdk.skills.CycloneArmor, 20, false],
- [sdk.skills.OakSage, 20, false],
- [sdk.skills.Twister, 20],
- ],
- active: function () {
- return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.checkSkill(sdk.skills.Tornado, sdk.skills.subindex.HardPoints) && !Check.finalBuild().active());
- },
-};
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: 42, // elemental
+ wantedskills: [sdk.skills.Tornado, sdk.skills.Hurricane, sdk.skills.Twister],
+ usefulskills: [sdk.skills.CycloneArmor],
+ wantedMerc: MercData[sdk.skills.BlessedAim],
+ stats: [
+ ["strength", 48], ["dexterity", 35], ["vitality", 165],
+ ["strength", 61], ["vitality", 252], ["strength", 156], ["vitality", "all"]
+ ],
+ skills: [
+ // Total skills at respec = 25 (assume hasn't killed izual yet)
+ [sdk.skills.Tornado, 1], // points left 21
+ [sdk.skills.OakSage, 6], // points left 15
+ [sdk.skills.SummonDireWolf, 1], // points left 12
+ [sdk.skills.CycloneArmor, 13], // points left 0
+ // Start
+ [sdk.skills.Tornado, 13, false],
+ [sdk.skills.Hurricane, 6, false],
+ [sdk.skills.Tornado, 14, false],
+ [sdk.skills.Hurricane, 7, false],
+ [sdk.skills.Grizzly, 1, false],
+ [sdk.skills.Tornado, 15, false],
+ [sdk.skills.Hurricane, 8, false],
+ [sdk.skills.Tornado, 20, false],
+ [sdk.skills.Hurricane, 20, false],
+ [sdk.skills.CycloneArmor, 20, false],
+ [sdk.skills.OakSage, 20, false],
+ [sdk.skills.Twister, 20],
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.Tornado, -1, sdk.skills.Tornado, -1, sdk.skills.ArticBlast, -1];
- Config.LowManaSkill = [-1, -1];
- Config.SummonAnimal = "Grizzly";
- Config.SummonSpirit = "Oak Sage";
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = 2;
- Config.MPBuffer = me.charlvl < 80 ? 6 : 2;
- SetUp.belt();
-});
+ active: function () {
+ return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.checkSkill(sdk.skills.Tornado, sdk.skills.subindex.HardPoints) && !Check.finalBuild().active());
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.SkipImmune = ["cold and physical"];
+ Config.AttackSkill = [-1, sdk.skills.Tornado, -1, sdk.skills.Tornado, -1, sdk.skills.ArcticBlast, -1];
+ Config.LowManaSkill = [-1, -1];
+ Config.SummonAnimal = "Grizzly";
+ Config.SummonSpirit = "Oak Sage";
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = 2;
+ Config.MPBuffer = me.charlvl < 80 ? 6 : 2;
+ SetUp.belt();
+ }
+ }
+ },
+ };
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.PlaguewolfBuild.js b/libs/SoloPlay/BuildFiles/druid/druid.PlaguewolfBuild.js
index a05c8399..e66cb246 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.PlaguewolfBuild.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.PlaguewolfBuild.js
@@ -5,111 +5,123 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.ShapeShifting,
- wantedskills: [sdk.skills.Werewolf, sdk.skills.Lycanthropy, sdk.skills.Fury],
- usefulskills: [sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
- precastSkills: [sdk.skills.Werewolf, sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
- usefulStats: [sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce, sdk.stats.PiercePois],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 156], ["dexterity", 136], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Werewolf, 20, false],
- [sdk.skills.Lycanthropy, 20, false],
- [sdk.skills.PoisonCreeper, 1, false],
- [sdk.skills.Grizzly, 1, false],
- [sdk.skills.Rabies, 20, false],
- [sdk.skills.Fury, 20, false],
- [sdk.skills.HeartofWolverine, 20, false],
- [sdk.skills.PoisonCreeper, 20, false],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Grief
- "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 110000",
- // Shield - Stormshield
- "[name] == monarch && [quality] == unique # [damageresist] >= 35 # [tier] == 110000",
- // Helmet - Jalal's Mane
- "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == 110000 + tierscore(item)",
- // Belt - Verdungo's
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 110000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
- // Gloves - Dracul's
- "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 110000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 110000 + tierscore(item)",
- // Final Rings - Perfect Wisp & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Wisp & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- // Merc Weapon - Reaper's Toll
- "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 100000 + mercscore(item)",
- ],
- charms: {
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.ShapeShifting,
+ wantedskills: [sdk.skills.Werewolf, sdk.skills.Lycanthropy, sdk.skills.Fury],
+ usefulskills: [sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
+ precastSkills: [sdk.skills.Werewolf, sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
+ usefulStats: [sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce, sdk.stats.PiercePois],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 156], ["dexterity", 136], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Werewolf, 20, false],
+ [sdk.skills.Lycanthropy, 20, false],
+ [sdk.skills.PoisonCreeper, 1, false],
+ [sdk.skills.Grizzly, 1, false],
+ [sdk.skills.Rabies, 20, false],
+ [sdk.skills.Fury, 20, false],
+ [sdk.skills.HeartofWolverine, 20, false],
+ [sdk.skills.PoisonCreeper, 20, false],
+ ],
- Poison: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid
- && ((check.getStat(sdk.stats.PoisonLength) * check.getStat(sdk.stats.PoisonMaxDamage)) / 256) >= 141);
- }
- },
+ charms: {
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShapeShifting) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ Poison: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && ((check.getStat(sdk.stats.PoisonLength) * check.getStat(sdk.stats.PoisonMaxDamage)) / 256) >= 141);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- Config.AttackSkill = [sdk.skills.FeralRage, sdk.skills.Fury, sdk.skills.Rabies, sdk.skills.Fury, sdk.skills.Rabies, sdk.skills.Rabies, -1];
- Config.LowManaSkill = [0, 0];
- Config.Wereform = "Werewolf";
- Config.SummonAnimal = "Grizzly";
- Config.SummonSpirit = "Heart of Wolverine";
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShapeShifting) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- return me.haveAll([{name: sdk.locale.items.Grief}, {name: sdk.locale.items.ChainsofHonor}]);
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ Config.AttackSkill = [sdk.skills.FeralRage, sdk.skills.Fury, sdk.skills.Rabies, sdk.skills.Fury, sdk.skills.Rabies, sdk.skills.Rabies, -1];
+ Config.LowManaSkill = [0, 0];
+ Config.Wereform = "Werewolf";
+ Config.SummonAnimal = "Grizzly";
+ Config.SummonSpirit = "Heart of Wolverine";
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Rabies, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ return me.haveAll([{ name: sdk.locale.items.Grief }, { name: sdk.locale.items.ChainsofHonor }]);
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Rabies, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - Grief
+ "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 110000",
+ // Shield - Stormshield
+ "[name] == monarch && [quality] == unique # [damageresist] >= 35 # [tier] == 110000",
+ // Helmet - Jalal's Mane
+ "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == tierscore(item, 110000)",
+ // Belt - Verdungo's
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 110000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
+ // Gloves - Dracul's
+ "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 110000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 110000)",
+ // Final Rings - Perfect Wisp & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Wisp & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ // Merc Weapon - Reaper's Toll
+ "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 100000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.StartBuild.js b/libs/SoloPlay/BuildFiles/druid/druid.StartBuild.js
index 4950d432..d4768ae2 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.StartBuild.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.StartBuild.js
@@ -5,64 +5,74 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: 42, // elemental
- wantedskills: [sdk.skills.Firestorm, sdk.skills.Fissure],
- usefulskills: [sdk.skills.MoltenBoulder],
- mercAuraName: "Blessed Aim",
- mercAuraWanted: 108,
- mercDiff: 0,
- stats: [
- ["vitality", 70],
- ["strength", 35],
- ["energy", 85],
- ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.PoisonCreeper, 2, false],
- [sdk.skills.Firestorm, 4, false],
- [sdk.skills.MoltenBoulder, 1],
- [sdk.skills.Firestorm, 7, false],
- [sdk.skills.Fissure, 1],
- [sdk.skills.CarrionVine, 1],
- [sdk.skills.Fissure, 8, false],
- [sdk.skills.Firestorm, 11, false],
- [sdk.skills.Fissure, 20, false],
- [sdk.skills.Firestorm, 18, false],
- ],
- active: function () {
- return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.Tornado, sdk.skills.subindex.HardPoints);
- },
-};
+(function (module, require) {
+ module.exports = (function () {
+ /**
+ * @todo Test summoner/elemental build
+ */
+ const build = {
+ AutoBuildTemplate: {},
+ caster: true,
+ skillstab: sdk.skills.tabs.Elemental,
+ wantedskills: [sdk.skills.Firestorm, sdk.skills.Fissure],
+ usefulskills: [sdk.skills.MoltenBoulder],
+ wantedMerc: MercData[sdk.skills.BlessedAim],
+ stats: [
+ ["vitality", 70],
+ ["strength", 35],
+ ["energy", 85],
+ ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.PoisonCreeper, 2, false],
+ [sdk.skills.Firestorm, 4, false],
+ [sdk.skills.MoltenBoulder, 1],
+ [sdk.skills.Firestorm, 7, false],
+ [sdk.skills.Fissure, 1],
+ [sdk.skills.CarrionVine, 1],
+ [sdk.skills.Fissure, 8, false],
+ [sdk.skills.Firestorm, 11, false],
+ [sdk.skills.Fissure, 20, false],
+ [sdk.skills.Firestorm, 18, false],
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.SkipImmune = ["fire"];
- Config.BeltColumn = me.charlvl < 7 ? ["hp", "hp", "hp", "mp"] : ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = 4;
- Config.MPBuffer = 4;
- Config.AttackSkill = [-1, 0, 0, 0, 0, 0, 0];
- Config.LowManaSkill = [0, 0];
- Config.SummonVine = 1; // "Poison Creeper"
- if (me.checkSkill(sdk.skills.Firestorm, sdk.skills.subindex.HardPoints)) {
- Config.AttackSkill = [-1, sdk.skills.Firestorm, -1, sdk.skills.Firestorm, -1, 0, 0];
- } else {
- Config.AttackSkill = [-1, 0, 0, 0, 0, 0, 0];
- }
- SetUp.belt();
-});
+ active: function () {
+ return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.Tornado, sdk.skills.subindex.HardPoints);
+ },
+ };
-build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
- if (me.checkSkill(sdk.skills.Fissure, sdk.skills.subindex.HardPoints)) {
- Config.AttackSkill = [-1, sdk.skills.Fissure, sdk.skills.Firestorm, sdk.skills.Fissure, sdk.skills.Firestorm, 0, 0];
- }
-});
+ const { buildAutoBuildTempObj } = require("../../Utils/General");
-build.AutoBuildTemplate[13] = buildAutoBuildTempObj(() => {
- if (Skill.canUse(sdk.skills.Fissure) && Skill.skills.all[sdk.skills.Fissure].hardpoints) {
- Config.AttackSkill = [-1, sdk.skills.Fissure, sdk.skills.Firestorm, sdk.skills.Fissure, sdk.skills.Firestorm, 0, 0];
- }
-});
+ build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.SkipImmune = ["fire"];
+ Config.BeltColumn = me.charlvl < 7 ? ["hp", "hp", "hp", "mp"] : ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = 4;
+ Config.MPBuffer = 4;
+ Config.AttackSkill = [-1, 0, 0, 0, 0, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ Config.SummonVine = 1; // "Poison Creeper"
+ if (me.checkSkill(sdk.skills.Firestorm, sdk.skills.subindex.HardPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.Firestorm, -1, sdk.skills.Firestorm, -1, 0, 0];
+ } else {
+ Config.AttackSkill = [-1, 0, 0, 0, 0, 0, 0];
+ }
+ SetUp.belt();
+ });
+
+ build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
+ if (me.checkSkill(sdk.skills.Fissure, sdk.skills.subindex.HardPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.Fissure, sdk.skills.Firestorm, sdk.skills.Fissure, sdk.skills.Firestorm, 0, 0];
+ }
+ });
+
+ build.AutoBuildTemplate[13] = buildAutoBuildTempObj(() => {
+ if (Skill.canUse(sdk.skills.Fissure)) {
+ Config.AttackSkill = [-1, sdk.skills.Fissure, sdk.skills.Firestorm, sdk.skills.Fissure, sdk.skills.Firestorm, 0, 0];
+ }
+ });
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.StormbearBuild.js b/libs/SoloPlay/BuildFiles/druid/druid.StormbearBuild.js
index 211d09fa..120fdce8 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.StormbearBuild.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.StormbearBuild.js
@@ -1,126 +1,136 @@
/**
* @filename druid.StormbearBuild.js
* @author theBGuy
-* @desc CTC based bear final build - uses Destruction and Dragon RWs for chance to cast spells
+* @desc CTC based bear final build - uses Destruction and Dragon RWs for chance to cast spells - not implemented - don't use!
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.ShapeShifting,
- wantedskills: [sdk.skills.Werebear, sdk.skills.Lycanthropy, sdk.skills.Maul],
- usefulskills: [sdk.skills.HeartofWolverine, sdk.skills.Grizzly, sdk.skills.Shockwave, sdk.skills.PoisonCreeper],
- precastSkills: [sdk.skills.Werebear, sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
- usefulStats: [],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 127], ["dexterity", 136], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Werebear, 20, false],
- [sdk.skills.Lycanthropy, 20, false],
- [sdk.skills.ShockWave, 1, false],
- [sdk.skills.Maul, 20, false],
- [sdk.skills.Grizzly, 20, false],
- [sdk.skills.HeartofWolverine, 20, false],
- [sdk.skills.PoisonCreeper, 20, false],
- [sdk.skills.Shockwave, 20, false],
- ],
- // AutoEquip Final Gear
- autoEquipTiers: [
- // Weapon - Destruction
- "[name] == phaseblade && [flag] == runeword # [enhanceddamage] >= 350 && [itemdeadlystrike] == 20 && [itemcrushingblow] == 20 # [tier] == 110000",
- // Shield - Sanctuary
- "[name] == hyperion # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 70 # [tier] == 110000",
- "[name] == hyperion # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000 + tierscore(item)",
- // Helmet - Jalal's Mane
- "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == 110000 + tierscore(item)",
- // Belt - Verdungo's
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 110000 + tierscore(item)",
- // Armor - Chains of Honor
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
- // Gloves - Dracul's Grasp
- "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 110000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Amulet - Metalgrid
- "[type] == amulet && [quality] == unique # [tohit] >= 400 && [coldresist] >= 25 # [tier] == 110000 + tierscore(item)",
- // Ring 1 - Ravenfrost
- "[type] == ring && [quality] == unique # [tohit] >= 180 && [dexterity] >= 15 # [tier] == 100000",
- // Ring 2 - Carrion Wind
- "[name] == ring && [quality] == unique # [poisonresist] == 55 && [lifeleech] >= 6 # [tier] == 110000 + tierscore(item)",
- // Merc
- // Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Temporary Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Final Helm - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Temporary Helm - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- // Final Weapon - Infinity
- "[type] == polearm && [flag] == runeword # [convictionaura] >= 13 # [merctier] == 100000 + mercscore(item)",
- // Temporary Weapon - Reaper's Toll
- "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 50000 + mercscore(item)",
- ],
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.ShapeShifting,
+ wantedskills: [sdk.skills.Werebear, sdk.skills.Lycanthropy, sdk.skills.Maul],
+ usefulskills: [sdk.skills.HeartofWolverine, sdk.skills.Grizzly, sdk.skills.Shockwave, sdk.skills.PoisonCreeper],
+ precastSkills: [sdk.skills.Werebear, sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
+ usefulStats: [],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 127], ["dexterity", 136], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Werebear, 20, false],
+ [sdk.skills.Lycanthropy, 20, false],
+ [sdk.skills.ShockWave, 1, false],
+ [sdk.skills.Maul, 20, false],
+ [sdk.skills.Grizzly, 20, false],
+ [sdk.skills.HeartofWolverine, 20, false],
+ [sdk.skills.PoisonCreeper, 20, false],
+ [sdk.skills.Shockwave, 20, false],
+ ],
- charms: {
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Poison: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid
- && ((check.getStat(sdk.stats.PoisonLength) * check.getStat(sdk.stats.PoisonMaxDamage)) / 256) >= 141);
- }
- },
+ Poison: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && ((check.getStat(sdk.stats.PoisonLength) * check.getStat(sdk.stats.PoisonMaxDamage)) / 256) >= 141);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShapeShifting) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShapeShifting) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- Config.AttackSkill = [
- sdk.skills.Maul,
- sdk.skills.Maul, sdk.skills.Shockwave,
- sdk.skills.Maul, sdk.skills.Shockwave,
- -1, -1
- ];
- Config.LowManaSkill = [0, 0];
- Config.Wereform = "Werebear";
- Config.SummonAnimal = "Grizzly";
- Config.SummonSpirit = "Heart of Wolverine";
- }
- },
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ Config.AttackSkill = [
+ sdk.skills.Maul,
+ sdk.skills.Maul, sdk.skills.Shockwave,
+ sdk.skills.Maul, sdk.skills.Shockwave,
+ -1, -1
+ ];
+ Config.LowManaSkill = [0, 0];
+ Config.Wereform = "Werebear";
+ Config.SummonAnimal = "Grizzly";
+ Config.SummonSpirit = "Heart of Wolverine";
+ }
+ },
+ },
- respec: function () {
- return me.haveAll([{name: sdk.locale.items.Destruction}, {name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}]);
- },
+ respec: function () {
+ return me.haveAll([{ name: sdk.locale.items.Destruction }, { name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor }]);
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Maul, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Maul, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [
+ // Weapon - Destruction
+ "[name] == phaseblade && [flag] == runeword # [enhanceddamage] >= 350 && [itemdeadlystrike] == 20 && [itemcrushingblow] == 20 # [tier] == 110000",
+ // Shield - Sanctuary
+ "[name] == hyperion # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 70 # [tier] == 110000",
+ "[name] == hyperion # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == tierscore(item, 100000)",
+ // Helmet - Jalal's Mane
+ "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == tierscore(item, 110000)",
+ // Belt - Verdungo's
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 110000)",
+ // Armor - Chains of Honor
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
+ // Gloves - Dracul's Grasp
+ "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 110000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Amulet - Metalgrid
+ "[type] == amulet && [quality] == unique # [tohit] >= 400 && [coldresist] >= 25 # [tier] == tierscore(item, 110000)",
+ // Ring 1 - Ravenfrost
+ "[type] == ring && [quality] == unique # [tohit] >= 180 && [dexterity] >= 15 # [tier] == 100000",
+ // Ring 2 - Carrion Wind
+ "[name] == ring && [quality] == unique # [poisonresist] == 55 && [lifeleech] >= 6 # [tier] == tierscore(item, 110000)",
+ // Merc
+ // Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Temporary Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Final Helm - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Temporary Helm - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ // Final Weapon - Infinity
+ "[type] == polearm && [flag] == runeword # [convictionaura] >= 13 # [merctier] == 100000 + mercscore(item)",
+ // Temporary Weapon - Reaper's Toll
+ "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 50000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.WindBuild.js b/libs/SoloPlay/BuildFiles/druid/druid.WindBuild.js
index 9aadb53d..fb868544 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.WindBuild.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.WindBuild.js
@@ -5,122 +5,136 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Elemental,
- wantedskills: [sdk.skills.Tornado, sdk.skills.Hurricane, sdk.skills.Twister],
- usefulskills: [sdk.skills.CycloneArmor],
- precastSkills: [sdk.skills.CycloneArmor],
- usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["dexterity", 35], ["strength", 156], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Grizzly, 1],
- [sdk.skills.Tornado, 20, false],
- [sdk.skills.Hurricane, 20, false],
- [sdk.skills.CycloneArmor, 20, false],
- [sdk.skills.OakSage, 20, false],
- [sdk.skills.Twister, 20],
- [sdk.skills.Grizzly, 5],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - HotO
- "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
- // Helmet - Jalal's mane
- "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == 110000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Shield - Spirit
- "[name] == monarch && [flag] != ethereal && [flag] == runeword # [fcr] >= 35 # [tier] == 100000",
- // Final Gloves - Perfect 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- // Merc Weapon - Reaper's Toll
- "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 100000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Elemental,
+ wantedskills: [sdk.skills.Tornado, sdk.skills.Hurricane, sdk.skills.Twister],
+ usefulskills: [sdk.skills.CycloneArmor],
+ precastSkills: [sdk.skills.CycloneArmor],
+ usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["dexterity", 35], ["strength", 156], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Grizzly, 1],
+ [sdk.skills.Tornado, 20, false],
+ [sdk.skills.Hurricane, 20, false],
+ [sdk.skills.CycloneArmor, 20, false],
+ [sdk.skills.OakSage, 20, false],
+ [sdk.skills.Twister, 20],
+ [sdk.skills.Grizzly, 5],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Elemental) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Tornado, -1, sdk.skills.Tornado, -1, sdk.skills.ArticBlast, -1];
- Config.LowManaSkill = [-1, -1];
- Config.SummonAnimal = "Grizzly";
- Config.SummonSpirit = "Oak Sage";
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Elemental) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- return Check.haveItem("armor", "runeword", "Enigma");
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.SkipImmune = ["cold and physical"];
+ Config.AttackSkill = [-1, sdk.skills.Tornado, -1, sdk.skills.Tornado, -1, sdk.skills.ArcticBlast, -1];
+ Config.LowManaSkill = [-1, -1];
+ Config.SummonAnimal = "Grizzly";
+ Config.SummonSpirit = "Oak Sage";
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Tornado, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ return me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have;
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Tornado, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - HotO
+ "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
+ // Helmet - Jalal's mane
+ "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == tierscore(item, 110000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Shield - Spirit
+ "[name] == monarch && [flag] != ethereal && [flag] == runeword # [fcr] >= 35 # [tier] == 100000",
+ // Final Gloves - Perfect 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
+ // Gloves - 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ // Merc Weapon - Reaper's Toll
+ "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 100000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.WolfBuild.js b/libs/SoloPlay/BuildFiles/druid/druid.WolfBuild.js
index eeac3e6c..293a40e0 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.WolfBuild.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.WolfBuild.js
@@ -5,114 +5,154 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.ShapeShifting,
- wantedskills: [sdk.skills.Werewolf, sdk.skills.Lycanthropy, sdk.skills.Fury],
- usefulskills: [sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
- precastSkills: [sdk.skills.Werewolf, sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 103], ["dexterity", 35], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Werewolf, 20, true],
- [sdk.skills.Lycanthropy, 20, true],
- [sdk.skills.Fury, 20, true],
- [sdk.skills.Grizzly, 1, false],
- [sdk.skills.HeartofWolverine, 20, true],
- [sdk.skills.FeralRage, 10, false],
- [sdk.skills.Grizzly, 15],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Upp'ed Ribcracker
- "[name] == stalagmite && [quality] == unique # [enhanceddamage] >= 300 && [ias] >= 50 # [tier] == 110000",
- // Helmet - Jalal's Mane
- "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == 110000 + tierscore(item)",
- // Belt - Verdungo's
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 110000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
- // Gloves - Dracul's
- "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 110000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 110000 + tierscore(item)",
- // Final Rings - Perfect Wisp & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == 110000 + tierscore(item)",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000 + tierscore(item)",
- // Rings - Wisp & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == 110000 + tierscore(item)",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 110000 + tierscore(item)",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- // Merc Weapon - Reaper's Toll
- "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 100000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.ShapeShifting,
+ wantedskills: [sdk.skills.Werewolf, sdk.skills.Lycanthropy, sdk.skills.Fury],
+ usefulskills: [sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
+ precastSkills: [sdk.skills.Werewolf, sdk.skills.HeartofWolverine, sdk.skills.Grizzly],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 103], ["dexterity", 35], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Werewolf, 20, true],
+ [sdk.skills.Lycanthropy, 20, true],
+ [sdk.skills.Fury, 20, true],
+ [sdk.skills.Grizzly, 1, false],
+ [sdk.skills.HeartofWolverine, 20, true],
+ [sdk.skills.FeralRage, 10, false],
+ [sdk.skills.Grizzly, 15],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShapeShifting) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Fury, sdk.skills.FeralRage, sdk.skills.Fury, sdk.skills.FeralRage, sdk.skills.Rabies, -1];
- Config.LowManaSkill = [0, 0];
- Config.Wereform = "Werewolf";
- Config.SummonAnimal = "Grizzly";
- Config.SummonSpirit = "Heart of Wolverine";
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.ShapeShifting) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- return Check.haveItem("stalagmite", "unique", "Ribcracker") && Check.haveItem("armor", "runeword", "Chains of Honor");
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Fury, sdk.skills.FeralRage,
+ sdk.skills.Fury, sdk.skills.FeralRage,
+ sdk.skills.Rabies, -1
+ ];
+ Config.LowManaSkill = [0, 0];
+ Config.Wereform = "Werewolf";
+ Config.SummonAnimal = "Grizzly";
+ Config.SummonSpirit = "Heart of Wolverine";
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Werewolf, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ return (
+ me.checkItem({ name: sdk.locale.items.Ribcracker, classid: sdk.items.Stalagmite }).have
+ && me.checkItem({ name: sdk.locale.items.ChainsofHonor, }).have
+ );
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Werewolf, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - Upp'ed Ribcracker
+ "[name] == stalagmite && [quality] == unique # [enhanceddamage] >= 300 && [ias] >= 50 # [tier] == 110000",
+ // Helmet - Jalal's Mane
+ "[name] == totemicmask && [quality] == unique # [druidskills] == 2 && [shapeshiftingskilltab] == 2 # [tier] == tierscore(item, 110000)",
+ // Belt - Verdungo's
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 110000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 110000",
+ // Gloves - Dracul's
+ "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 110000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 110000)",
+ // Final Rings - Perfect Wisp & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == tierscore(item, 110000)",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == tierscore(item, 110000)",
+ // Rings - Wisp & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == tierscore(item, 110000)",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == tierscore(item, 110000)",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ // Merc Weapon - Reaper's Toll
+ "[name] == thresher && [quality] == unique # [enhanceddamage] >= 190 && [lifeleech] >= 11 # [merctier] == 100000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/druid/druid.js b/libs/SoloPlay/BuildFiles/druid/druid.js
index 6053fc15..dca84ec8 100644
--- a/libs/SoloPlay/BuildFiles/druid/druid.js
+++ b/libs/SoloPlay/BuildFiles/druid/druid.js
@@ -6,37 +6,37 @@
*/
const CharInfo = {
- respecOne: 26,
- respecTwo: 0,
- levelCap: (function() {
- const currentDiff = sdk.difficulty.nameOf(me.diff);
- const softcoreMode = {
- "Normal": 33,
- "Nightmare": 73,
- "Hell": 100,
- };
- const hardcoreMode = {
- "Normal": 33,
- "Nightmare": 73,
- "Hell": 100,
- };
+ respecOne: 26,
+ respecTwo: 0,
+ levelCap: (function() {
+ const currentDiff = sdk.difficulty.nameOf(me.diff);
+ const softcoreMode = {
+ "Normal": 33,
+ "Nightmare": 73,
+ "Hell": 100,
+ };
+ const hardcoreMode = {
+ "Normal": 36,
+ "Nightmare": 73,
+ "Hell": 100,
+ };
- return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
- })(),
+ return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
+ })(),
- getActiveBuild: function () {
- const nSkills = me.getStat(sdk.stats.NewSkills);
- const currLevel = me.charlvl;
- const justRepeced = (nSkills >= currLevel);
+ getActiveBuild: function () {
+ const nSkills = me.getStat(sdk.stats.NewSkills);
+ const currLevel = me.charlvl;
+ const justRepeced = (nSkills >= currLevel);
- switch (true) {
- case currLevel < this.respecOne && !me.checkSkill(sdk.skills.Tornado, sdk.skills.subindex.HardPoints):
- return "Start";
- case Check.finalBuild().respec() && justRepeced:
- case Check.finalBuild().active():
- return SetUp.finalBuild;
- default:
- return "Leveling";
- }
- },
+ switch (true) {
+ case currLevel < this.respecOne && !me.checkSkill(sdk.skills.Tornado, sdk.skills.subindex.HardPoints):
+ return "Start";
+ case Check.finalBuild().respec() && justRepeced:
+ case Check.finalBuild().active():
+ return SetUp.finalBuild;
+ default:
+ return "Leveling";
+ }
+ },
};
diff --git a/libs/SoloPlay/BuildFiles/necromancer/necromancer.BoneBuild.js b/libs/SoloPlay/BuildFiles/necromancer/necromancer.BoneBuild.js
index 4a8a1672..c361153f 100644
--- a/libs/SoloPlay/BuildFiles/necromancer/necromancer.BoneBuild.js
+++ b/libs/SoloPlay/BuildFiles/necromancer/necromancer.BoneBuild.js
@@ -5,156 +5,175 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.PoisonandBone,
- wantedskills: [sdk.skills.BoneSpirit, sdk.skills.BoneSpear, sdk.skills.Teeth],
- usefulskills: [sdk.skills.AmplifyDamage, sdk.skills.BoneArmor, sdk.skills.Decrepify, sdk.skills.BoneWall, sdk.skills.BonePrison],
- precastSkills: [sdk.skills.BoneArmor],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 48], ["dexterity", 35], ["vitality", 165],
- ["strength", 61], ["vitality", 252], ["strength", 156], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.BoneSpirit, 1],
- [sdk.skills.BonePrison, 1],
- [sdk.skills.SummonResist, 1],
- [sdk.skills.Decrepify, 1],
- [sdk.skills.Attract, 1],
- [sdk.skills.BoneSpear, 20, false],
- [sdk.skills.BonePrison, 20, false],
- [sdk.skills.BoneWall, 20, false],
- [sdk.skills.BoneSpirit, 20, false],
- [sdk.skills.Teeth, 20, false],
- ],
- classicTiers: [
- // Weapon - Spectral Shard
- "[name] == blade && [quality] == unique # [fcr] == 20 && [allres] == 10 # [tier] == 100000",
- // Helm - Tarnhelm
- "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == 100000 + tierscore(item)",
- // Shield
- "[type] == shield && [quality] >= magic # [necromancerskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Amulet
- "[type] == amulet && [quality] >= magic # [necromancerskills] == 2 && [fcr] == 10 # [tier] == 100000 + tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == 100000 + tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- ],
- expansionTiers: [
- // Weapon
- "([type] == wand || [type] == sword && ([quality] >= magic || [flag] == runeword) || [type] == knife && [quality] >= magic) && [flag] != ethereal # [secondarymindamage] == 0 && [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Helmet - Harlequin's Crest
- "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Shield
- "([type] == shield && ([quality] >= magic || [flag] == runeword) || [type] == voodooheads) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Final Gloves - Perfect 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Rings - Dwarf Star & SoJ
- "[type] == ring && [quality] == unique # [maxhp] >= 40 && [magicdamagereduction] >= 12 # [tier] == 99000",
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.PoisonandBone,
+ wantedskills: [sdk.skills.BoneSpirit, sdk.skills.BoneSpear, sdk.skills.Teeth],
+ usefulskills: [
+ sdk.skills.AmplifyDamage, sdk.skills.BoneArmor,
+ sdk.skills.Decrepify, sdk.skills.BoneWall, sdk.skills.BonePrison
+ ],
+ precastSkills: [sdk.skills.BoneArmor],
+ wantedMerc: MercData[sdk.skills.Might],
+ skills: [
+ [sdk.skills.BoneSpirit, 1],
+ [sdk.skills.BonePrison, 1],
+ [sdk.skills.SummonResist, 1],
+ [sdk.skills.Decrepify, 1],
+ [sdk.skills.Attract, 1],
+ [sdk.skills.BoneSpear, 20, false],
+ [sdk.skills.BonePrison, 20, false],
+ [sdk.skills.BoneWall, 20, false],
+ [sdk.skills.BoneSpirit, 20, false],
+ [sdk.skills.Teeth, 20, false],
+ ],
+ stats: [],
- ResMf: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PoisonandBone) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.BoneSpear, -1, sdk.skills.BoneSpear, -1, -1, -1];
- Config.ExplodeCorpses = sdk.skills.CorpseExplosion;
- Config.Golem = "Clay";
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PoisonandBone) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return Check.haveItem("armor", "runeword", "Enigma");
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.BoneSpear, -1, sdk.skills.BoneSpear, -1, -1, -1];
+ Config.ExplodeCorpses = sdk.skills.CorpseExplosion;
+ Config.Golem = "Clay";
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.BoneSpear, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have;
+ }
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.BoneSpear, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
+ ]
+ : [
+ ["strength", 48], ["dexterity", 35], ["vitality", 165],
+ ["strength", 61], ["vitality", 252], ["strength", 156], ["vitality", "all"]
+ ];
+
+ let finalGear = me.classic
+ ? [
+ // Weapon - Spectral Shard
+ "[name] == blade && [quality] == unique # [fcr] == 20 && [allres] == 10 # [tier] == 100000",
+ // Helm - Tarnhelm
+ "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == tierscore(item, 100000)",
+ // Shield
+ "[type] == shield && [quality] >= magic # [necromancerskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Amulet
+ "[type] == amulet && [quality] >= magic # [necromancerskills] == 2 && [fcr] == 10 # [tier] == tierscore(item, 100000)",
+ // Boots
+ "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == tierscore(item, 100000)",
+ // Belt
+ "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ ] : [
+ // Weapon - White
+ "[type] == wand && [flag] == runeword # [skillbonespear] == 8 && [skillbonespirit] == 6 # [tier] == 100000",
+ // Helmet - Harlequin's Crest
+ "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Shield - Perfect Spirit Monarch
+ "[name] == monarch && [flag] == runeword # [fcr] == 35 && [maxmana] == 112 # [tier] == 100000",
+ // Final Gloves - Perfect 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
+ // Gloves - 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Rings - Dwarf Star & SoJ
+ "[type] == ring && [quality] == unique # [maxhp] >= 40 && [magicdamagereduction] >= 12 # [tier] == 99000",
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ if (me.expansion && me.equipped.get(sdk.body.RightArmSecondary).prefixnum === sdk.locale.items.CalltoArms) {
+ // Switch - Spirit
+ NTIP.addLine("[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000");
+ }
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/necromancer/necromancer.LevelingBuild.js b/libs/SoloPlay/BuildFiles/necromancer/necromancer.LevelingBuild.js
index 7004fa87..58773b7c 100644
--- a/libs/SoloPlay/BuildFiles/necromancer/necromancer.LevelingBuild.js
+++ b/libs/SoloPlay/BuildFiles/necromancer/necromancer.LevelingBuild.js
@@ -7,55 +7,70 @@
// TODO: test summonnovamancer for classic (wouldn't be able to farcast diablo though :( )
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: sdk.skills.tabs.PoisonandBone,
- wantedskills: [sdk.skills.CorpseExplosion, sdk.skills.BoneSpear],
- usefulskills: [sdk.skills.AmplifyDamage, sdk.skills.BoneArmor, sdk.skills.Decrepify, sdk.skills.BoneWall, sdk.skills.BonePrison, sdk.skills.BoneSpirit, sdk.skills.Teeth],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 48], ["vitality", 165], ["strength", 61],
- ["vitality", 252], ["strength", 156], ["vitality", "all"]
- ],
- skills: [
- // Total skills at respec = 29
- [sdk.skills.Decrepify, 1], // points left 25
- [sdk.skills.SummonResist, 1], // points left 22
- [sdk.skills.BonePrison, 1], // points left 16
- [sdk.skills.Attract, 1], // points left 13
- [sdk.skills.BoneSpear, 9], // points left 5
- [sdk.skills.BonePrison, 3], // points left 3
- [sdk.skills.BoneSpear, 20, false],
- [sdk.skills.BoneSpirit, 1, false],
- [sdk.skills.BonePrison, 20, false],
- [sdk.skills.CorpseExplosion, 20, false],
- [sdk.skills.BoneWall, 20, false],
- [sdk.skills.Teeth, 20, false],
- ],
- stats: undefined,
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.PoisonandBone,
+ wantedskills: [sdk.skills.CorpseExplosion, sdk.skills.BoneSpear],
+ usefulskills: [
+ sdk.skills.AmplifyDamage, sdk.skills.BoneArmor,
+ sdk.skills.Decrepify, sdk.skills.BoneWall,
+ sdk.skills.BonePrison, sdk.skills.BoneSpirit, sdk.skills.Teeth
+ ],
+ wantedMerc: MercData[sdk.skills.Might],
+ skills: [
+ // Total skills at respec = 29
+ [sdk.skills.Decrepify, 1], // points left 25
+ [sdk.skills.SummonResist, 1], // points left 22
+ [sdk.skills.BonePrison, 1], // points left 16
+ [sdk.skills.Attract, 1], // points left 13
+ [sdk.skills.BoneSpear, 9], // points left 5
+ [sdk.skills.BonePrison, 3], // points left 3
+ [sdk.skills.BoneSpear, 20, false],
+ [sdk.skills.BoneSpirit, 1, false],
+ [sdk.skills.BonePrison, 20, false],
+ [sdk.skills.CorpseExplosion, 20, false],
+ [sdk.skills.BoneWall, 20, false],
+ [sdk.skills.Teeth, 20, false],
+ ],
+ stats: [],
- active: function () {
- return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.checkSkill(sdk.skills.BonePrison, sdk.skills.subindex.HardPoints) && !Check.finalBuild().active());
- },
-};
+ active: function () {
+ return (me.charlvl > CharInfo.respecOne
+ && me.charlvl > CharInfo.respecTwo
+ && me.checkSkill(sdk.skills.BonePrison, sdk.skills.subindex.HardPoints)
+ && !Check.finalBuild().active());
+ },
-// Has to be set after its loaded
-build.stats = me.classic ? build.classicStats : build.expansionStats;
-
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.AttackSkill = [-1, sdk.skills.BoneSpear, -1, sdk.skills.BoneSpear, -1, -1, -1];
- Config.LowManaSkill = [sdk.skills.Teeth, -1];
- Config.ExplodeCorpses = sdk.skills.CorpseExplosion;
- Config.Golem = "Clay";
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = me.expansion ? 2 : 4;
- Config.MPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
- SetUp.belt();
-});
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.AttackSkill = [-1, sdk.skills.BoneSpear, -1, sdk.skills.BoneSpear, -1, -1, -1];
+ Config.LowManaSkill = [sdk.skills.Teeth, -1];
+ Config.ExplodeCorpses = sdk.skills.CorpseExplosion;
+ Config.Golem = "Clay";
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = me.expansion ? 2 : 4;
+ Config.MPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
+ SetUp.belt();
+ }
+ }
+ },
+ };
+
+ // Has to be set after its loaded
+ build.stats = me.classic
+ ? [
+ ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
+ ] : [
+ ["strength", 48], ["energy", 50],
+ ["vitality", 100], ["strength", 61],
+ ["vitality", 165], ["vitality", 252],
+ ["strength", 156], ["vitality", "all"]
+ ];
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/necromancer/necromancer.PoisonBuild.js b/libs/SoloPlay/BuildFiles/necromancer/necromancer.PoisonBuild.js
index db1c9403..2671835f 100644
--- a/libs/SoloPlay/BuildFiles/necromancer/necromancer.PoisonBuild.js
+++ b/libs/SoloPlay/BuildFiles/necromancer/necromancer.PoisonBuild.js
@@ -5,157 +5,167 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.PoisonandBone,
- wantedskills: [sdk.skills.PoisonNova, sdk.skills.CorpseExplosion],
- usefulskills: [sdk.skills.AmplifyDamage, sdk.skills.BoneArmor, sdk.skills.LowerResist],
- precastSkills: [sdk.skills.BoneArmor],
- usefulStats: [sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 48], ["vitality", 165], ["strength", 61],
- ["vitality", 252], ["strength", 156], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.LowerResist, 5],
- [sdk.skills.SummonResist, 1],
- [sdk.skills.BonePrison, 1],
- [sdk.skills.PoisonNova, 20],
- [sdk.skills.PoisonExplosion, 20],
- [sdk.skills.PoisonDagger, 20],
- [sdk.skills.BonePrison, 10],
- [sdk.skills.BoneSpear, 10],
- [sdk.skills.BonePrison, 20],
- [sdk.skills.BoneSpear, 20],
- ],
- classicTiers: [
- // Weapon - Blackbog's Sharp
- "[name] == cinquedeas && [quality] == unique # [ias] == 30 && [skillpoisonnova] == 4 # [tier] == 100000",
- // Helm - Tarnhelm
- "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == 100000 + tierscore(item)",
- // Shield
- "[type] == shield && [quality] >= magic # [necromancerskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Amulet
- "[type] == amulet && [quality] >= magic # [necromancerskills] == 2 && [fcr] == 10 # [tier] == 100000 + tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == 100000 + tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- ],
- expansionTiers: [
- // Weapon
- "([type] == wand || [type] == sword && ([quality] >= magic || [flag] == runeword) || [type] == knife && [quality] >= magic) && [flag] != ethereal # [secondarymindamage] == 0 && [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Helmet - Harlequin's Crest
- "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Shield
- "([type] == shield && ([quality] >= magic || [flag] == runeword) || [type] == voodooheads) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Final Gloves - Perfect 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)", // Maras
- // Rings - Dwarf Star & SoJ
- "[name] == ring && [quality] == unique # [maxhp] >= 40 && [magicdamagereduction] >= 12 # [tier] == 99000",
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.PoisonandBone,
+ wantedskills: [sdk.skills.PoisonNova, sdk.skills.CorpseExplosion],
+ usefulskills: [sdk.skills.AmplifyDamage, sdk.skills.BoneArmor, sdk.skills.LowerResist],
+ precastSkills: [sdk.skills.BoneArmor],
+ usefulStats: [sdk.stats.PassivePoisonMastery, sdk.stats.PassivePoisonPierce],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ skills: [
+ [sdk.skills.LowerResist, 5],
+ [sdk.skills.SummonResist, 1],
+ [sdk.skills.BonePrison, 1],
+ [sdk.skills.PoisonNova, 20],
+ [sdk.skills.PoisonExplosion, 20],
+ [sdk.skills.PoisonDagger, 20],
+ [sdk.skills.BonePrison, 10],
+ [sdk.skills.BoneSpear, 10],
+ [sdk.skills.BonePrison, 20],
+ [sdk.skills.BoneSpear, 20],
+ ],
+ stats: [],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PoisonandBone) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
-
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.PoisonNova, -1, sdk.skills.PoisonNova, -1, sdk.skills.BoneSpear, -1];
- Config.ExplodeCorpses = sdk.skills.CorpseExplosion;
- Config.Golem = "Clay";
- }
- },
- },
+ ResFHR: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return Check.haveItem("armor", "runeword", "Enigma");
- }
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PoisonandBone) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.PoisonNova, -1, sdk.skills.PoisonNova, -1, sdk.skills.BoneSpear, -1];
+ Config.ExplodeCorpses = sdk.skills.CorpseExplosion;
+ Config.Golem = "Clay";
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.PoisonNova, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have;
+ }
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.PoisonNova, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
+ ]
+ : [
+ ["strength", 48], ["vitality", 165], ["strength", 61],
+ ["vitality", 252], ["strength", 156], ["vitality", "all"]
+ ];
+
+ let finalGear = me.classic
+ ? [
+ // Weapon - Blackbog's Sharp
+ "[name] == cinquedeas && [quality] == unique # [ias] == 30 && [skillpoisonnova] == 4 # [tier] == 100000",
+ // Helm - Tarnhelm
+ "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == tierscore(item, 100000)",
+ // Shield
+ "[type] == shield && [quality] >= magic # [necromancerskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Amulet
+ "[type] == amulet && [quality] >= magic # [necromancerskills] == 2 && [fcr] == 10 # [tier] == tierscore(item, 100000)",
+ // Boots
+ "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == tierscore(item, 100000)",
+ // Belt
+ "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ ] : [
+ // Weapon
+ // "([type] == wand || [type] == sword && ([quality] >= magic || [flag] == runeword) || [type] == knife && [quality] >= magic) && [flag] != ethereal # [secondarymindamage] == 0 && [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Helmet - Harlequin's Crest
+ "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Shield
+ "([type] == shield && ([quality] >= magic || [flag] == runeword) || [type] == voodooheads) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Final Gloves - Perfect 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
+ // Gloves - 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)", // Maras
+ // Rings - Dwarf Star & SoJ
+ "[name] == ring && [quality] == unique # [maxhp] >= 40 && [magicdamagereduction] >= 12 # [tier] == 99000",
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/necromancer/necromancer.StartBuild.js b/libs/SoloPlay/BuildFiles/necromancer/necromancer.StartBuild.js
index 6f1cc864..b73c9ec6 100644
--- a/libs/SoloPlay/BuildFiles/necromancer/necromancer.StartBuild.js
+++ b/libs/SoloPlay/BuildFiles/necromancer/necromancer.StartBuild.js
@@ -5,72 +5,82 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: sdk.skills.tabs.PoisonandBone,
- wantedskills: [sdk.skills.Teeth, sdk.skills.BoneSpear],
- usefulskills: [sdk.skills.AmplifyDamage, sdk.skills.BoneArmor, sdk.skills.Decrepify, sdk.skills.BoneWall],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- stats: [
- ["strength", 20], ["vitality", 70], ["strength", 35],
- ["energy", 85], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Teeth, 4], // charlvl 4
- [sdk.skills.AmplifyDamage, 1], // charlvl 5
- [sdk.skills.ClayGolem, 1], // charlvl 6
- [sdk.skills.BoneArmor, 1], // charlvl 7
- [sdk.skills.Weaken, 1], // charlvl 8
- [sdk.skills.DimVision, 1], // charlvl 9
- [sdk.skills.Teeth, 7], // charlvl 11
- [sdk.skills.GolemMastery, 1], // charlvl 12
- [sdk.skills.IronMaiden, 1], // charlvl 13
- [sdk.skills.CorpseExplosion, 1], // charlvl 14
- [sdk.skills.BoneWall, 3], // charlvl 17
- [sdk.skills.BoneSpear, 6], // charlvl 23
- [sdk.skills.Decrepify, 1], // charlvl 24
- [sdk.skills.BoneSpear, 20], // charlvl -> Until respec at 26
- ],
- active: function () {
- return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.BonePrison, sdk.skills.subindex.HardPoints);
- },
-};
+(function (module, require) {
+ module.exports = (function () {
+ const build = {
+ AutoBuildTemplate: {},
+ caster: true,
+ skillstab: sdk.skills.tabs.PoisonandBone,
+ wantedskills: [sdk.skills.Teeth, sdk.skills.BoneSpear],
+ usefulskills: [sdk.skills.AmplifyDamage, sdk.skills.BoneArmor, sdk.skills.Decrepify, sdk.skills.BoneWall],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [
+ ["strength", 20], ["energy", 45],
+ ["vitality", 70], ["strength", 35],
+ ["energy", 85], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Teeth, 4], // charlvl 4
+ // [sdk.skills.BoneArmor, 1], // charlvl 7
+ [sdk.skills.AmplifyDamage, 1], // charlvl 5
+ [sdk.skills.ClayGolem, 1], // charlvl 6
+ [sdk.skills.BoneArmor, 1], // charlvl 7
+ [sdk.skills.Weaken, 1], // charlvl 8
+ [sdk.skills.DimVision, 1], // charlvl 9
+ [sdk.skills.Teeth, 7], // charlvl 11
+ [sdk.skills.GolemMastery, 1], // charlvl 12
+ [sdk.skills.IronMaiden, 1], // charlvl 13
+ [sdk.skills.CorpseExplosion, 1], // charlvl 14
+ [sdk.skills.BoneWall, 3], // charlvl 17
+ [sdk.skills.BoneSpear, 6], // charlvl 23
+ [sdk.skills.Decrepify, 1], // charlvl 24
+ [sdk.skills.BoneSpear, 20], // charlvl -> Until respec at 26
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
- Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
- Config.FieldID.UsedSpace = 0;
-
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.BeltColumn = ["hp", "hp", "hp", "hp"];
- Config.HPBuffer = 6;
- Config.MPBuffer = 6;
- Config.AttackSkill = [-1, 0, 0, 0, 0, -1, -1];
- Config.LowManaSkill = [0, 0];
- Config.Golem = "Clay";
- SetUp.belt();
-});
-build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
- Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
- Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
- Config.FieldID.UsedSpace = 0;
-
- Config.AttackSkill = [-1, sdk.skills.Teeth, -1, sdk.skills.Teeth, -1, -1, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = 2;
- Config.MPBuffer = 6;
- SetUp.belt();
-});
-build.AutoBuildTemplate[18] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.Teeth, -1, sdk.skills.Teeth, -1, -1, -1];
- Config.ExplodeCorpses = sdk.skills.CorpseExplosion;
+ active: function () {
+ return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.BonePrison, sdk.skills.subindex.HardPoints);
+ },
+ };
- if (me.checkSkill(sdk.skills.BoneSpear, sdk.skills.subindex.HardPoints)) {
- Config.AttackSkill = [-1, sdk.skills.BoneSpear, -1, sdk.skills.BoneSpear, -1, -1, -1];
- Config.LowManaSkill = [sdk.skills.Teeth, -1];
- }
-});
+ const { buildAutoBuildTempObj } = require("../../Utils/General");
+
+ build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
+ Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
+ Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
+ Config.FieldID.UsedSpace = 0;
+
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.BeltColumn = ["hp", "hp", "hp", "hp"];
+ Config.HPBuffer = 6;
+ Config.MPBuffer = 8;
+ Config.AttackSkill = [-1, 0, 0, 0, 0, -1, -1];
+ Config.LowManaSkill = [0, 0];
+ Config.Golem = "Clay";
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
+ Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
+ Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
+ Config.FieldID.UsedSpace = 0;
+
+ Config.AttackSkill = [-1, sdk.skills.Teeth, -1, sdk.skills.Teeth, -1, -1, -1];
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = Storage.BeltSize() < 4 ? 4 : 2;
+ Config.MPBuffer = 8;
+ Config.RejuvBuffer = 6;
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[18] = buildAutoBuildTempObj(() => {
+ Config.AttackSkill = [-1, sdk.skills.Teeth, -1, sdk.skills.Teeth, -1, -1, -1];
+ Config.ExplodeCorpses = sdk.skills.CorpseExplosion;
+
+ if (me.checkSkill(sdk.skills.BoneSpear, sdk.skills.subindex.HardPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.BoneSpear, -1, sdk.skills.BoneSpear, -1, -1, -1];
+ Config.LowManaSkill = [sdk.skills.Teeth, -1];
+ }
+ });
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/necromancer/necromancer.SummonBuild.js b/libs/SoloPlay/BuildFiles/necromancer/necromancer.SummonBuild.js
index a19d58ef..74dd5c2a 100644
--- a/libs/SoloPlay/BuildFiles/necromancer/necromancer.SummonBuild.js
+++ b/libs/SoloPlay/BuildFiles/necromancer/necromancer.SummonBuild.js
@@ -5,170 +5,180 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.NecroSummoning,
- wantedskills: [sdk.skills.RaiseSkeleton, sdk.skills.CorpseExplosion],
- usefulskills: [sdk.skills.AmplifyDamage, sdk.skills.SkeletonMastery, sdk.skills.BoneArmor, sdk.skills.Decrepify],
- precastSkills: [sdk.skills.BoneArmor],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Might",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 48], ["vitality", 165], ["strength", 61],
- ["vitality", 252], ["strength", 156], ["vitality", "all"]
- ],
- classicSkills: [
- [sdk.skills.SummonResist, 1],
- [sdk.skills.BonePrison, 1],
- [sdk.skills.Decrepify, 1],
- [sdk.skills.LowerResist, 1],
- [sdk.skills.RaiseSkeleton, 20, false],
- [sdk.skills.SkeletonMastery, 20, false],
- [sdk.skills.PoisonNova, 20, false],
- [sdk.skills.LowerResist, 5, false],
- [sdk.skills.SummonResist, 5, false],
- [sdk.skills.RaiseSkeletalMage, 20, false],
- [sdk.skills.BonePrison, 20, false],
- ],
- expansionSkills: [
- [sdk.skills.SummonResist, 1],
- [sdk.skills.BonePrison, 1],
- [sdk.skills.Decrepify, 1],
- [sdk.skills.RaiseSkeleton, 20, false],
- [sdk.skills.SkeletonMastery, 20, false],
- [sdk.skills.CorpseExplosion, 20, false],
- [sdk.skills.AmplifyDamage, 20, false],
- [sdk.skills.Revive, 20, false],
- ],
- classicTiers: [
- // Weapon - Blackbog's Sharp
- "[name] == cinquedeas && [quality] == unique # [ias] == 30 && [skillpoisonnova] == 4 # [tier] == 100000",
- // Helm - Tarnhelm
- "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == 100000 + tierscore(item)",
- // Shield
- "[type] == shield && [quality] >= magic # [necromancerskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Amulet
- "[type] == amulet && [quality] >= magic # [necromancerskills] == 2 && [fcr] == 10 # [tier] == 100000 + tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == 100000 + tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- ],
- expansionTiers: [
- // Weapon
- "([type] == wand || [type] == sword && ([quality] >= magic || [flag] == runeword) || [type] == knife && [quality] >= magic) && [flag] != ethereal # [secondarymindamage] == 0 && [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Helmet - Harlequin's Crest
- "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Boots - Marrowalk
- "[name] == boneweaveboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 170 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Shield
- "([type] == shield && ([quality] >= magic || [flag] == runeword) || [type] == voodooheads) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Final Gloves - Perfect 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Final Rings - SoJ & Perfect Raven Frost
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 100000",
- // Rings - Dwarf Star & Raven Frost
- "[name] == ring && [quality] == unique # [maxhp] >= 40 && [magicdamagereduction] >= 12 # [tier] == 99000",
- "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 99000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- skills: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.NecroSummoning,
+ wantedskills: [sdk.skills.RaiseSkeleton, sdk.skills.CorpseExplosion],
+ usefulskills: [sdk.skills.AmplifyDamage, sdk.skills.SkeletonMastery, sdk.skills.BoneArmor, sdk.skills.Decrepify],
+ precastSkills: [sdk.skills.BoneArmor],
+ wantedMerc: MercData[sdk.skills.Might],
+ stats: [],
+ skills: [],
- ResMf: {
- max: 4,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.NecroSummoning) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResMf: {
+ max: 4,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = me.classic
- ? [-1, sdk.skills.PoisonNova, -1, sdk.skills.PoisonNova, -1, sdk.skills.BoneSpear, -1]
- : [-1, sdk.skills.BoneSpear, -1, sdk.skills.BoneSpear, -1, -1, -1];
- Config.LowManaSkill = [0, 0];
- Config.ActiveSummon = true;
- Config.Skeletons = "max";
- Config.SkeletonMages = "max";
- Config.Revives = "max";
- Config.Golem = "Clay";
- Config.MPBuffer = me.expansion ? 4 : 6;
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.NecroSummoning) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return Check.haveItem("armor", "runeword", "Enigma");
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = me.classic
+ ? [-1, sdk.skills.PoisonNova, -1, sdk.skills.PoisonNova, -1, sdk.skills.BoneSpear, -1]
+ : [-1, sdk.skills.BoneSpear, -1, sdk.skills.BoneSpear, -1, -1, -1];
+ Config.LowManaSkill = [0, 0];
+ Config.ActiveSummon = true;
+ Config.Skeletons = "max";
+ Config.SkeletonMages = "max";
+ Config.Revives = "max";
+ Config.Golem = "Clay";
+ Config.MPBuffer = me.expansion ? 4 : 6;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.RaiseSkeleton, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have;
+ }
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.skills = me.classic ? finalBuild.classicSkills : finalBuild.expansionSkills;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.RaiseSkeleton, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
+ ]
+ : [
+ ["strength", 48], ["vitality", 165], ["strength", 61],
+ ["vitality", 252], ["strength", 156], ["vitality", "all"]
+ ];
+
+ build.skills = me.classic
+ ? [
+ [sdk.skills.SummonResist, 1],
+ [sdk.skills.BonePrison, 1],
+ [sdk.skills.Decrepify, 1],
+ [sdk.skills.LowerResist, 1],
+ [sdk.skills.RaiseSkeleton, 20, false],
+ [sdk.skills.SkeletonMastery, 20, false],
+ [sdk.skills.PoisonNova, 20, false],
+ [sdk.skills.LowerResist, 5, false],
+ [sdk.skills.SummonResist, 5, false],
+ [sdk.skills.RaiseSkeletalMage, 20, false],
+ [sdk.skills.BonePrison, 20, false],
+ ]
+ : [
+ [sdk.skills.SummonResist, 1],
+ [sdk.skills.BonePrison, 1],
+ [sdk.skills.Decrepify, 1],
+ [sdk.skills.RaiseSkeleton, 20, false],
+ [sdk.skills.SkeletonMastery, 20, false],
+ [sdk.skills.CorpseExplosion, 20, false],
+ [sdk.skills.AmplifyDamage, 20, false],
+ [sdk.skills.Revive, 20, false],
+ ];
+
+ let finalGear = me.classic
+ ? [
+ // Weapon - Blackbog's Sharp
+ "[name] == cinquedeas && [quality] == unique # [ias] == 30 && [skillpoisonnova] == 4 # [tier] == 100000",
+ // Helm - Tarnhelm
+ "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == tierscore(item, 100000)",
+ // Shield
+ "[type] == shield && [quality] >= magic # [necromancerskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Amulet
+ "[type] == amulet && [quality] >= magic # [necromancerskills] == 2 && [fcr] == 10 # [tier] == tierscore(item, 100000)",
+ // Boots
+ "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == tierscore(item, 100000)",
+ // Belt
+ "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ ] : [
+ // Weapon
+ // "([type] == wand || [type] == sword && ([quality] >= magic || [flag] == runeword) || [type] == knife && [quality] >= magic) && [flag] != ethereal # [secondarymindamage] == 0 && [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Helmet - Harlequin's Crest
+ "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Boots - Marrowalk
+ "[name] == boneweaveboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 170 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Shield
+ "([type] == shield && ([quality] >= magic || [flag] == runeword) || [type] == voodooheads) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Final Gloves - Perfect 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
+ // Gloves - 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Final Rings - SoJ & Perfect Raven Frost
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 100000",
+ // Rings - Dwarf Star & Raven Frost
+ "[name] == ring && [quality] == unique # [maxhp] >= 40 && [magicdamagereduction] >= 12 # [tier] == 99000",
+ "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 99000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/necromancer/necromancer.js b/libs/SoloPlay/BuildFiles/necromancer/necromancer.js
index e637a114..a5256360 100644
--- a/libs/SoloPlay/BuildFiles/necromancer/necromancer.js
+++ b/libs/SoloPlay/BuildFiles/necromancer/necromancer.js
@@ -6,37 +6,37 @@
*/
const CharInfo = {
- respecOne: 26,
- respecTwo: 0,
- levelCap: (function() {
- const currentDiff = sdk.difficulty.nameOf(me.diff);
- const softcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 70 : 70,
- "Hell": 100,
- };
- const hardcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 70 : 70,
- "Hell": 100,
- };
+ respecOne: 26,
+ respecTwo: 0,
+ levelCap: (function() {
+ const currentDiff = sdk.difficulty.nameOf(me.diff);
+ const softcoreMode = {
+ "Normal": me.expansion ? 33 : 33,
+ "Nightmare": me.expansion ? 70 : 70,
+ "Hell": 100,
+ };
+ const hardcoreMode = {
+ "Normal": me.expansion ? 36 : 33,
+ "Nightmare": me.expansion ? 71 : 70,
+ "Hell": 100,
+ };
- return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
- })(),
+ return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
+ })(),
- getActiveBuild: function () {
- const nSkills = me.getStat(sdk.stats.NewSkills);
- const currLevel = me.charlvl;
- const justRepeced = (nSkills >= currLevel);
+ getActiveBuild: function () {
+ const nSkills = me.getStat(sdk.stats.NewSkills);
+ const currLevel = me.charlvl;
+ const justRepeced = (nSkills >= currLevel);
- switch (true) {
- case currLevel < this.respecOne && !me.checkSkill(sdk.skills.BonePrison, sdk.skills.subindex.HardPoints):
- return "Start";
- case Check.finalBuild().respec() && justRepeced:
- case Check.finalBuild().active():
- return SetUp.finalBuild;
- default:
- return "Leveling";
- }
- },
+ switch (true) {
+ case currLevel < this.respecOne && !me.checkSkill(sdk.skills.BonePrison, sdk.skills.subindex.HardPoints):
+ return "Start";
+ case Check.finalBuild().respec() && justRepeced:
+ case Check.finalBuild().active():
+ return SetUp.finalBuild;
+ default:
+ return "Leveling";
+ }
+ },
};
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.AuradinBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.AuradinBuild.js
index e2910724..47601b81 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.AuradinBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.AuradinBuild.js
@@ -5,125 +5,168 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.Zeal, sdk.skills.Conviction],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.ResistFire, sdk.skills.ResistLightning],
- precastSkills: [sdk.skills.HolyShield],
- usefulStats: [sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce, sdk.stats.PierceLtng, sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce, sdk.stats.PierceFire],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 103], ["dexterity", 136], ["vitality", 300], ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Conviction, 20],
- [sdk.skills.Zeal, 4],
- [sdk.skills.ResistLightning, 20],
- [sdk.skills.Salvation, 20],
- [sdk.skills.ResistFire, 20],
- [sdk.skills.Redemption, 1],
- [sdk.skills.HolyShield, 15],
- [sdk.skills.Zeal, 10],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - HoJ
- "[type] == sword && [flag] == runeword # [holyfireaura] >= 16 # [tier] == 120000",
- // Weapon - Crescent Moon & Voice of Reason
- "[type] == sword && [flag] == runeword # [ias] >= 20 && [passiveltngpierce] >= 35 # [tier] == 110000",
- "[type] == sword && [flag] == runeword # [passivecoldpierce] >= 24 # [tier] == 102500",
- // Helm - Dream
- "[type] == helm && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
- // Belt - TGods
- "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Armor - Dragon
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [holyfireaura] >= 14 # [tier] == 110000",
- // Shield - Dream
- "[type] == auricshields && [flag] != ethereal && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
- // Gloves - Laying of Hand's
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.Zeal, sdk.skills.Conviction],
+ usefulskills: [sdk.skills.HolyShield, sdk.skills.ResistFire, sdk.skills.ResistLightning],
+ precastSkills: [sdk.skills.HolyShield],
+ usefulStats: [
+ sdk.stats.PassiveLightningMastery,
+ sdk.stats.PassiveLightningPierce,
+ sdk.stats.PierceLtng,
+ sdk.stats.PassiveFireMastery,
+ sdk.stats.PassiveFirePierce,
+ sdk.stats.PierceFire
+ ],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 103], ["dexterity", 136], ["vitality", 300], ["dexterity", "block"], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Conviction, 20],
+ [sdk.skills.Zeal, 4],
+ [sdk.skills.ResistLightning, 20],
+ [sdk.skills.Salvation, 20],
+ [sdk.skills.ResistFire, 20],
+ [sdk.skills.Redemption, 1],
+ [sdk.skills.HolyShield, 15],
+ [sdk.skills.Zeal, 10],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.Vigor = false;
- Config.AttackSkill = [-1, sdk.skills.Zeal, sdk.skills.Conviction, sdk.skills.Zeal, sdk.skills.Conviction, -1, -1];
- Config.LowManaSkill = [-1, -1];
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- if (!me.haveSome([{name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}, {name: sdk.locale.items.HandofJustice}])) {
- Config.SkipImmune = ["lightning and physical"];
- } else {
- Config.SkipImmune = ["lightning and fire and physical"]; // Don't think this ever happens but should skip if it does
- }
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.Vigor = false;
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Zeal, sdk.skills.Conviction,
+ sdk.skills.Zeal, sdk.skills.Conviction,
+ -1, -1
+ ];
+ Config.LowManaSkill = [-1, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- SetUp.belt();
- }
- },
- },
+ if (!me.haveSome([
+ { name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor },
+ { name: sdk.locale.items.HandofJustice }
+ ])) {
+ Config.SkipImmune = ["lightning and physical"];
+ } else {
+ Config.SkipImmune = ["lightning and fire and physical"]; // Don't think this ever happens but should skip if it does
+ }
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return me.haveAll([{name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}, {name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}]);
- }
- },
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ SetUp.belt();
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Conviction, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return me.haveAll([
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm }
+ ]);
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Conviction, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Final Weapon - HoJ
+ "[type] == sword && [flag] == runeword # [holyfireaura] >= 16 # [tier] == 120000",
+ // Weapon - Crescent Moon & Voice of Reason
+ "[type] == sword && [flag] == runeword # [ias] >= 20 && [passiveltngpierce] >= 35 # [tier] == 110000",
+ "[type] == sword && [flag] == runeword # [passivecoldpierce] >= 24 # [tier] == 102500",
+ // Helm - Dream
+ "[type] == helm && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
+ // Belt - TGods
+ "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Armor - Dragon
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [holyfireaura] >= 14 # [tier] == 110000",
+ // Shield - Dream
+ "[type] == auricshields && [flag] != ethereal && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
+ // Gloves - Laying of Hand's
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.ClassicauradinBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.ClassicauradinBuild.js
index 7c86d559..2690bbe9 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.ClassicauradinBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.ClassicauradinBuild.js
@@ -5,148 +5,178 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.Zeal, sdk.skills.HolyShock],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.HolyFreeze, sdk.skills.ResistCold, sdk.skills.ResistLightning],
- precastSkills: [sdk.skills.HolyShield],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["strength", 80], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 103], ["dexterity", 136], ["vitality", 300],
- ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Zeal, 4],
- [sdk.skills.Redemption, 1],
- [sdk.skills.HolyShield, 1],
- [sdk.skills.HolyShock, 20],
- [sdk.skills.HolyFreeze, 20],
- [sdk.skills.Salvation, 20, false],
- [sdk.skills.ResistLightning, 20, false],
- [sdk.skills.Zeal, 20, false],
- ],
- classicTiers: [
- // Weapon
- "[name] == warscepter && [quality] >= magic # [paladinskills] == 2 && [ias] == 40 && [skillholyshock] >= 1 # [tier] == 100000 + tierscore(item)",
- // Helm - Tarnhelm
- "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == 100000 + tierscore(item)",
- // Shield
- "[type] == shield && [quality] >= magic # [paladinskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Amulet
- "[type] == amulet && [quality] >= magic # [paladinskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Boots - Hsaru's Iron Heel
- "[name] == chainboots && [quality] == set # [frw] == 20 # [tier] == 100000",
- // Belt - Hsaru's Iron Stay
- "[name] == belt && [quality] == set # [coldresist] == 20 && [maxhp] == 20 # [tier] == 100000",
- ],
- // idea: since this is based on the classic build, could do tri-element instead of just normal auradin
- // max Holy Shock/Freeze then use HoJ + Dragon Armor/Shield for level 44 Holy Fire
- // could even then do infinity for the merc to still have some conviction
- expansionTiers: [
- // Final Weapon - HoJ
- "[type] == sword && [flag] == runeword # [holyfireaura] >= 16 # [tier] == 120000",
- // Weapon - Crescent Moon
- "[type] == sword && [flag] == runeword # [ias] >= 20 && [passiveltngpierce] >= 35 # [tier] == 110000",
- // Weapon - Voice of Reason
- "[type] == sword && [flag] == runeword # [passivecoldpierce] >= 24 # [tier] == 102500",
- // Helm - Dream
- "[type] == helm && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
- // Belt - TGods
- "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Armor - Dragon
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [holyfireaura] >= 14 # [tier] == 110000",
- // Shield - Dream
- "[type] == auricshields && [flag] != ethereal && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
- // Gloves - Laying of Hand's
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ // idea: since this is based on the classic build, could do tri-element instead of just normal auradin
+ // max Holy Shock/Freeze then use HoJ + Dragon Armor/Shield for level 44 Holy Fire
+ // could even then do infinity for the merc to still have some conviction
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.Zeal, sdk.skills.HolyShock],
+ usefulskills: [sdk.skills.HolyShield, sdk.skills.HolyFreeze, sdk.skills.ResistCold, sdk.skills.ResistLightning],
+ precastSkills: [sdk.skills.HolyShield],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ skills: [
+ [sdk.skills.Zeal, 4],
+ [sdk.skills.Redemption, 1],
+ [sdk.skills.HolyShield, 1],
+ [sdk.skills.HolyShock, 20],
+ [sdk.skills.HolyFreeze, 20],
+ [sdk.skills.Salvation, 20, false],
+ [sdk.skills.ResistLightning, 20, false],
+ [sdk.skills.Zeal, 20, false],
+ ],
+ stats: [],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
-
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.Vigor = false;
- Config.AttackSkill = [-1, sdk.skills.Zeal, sdk.skills.HolyShock, sdk.skills.Zeal, sdk.skills.HolyShock, sdk.skills.Zeal, sdk.skills.HolyFreeze];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["lightning and cold and physical"]; // Don't think this ever happens but should skip if it does
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.Vigor = false;
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Zeal, sdk.skills.HolyShock,
+ sdk.skills.Zeal, sdk.skills.HolyShock,
+ sdk.skills.Zeal, sdk.skills.HolyFreeze
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["lightning and cold and physical"]; // Don't think this ever happens but should skip if it does
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.haveAll([{name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}, {name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}]);
- }
- },
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.HolyShock, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ }
+ return me.haveAll([
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm }
+ ]);
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.HolyShock, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["strength", 80], ["vitality", "all"]
+ ]
+ : [
+ ["strength", 103], ["dexterity", 136], ["vitality", 300],
+ ["dexterity", "block"], ["vitality", "all"]
+ ];
+
+ let finalGear = me.classic
+ ? [
+ // Weapon
+ "[name] == warscepter && [quality] >= magic # [paladinskills] == 2 && [ias] == 40 && [skillholyshock] >= 1 # [tier] == tierscore(item, 100000)",
+ // Helm - Tarnhelm
+ "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == tierscore(item, 100000)",
+ // Shield
+ "[type] == shield && [quality] >= magic # [paladinskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Amulet
+ "[type] == amulet && [quality] >= magic # [paladinskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Boots - Hsaru's Iron Heel
+ "[name] == chainboots && [quality] == set # [frw] == 20 # [tier] == 100000",
+ // Belt - Hsaru's Iron Stay
+ "[name] == belt && [quality] == set # [coldresist] == 20 && [maxhp] == 20 # [tier] == 100000",
+ ] : [
+ // Final Weapon - HoJ
+ "[type] == sword && [flag] == runeword # [holyfireaura] >= 16 # [tier] == 120000",
+ // Weapon - Crescent Moon
+ "[type] == sword && [flag] == runeword # [ias] >= 20 && [passiveltngpierce] >= 35 # [tier] == 110000",
+ // Weapon - Voice of Reason
+ "[type] == sword && [flag] == runeword # [passivecoldpierce] >= 24 # [tier] == 102500",
+ // Helm - Dream
+ "[type] == helm && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
+ // Belt - TGods
+ "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Armor - Dragon
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [holyfireaura] >= 14 # [tier] == 110000",
+ // Shield - Dream
+ "[type] == auricshields && [flag] != ethereal && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
+ // Gloves - Laying of Hand's
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.HammerdinBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.HammerdinBuild.js
index 7daaf285..528deff0 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.HammerdinBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.HammerdinBuild.js
@@ -6,160 +6,180 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.BlessedHammer, sdk.skills.Concentration],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.BlessedAim],
- precastSkills: [sdk.skills.HolyShield],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["vitality", "all"]
- ],
- expansionStats: [
- ["vitality", 60], ["dexterity", 30], ["strength", 27],
- ["vitality", 91], ["dexterity", 44], ["strength", 30],
- ["vitality", 96], ["dexterity", 59], ["strength", 60],
- ["vitality", 109], ["dexterity", 77], ["strength", 89],
- ["vitality", 137], ["dexterity", 89], ["strength", 103],
- ["vitality", 173], ["dexterity", 103],
- ["vitality", 208], ["dexterity", 118],
- ["vitality", 243], ["dexterity", 133],
- ["vitality", 279], ["dexterity", 147],
- ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.HolyShield, 1],
- [sdk.skills.Meditation, 1],
- [sdk.skills.Redemption, 1],
- [sdk.skills.BlessedHammer, 20],
- [sdk.skills.Concentration, 20],
- [sdk.skills.Vigor, 20],
- [sdk.skills.BlessedAim, 20],
- [sdk.skills.HolyShield, 20]
- ],
- classicTiers: [
- // Weapon - Spectral Shard
- "[name] == blade && [quality] == unique # [fcr] == 20 && [allres] == 10 # [tier] == 100000",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- ],
- expansionTiers: [
- // Weapon - HotO
- "[type] == mace && [flag] == runeword # [fcr] == 40 # [tier] == 100000",
- // Helm - Harlequin's Crest
- "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Final Shield - Spirit
- "[type] == auricshields && [flag] == runeword # [fcr] == 35 && [maxmana] == 112 && [coldresist] == 80 # [tier] == 110000",
- // Shield - Spirit
- "[type] == auricshields && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 && [coldresist] == 80 # [tier] == 100000 + tierscore(item)",
- // Shield - HoZ
- "[name] == gildedshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 150 # [tier] == 50000 + tierscore(item)",
- // Final Gloves - Perfect Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Final Rings - SoJ & Perfect Raven Frost
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 100000",
- // Rings - Dwarf Star & Raven Frost
- "[name] == ring && [quality] == unique # [maxhp] >= 40 && [magicdamagereduction] >= 12 # [tier] == 99000",
- "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 99000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.BlessedHammer, sdk.skills.Concentration],
+ usefulskills: [sdk.skills.HolyShield, sdk.skills.BlessedAim],
+ precastSkills: [sdk.skills.HolyShield],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ skills: [
+ [sdk.skills.HolyShield, 1],
+ [sdk.skills.Meditation, 1],
+ [sdk.skills.Redemption, 1],
+ [sdk.skills.BlessedHammer, 20],
+ [sdk.skills.Concentration, 20],
+ [sdk.skills.Vigor, 20],
+ [sdk.skills.BlessedAim, 20],
+ [sdk.skills.HolyShield, 20]
+ ],
+ stats: [],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PalaCombat) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
-
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.BlessedHammer, sdk.skills.Concentration, sdk.skills.BlessedHammer, sdk.skills.Concentration, sdk.skills.HolyBolt, sdk.skills.Concentration];
- Config.LowManaSkill = [0, sdk.skills.Concentration];
+ ResFHR: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- if (me.hell && !Pather.accessToAct(5)) {
- Config.SkipImmune = ["magic"];
- }
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- SetUp.belt();
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PalaCombat) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.BlessedHammer, sdk.skills.Concentration,
+ sdk.skills.BlessedHammer, sdk.skills.Concentration,
+ sdk.skills.HolyBolt, sdk.skills.Concentration
+ ];
+ Config.LowManaSkill = [0, sdk.skills.Concentration];
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return Check.haveItem("armor", "runeword", "Enigma");
- }
- },
+ if (me.hell && !me.accessToAct(5)) {
+ Config.SkipImmune = ["magic"];
+ }
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ SetUp.belt();
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.BlessedHammer, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have;
+ }
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.BlessedHammer, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["dexterity", 51], ["strength", 80], ["vitality", "all"]
+ ]
+ : [
+ ["vitality", 60], ["dexterity", 30], ["strength", 27],
+ ["vitality", 91], ["dexterity", 44], ["strength", 30],
+ ["vitality", 96], ["dexterity", 59], ["strength", 60],
+ ["vitality", 109], ["dexterity", 77], ["strength", 89],
+ ["vitality", 137], ["dexterity", 89], ["strength", 103],
+ ["vitality", 173], ["dexterity", 103],
+ ["vitality", 208], ["dexterity", 118],
+ ["vitality", 243], ["dexterity", 133],
+ ["vitality", 279], ["dexterity", 147],
+ ["vitality", "all"]
+ ];
+
+ let finalGear = me.classic
+ ? [
+ // Weapon - Spectral Shard
+ "[name] == blade && [quality] == unique # [fcr] == 20 && [allres] == 10 # [tier] == 100000",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ ]
+ : [
+ // Weapon - HotO
+ "[type] == mace && [flag] == runeword # [fcr] == 40 # [tier] == 100000",
+ // Helm - Harlequin's Crest
+ "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Final Shield - Spirit
+ "[type] == auricshields && [flag] == runeword # [fcr] == 35 && [maxmana] == 112 && [coldresist] == 80 # [tier] == 110000",
+ // Shield - Spirit
+ "[type] == auricshields && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 && [coldresist] == 80 # [tier] == tierscore(item, 100000)",
+ // Shield - HoZ
+ "[name] == gildedshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 150 # [tier] == tierscore(item, 50000)",
+ // Final Gloves - Perfect Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 && [defense] == 71 # [tier] == 110000",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Final Rings - SoJ & Perfect Raven Frost
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 100000",
+ // Rings - Dwarf Star & Raven Frost
+ "[name] == ring && [quality] == unique # [maxhp] >= 40 && [magicdamagereduction] >= 12 # [tier] == 99000",
+ "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 99000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.HammershockBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.HammershockBuild.js
index 43369425..a01dd032 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.HammershockBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.HammershockBuild.js
@@ -5,147 +5,189 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.BlessedHammer, sdk.skills.HolyShock],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.ResistLightning, sdk.skills.Zeal, sdk.skills.Concentration, sdk.skills.Vigor, sdk.skills.BlessedAim],
- precastSkills: [sdk.skills.HolyShield],
- usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["strength", 80], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 103], ["dexterity", 136], ["vitality", 300],
- ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Zeal, 4],
- [sdk.skills.Vengeance, 1],
- [sdk.skills.Redemption, 1],
- [sdk.skills.Salvation, 1],
- [sdk.skills.HolyShield, 1],
- [sdk.skills.Concentration, 1],
- [sdk.skills.BlessedHammer, 20],
- [sdk.skills.HolyShock, 20],
- [sdk.skills.BlessedAim, 20, false],
- [sdk.skills.ResistLightning, 20, false],
- [sdk.skills.Zeal, 20, false],
- ],
- classicTiers: [
- // Weapon
- "[name] == warscepter && [quality] >= magic # [paladinskills] == 2 && [ias] == 40 && [skillholyshock] >= 1 # [tier] == 100000 + tierscore(item)",
- // Helm - Tarnhelm
- "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == 100000 + tierscore(item)",
- // Shield
- "[type] == shield && [quality] >= magic # [paladinskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Amulet
- "[type] == amulet && [quality] >= magic # [paladinskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Boots - Hsaru's Iron Heel
- "[name] == chainboots && [quality] == set # [frw] == 20 # [tier] == 100000",
- // Belt - Hsaru's Iron Stay
- "[name] == belt && [quality] == set # [coldresist] == 20 && [maxhp] == 20 # [tier] == 100000",
- ],
- expansionTiers: [
- // Weapon - Heaven's Light
- "[type] == scepter && [quality] == unique && [flag] != ethereal # [paladinskills] >= 2 && [enhanceddamage] >= 250 # [tier] == 100000 + tierscore(item)",
- // Helm - Harlequin's Crest
- "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == 100000 + tierscore(item)",
- // Belt - TGods
- "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Final Shield - Exile
- "[type] == auricshields && [flag] == runeword # [defianceaura] >= 13 # [tier] == 110000",
- // Shield - HoZ
- "[name] == gildedshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 150 # [tier] == 50000 + tierscore(item)",
- // Gloves - Laying of Hand's
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.BlessedHammer, sdk.skills.HolyShock],
+ usefulskills: [
+ sdk.skills.HolyShield,
+ sdk.skills.ResistLightning,
+ sdk.skills.Zeal,
+ sdk.skills.Concentration,
+ sdk.skills.Vigor,
+ sdk.skills.BlessedAim
+ ],
+ precastSkills: [sdk.skills.HolyShield],
+ usefulStats: [sdk.stats.PierceLtng, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ skills: [
+ [sdk.skills.Zeal, 4],
+ [sdk.skills.Vengeance, 1],
+ [sdk.skills.Redemption, 1],
+ [sdk.skills.Salvation, 1],
+ [sdk.skills.HolyShield, 1],
+ [sdk.skills.Concentration, 1],
+ [sdk.skills.BlessedHammer, 20],
+ [sdk.skills.HolyShock, 20],
+ [sdk.skills.BlessedAim, 20, false],
+ [sdk.skills.ResistLightning, 20, false],
+ [sdk.skills.Zeal, 20, false],
+ ],
+ stats: [],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.Vigor = false;
- Config.AttackSkill = [-1, sdk.skills.BlessedHammer, sdk.skills.Concentration, sdk.skills.BlessedHammer, sdk.skills.Concentration, sdk.skills.Zeal, sdk.skills.HolyShock];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["magic and lightning and physical"]; // Don't think this ever happens but should skip if it does
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- SetUp.belt();
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return Check.haveItem("scepter", "unique", "Heaven's Light") && Check.haveItem("armor", "runeword", "Enigma");
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.Vigor = false;
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.BlessedHammer, sdk.skills.Concentration,
+ sdk.skills.BlessedHammer, sdk.skills.Concentration,
+ sdk.skills.Zeal, sdk.skills.HolyShock
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["magic and lightning and physical"]; // Don't think this ever happens but should skip if it does
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ SetUp.belt();
+ }
+ },
+ },
- active: function () {
- return this.respec() && (me.getSkill(sdk.skills.HolyShock, sdk.skills.subindex.HardPoints) === 20 && me.getSkill(sdk.skills.BlessedHammer, sdk.skills.subindex.HardPoints) === 20);
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ }
+ return (
+ me.checkItem({ name: sdk.locale.items.HeavensLight }).have
+ && me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have
+ );
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
+ active: function () {
+ return (
+ this.respec()
+ && me.getSkill(sdk.skills.HolyShock, sdk.skills.subindex.HardPoints) === 20
+ && me.getSkill(sdk.skills.BlessedHammer, sdk.skills.subindex.HardPoints) === 20
+ );
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["strength", 80], ["vitality", "all"]
+ ]
+ : [
+ ["strength", 103], ["dexterity", 136], ["vitality", 300],
+ ["dexterity", "block"], ["vitality", "all"]
+ ];
+
+ let finalGear = me.classic
+ ? [
+ // Weapon
+ "[name] == warscepter && [quality] >= magic # [paladinskills] == 2 && [ias] == 40 && [skillholyshock] >= 1 # [tier] == tierscore(item, 100000)",
+ // Helm - Tarnhelm
+ "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == tierscore(item, 100000)",
+ // Shield
+ "[type] == shield && [quality] >= magic # [paladinskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Amulet
+ "[type] == amulet && [quality] >= magic # [paladinskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Boots - Hsaru's Iron Heel
+ "[name] == chainboots && [quality] == set # [frw] == 20 # [tier] == 100000",
+ // Belt - Hsaru's Iron Stay
+ "[name] == belt && [quality] == set # [coldresist] == 20 && [maxhp] == 20 # [tier] == 100000",
+ ]
+ : [
+ // Weapon - Heaven's Light
+ "[type] == scepter && [quality] == unique && [flag] != ethereal # [paladinskills] >= 2 && [enhanceddamage] >= 250 # [tier] == tierscore(item, 100000)",
+ // Helm - Harlequin's Crest
+ "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] == 10 # [tier] == tierscore(item, 100000)",
+ // Belt - TGods
+ "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Final Shield - Exile
+ "[type] == auricshields && [flag] == runeword # [defianceaura] >= 13 # [tier] == 110000",
+ // Shield - HoZ
+ "[name] == gildedshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 150 # [tier] == tierscore(item, 50000)",
+ // Gloves - Laying of Hand's
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.LevelingBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.LevelingBuild.js
index 43396fdb..8deefa42 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.LevelingBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.LevelingBuild.js
@@ -6,109 +6,119 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.BlessedHammer, sdk.skills.Concentration],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.BlessedAim],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["vitality", "all"]
- ],
- expansionStats: [
- ["vitality", 60], ["dexterity", 30], ["strength", 27],
- ["vitality", 91], ["dexterity", 44], ["strength", 30],
- ["vitality", 96], ["dexterity", 59], ["strength", 60],
- ["vitality", 109], ["dexterity", 77], ["strength", 89],
- ["vitality", 137], ["dexterity", 89], ["strength", 103],
- ["vitality", 173], ["dexterity", 103],
- ["vitality", 208], ["dexterity", 118],
- ["vitality", 243], ["dexterity", 133],
- ["vitality", 279], ["dexterity", 147],
- ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Might, 1],
- [sdk.skills.Smite, 1],
- [sdk.skills.Prayer, 1],
- [sdk.skills.HolyBolt, 1],
- [sdk.skills.Defiance, 1],
- [sdk.skills.Charge, 1],
- [sdk.skills.BlessedAim, 1],
- [sdk.skills.Cleansing, 1],
- [sdk.skills.BlessedAim, 6],
- [sdk.skills.BlessedHammer, 1],
- [sdk.skills.Concentration, 1],
- [sdk.skills.Vigor, 1],
- [sdk.skills.BlessedAim, 7],
- [sdk.skills.BlessedHammer, 2],
- [sdk.skills.Concentration, 2],
- [sdk.skills.Vigor, 2],
- [sdk.skills.BlessedHammer, 7],
- [sdk.skills.HolyShield, 1],
- [sdk.skills.Meditation, 1],
- [sdk.skills.BlessedHammer, 12],
- [sdk.skills.Redemption, 1],
- [sdk.skills.BlessedHammer, 20],
- [sdk.skills.Concentration, 3],
- [sdk.skills.Vigor, 3],
- [sdk.skills.Concentration, 4],
- [sdk.skills.Vigor, 4],
- [sdk.skills.Concentration, 5],
- [sdk.skills.Vigor, 5],
- [sdk.skills.Concentration, 6],
- [sdk.skills.Vigor, 6],
- [sdk.skills.Concentration, 7],
- [sdk.skills.Vigor, 7],
- [sdk.skills.Concentration, 8],
- [sdk.skills.Vigor, 8],
- [sdk.skills.Concentration, 9],
- [sdk.skills.Vigor, 9],
- [sdk.skills.Concentration, 10],
- [sdk.skills.Vigor, 10],
- [sdk.skills.Concentration, 11],
- [sdk.skills.Vigor, 11],
- [sdk.skills.Concentration, 12],
- [sdk.skills.Vigor, 12],
- [sdk.skills.Concentration, 13],
- [sdk.skills.Vigor, 13],
- [sdk.skills.Concentration, 14],
- [sdk.skills.Vigor, 14],
- [sdk.skills.Concentration, 15],
- [sdk.skills.Vigor, 15],
- [sdk.skills.Concentration, 16],
- [sdk.skills.Vigor, 16],
- [sdk.skills.Concentration, 17],
- [sdk.skills.Vigor, 17],
- [sdk.skills.Concentration, 18],
- [sdk.skills.Vigor, 18],
- [sdk.skills.Concentration, 19],
- [sdk.skills.Vigor, 19],
- [sdk.skills.Concentration, 20],
- [sdk.skills.Vigor, 20],
- [sdk.skills.BlessedAim, 20],
- [sdk.skills.HolyShield, 20]
- ],
- stats: undefined,
- active: function () {
- return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.checkSkill(sdk.skills.Concentration, sdk.skills.subindex.HardPoints) && !Check.finalBuild().active());
- },
-};
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.BlessedHammer, sdk.skills.Concentration],
+ usefulskills: [sdk.skills.HolyShield, sdk.skills.BlessedAim],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [],
+ skills: [
+ [sdk.skills.Might, 1],
+ [sdk.skills.Smite, 1],
+ [sdk.skills.Prayer, 1],
+ [sdk.skills.HolyBolt, 1],
+ [sdk.skills.Defiance, 1],
+ [sdk.skills.Charge, 1],
+ [sdk.skills.BlessedAim, 1],
+ [sdk.skills.Cleansing, 1],
+ [sdk.skills.BlessedAim, 6],
+ [sdk.skills.BlessedHammer, 1],
+ [sdk.skills.Concentration, 1],
+ [sdk.skills.Vigor, 1],
+ [sdk.skills.BlessedAim, 7],
+ [sdk.skills.BlessedHammer, 2],
+ [sdk.skills.Concentration, 2],
+ [sdk.skills.Vigor, 2],
+ [sdk.skills.BlessedHammer, 7],
+ [sdk.skills.HolyShield, 1],
+ [sdk.skills.Meditation, 1],
+ [sdk.skills.BlessedHammer, 12],
+ [sdk.skills.Redemption, 1],
+ [sdk.skills.BlessedHammer, 20],
+ [sdk.skills.Concentration, 3],
+ [sdk.skills.Vigor, 3],
+ [sdk.skills.Concentration, 4],
+ [sdk.skills.Vigor, 4],
+ [sdk.skills.Concentration, 5],
+ [sdk.skills.Vigor, 5],
+ [sdk.skills.Concentration, 6],
+ [sdk.skills.Vigor, 6],
+ [sdk.skills.Concentration, 7],
+ [sdk.skills.Vigor, 7],
+ [sdk.skills.Concentration, 8],
+ [sdk.skills.Vigor, 8],
+ [sdk.skills.Concentration, 9],
+ [sdk.skills.Vigor, 9],
+ [sdk.skills.Concentration, 10],
+ [sdk.skills.Vigor, 10],
+ [sdk.skills.Concentration, 11],
+ [sdk.skills.Vigor, 11],
+ [sdk.skills.Concentration, 12],
+ [sdk.skills.Vigor, 12],
+ [sdk.skills.Concentration, 13],
+ [sdk.skills.Vigor, 13],
+ [sdk.skills.Concentration, 14],
+ [sdk.skills.Vigor, 14],
+ [sdk.skills.Concentration, 15],
+ [sdk.skills.Vigor, 15],
+ [sdk.skills.Concentration, 16],
+ [sdk.skills.Vigor, 16],
+ [sdk.skills.Concentration, 17],
+ [sdk.skills.Vigor, 17],
+ [sdk.skills.Concentration, 18],
+ [sdk.skills.Vigor, 18],
+ [sdk.skills.Concentration, 19],
+ [sdk.skills.Vigor, 19],
+ [sdk.skills.Concentration, 20],
+ [sdk.skills.Vigor, 20],
+ [sdk.skills.BlessedAim, 20],
+ [sdk.skills.HolyShield, 20]
+ ],
-// Has to be set after its loaded
-build.stats = me.classic ? build.classicStats : build.expansionStats;
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.AttackSkill = [
+ -1, sdk.skills.BlessedHammer, sdk.skills.Concentration,
+ sdk.skills.BlessedHammer, sdk.skills.Concentration,
+ sdk.skills.HolyBolt, sdk.skills.Concentration
+ ];
+ Config.LowManaSkill = [0, sdk.skills.Concentration];
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = me.expansion ? 2 : 4;
+ Config.MPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
+ (me.hell && !me.accessToAct(5)) && (Config.SkipImmune = ["magic"]);
+ SetUp.belt();
+ }
+ }
+ },
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.AttackSkill = [-1, sdk.skills.BlessedHammer, sdk.skills.Concentration, sdk.skills.BlessedHammer, sdk.skills.Concentration, sdk.skills.HolyBolt, sdk.skills.Concentration];
- Config.LowManaSkill = [0, sdk.skills.Concentration];
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = me.expansion ? 2 : 4;
- Config.MPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
- (me.hell && !Pather.accessToAct(5)) && (Config.SkipImmune = ["magic"]);
- SetUp.belt();
-});
+ active: function () {
+ return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.checkSkill(sdk.skills.Concentration, sdk.skills.subindex.HardPoints) && !Check.finalBuild().active());
+ },
+ };
+
+ // Has to be set after its loaded
+ build.stats = me.classic
+ ? [["dexterity", 51], ["strength", 80], ["vitality", "all"]]
+ : [
+ ["vitality", 60], ["dexterity", 30], ["strength", 27],
+ ["vitality", 91], ["dexterity", 44], ["strength", 30],
+ ["vitality", 96], ["dexterity", 59], ["strength", 60],
+ ["vitality", 109], ["dexterity", 77], ["strength", 89],
+ ["vitality", 137], ["dexterity", 89], ["strength", 103],
+ ["vitality", 173], ["dexterity", 103],
+ ["vitality", 208], ["dexterity", 118],
+ ["vitality", 243], ["dexterity", 133],
+ ["vitality", 279], ["dexterity", 147],
+ ["vitality", "all"]
+ ];
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.SancdreamerBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.SancdreamerBuild.js
index 6b2d57b8..8c963fb5 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.SancdreamerBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.SancdreamerBuild.js
@@ -8,121 +8,160 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.Zeal, sdk.skills.Sanctuary],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.Sacrifice, sdk.skills.ResistLightning],
- precastSkills: [sdk.skills.HolyShield],
- usefulStats: [sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce, sdk.stats.PierceLtng, sdk.stats.PassiveMagMastery, sdk.stats.PassiveMagPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 103], ["dexterity", 136], ["vitality", 300], ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Sanctuary, 20],
- [sdk.skills.Zeal, 4],
- [sdk.skills.ResistLightning, 20],
- [sdk.skills.Cleansing, 20],
- [sdk.skills.Redemption, 1],
- [sdk.skills.HolyShield, 15],
- [sdk.skills.Sacrifice, 19],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - Last Wish
- "[type] == sword && [flag] == runeword # [mightaura] >= 17 # [tier] == 120000",
- // Temporary Weapon - Crescent Moon
- "[type] == sword && [flag] == runeword # [ias] >= 20 && [passiveltngpierce] >= 35 # [tier] == 110000",
- // Temporary Weapon - Voice of Reason
- "[type] == sword && [flag] == runeword # [passivecoldpierce] >= 24 # [tier] == 102500",
- // Helm - Dream
- "[type] == helm && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
- // Belt - Verdungos
- "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] == 15 # [tier] == 110000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
- // Shield - Dream
- "[type] == auricshields && [flag] != ethereal && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
- // Gloves - Laying of Hands
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Perfect Wisp
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == 110000",
- // Rings - Raven Frost & Wisp
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.Zeal, sdk.skills.Sanctuary],
+ usefulskills: [sdk.skills.HolyShield, sdk.skills.Sacrifice, sdk.skills.ResistLightning],
+ precastSkills: [sdk.skills.HolyShield],
+ usefulStats: [
+ sdk.stats.PassiveLightningMastery,
+ sdk.stats.PassiveLightningPierce,
+ sdk.stats.PierceLtng,
+ sdk.stats.PassiveMagMastery,
+ sdk.stats.PassiveMagPierce
+ ],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 103], ["dexterity", 136], ["vitality", 300], ["dexterity", "block"], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Sanctuary, 20],
+ [sdk.skills.Zeal, 4],
+ [sdk.skills.ResistLightning, 20],
+ [sdk.skills.Cleansing, 20],
+ [sdk.skills.Redemption, 1],
+ [sdk.skills.HolyShield, 15],
+ [sdk.skills.Sacrifice, 19],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.Vigor = false;
- Config.AttackSkill = [-1, sdk.skills.Zeal, sdk.skills.Sanctuary, sdk.skills.Zeal, sdk.skills.Sanctuary, -1, -1];
- Config.LowManaSkill = [-1, -1];
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- Config.SkipImmune = ["lightning and magic and physical"]; // Don't think this ever happens but should skip if it does
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- SetUp.belt();
- }
- },
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.Vigor = false;
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Zeal, sdk.skills.Sanctuary,
+ sdk.skills.Zeal, sdk.skills.Sanctuary,
+ -1, -1
+ ];
+ Config.LowManaSkill = [-1, -1];
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return me.haveAll([{name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}, {name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm},
- {name: sdk.locale.items.LastWish}]);
- }
- },
+ Config.SkipImmune = ["lightning and magic and physical"]; // Don't think this ever happens but should skip if it does
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ SetUp.belt();
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Sanctuary, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return me.haveAll([
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm },
+ { name: sdk.locale.items.LastWish }
+ ]);
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Sanctuary, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Final Weapon - Last Wish
+ "[type] == sword && [flag] == runeword # [mightaura] >= 17 # [tier] == 120000",
+ // Temporary Weapon - Crescent Moon
+ "[type] == sword && [flag] == runeword # [ias] >= 20 && [passiveltngpierce] >= 35 # [tier] == 110000",
+ // Temporary Weapon - Voice of Reason
+ "[type] == sword && [flag] == runeword # [passivecoldpierce] >= 24 # [tier] == 102500",
+ // Helm - Dream
+ "[type] == helm && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
+ // Belt - Verdungos
+ "[name] == mithrilcoil && [quality] == unique && [flag] != ethereal # [damageresist] == 15 # [tier] == tierscore(item, 110000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
+ // Shield - Dream
+ "[type] == auricshields && [flag] != ethereal && [flag] == runeword # [holyshockaura] >= 15 # [tier] == 110000",
+ // Gloves - Laying of Hands
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Perfect Wisp
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == 110000",
+ // Rings - Raven Frost & Wisp
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.SmiterBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.SmiterBuild.js
index 7bf3905a..e34745b4 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.SmiterBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.SmiterBuild.js
@@ -5,111 +5,142 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.Smite, sdk.skills.Fanaticism],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.Salvation],
- precastSkills: [sdk.skills.HolyShield],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 115], ["dexterity", 136], ["vitality", 300],
- ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Smite, 20],
- [sdk.skills.HolyShield, 20],
- [sdk.skills.Fanaticism, 20],
- [sdk.skills.Salvation, 5],
- [sdk.skills.ResistLightning, 15],
- [sdk.skills.ResistFire, 14],
- [sdk.skills.ResistCold, 10]
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Grief
- "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 100000",
- // Helm - GFace
- "[name] == wingedhelm && [quality] == set && [flag] != ethereal # [fhr] >= 30 # [tier] == 100000 + tierscore(item)",
- // Belt - Tgods
- "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 100000 + tierscore(item)",
- // Boots - Goblin Toes
- "[name] == lightplatedboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 50 # [tier] == 100000 + tierscore(item)",
- // Armor - Enigma
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
- // Shield - HoZ
- "[name] == gildedshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 150 # [tier] == 100000 + tierscore(item)",
- // Gloves - Drac's
- "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 && [strength] >= 12 && [lifeleech] >= 9 # [tier] == 100000 + tierscore(item)",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.Smite, sdk.skills.Fanaticism],
+ usefulskills: [sdk.skills.HolyShield, sdk.skills.Salvation],
+ precastSkills: [sdk.skills.HolyShield],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 115], ["dexterity", 136], ["vitality", 300],
+ ["dexterity", "block"], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Smite, 20],
+ [sdk.skills.HolyShield, 20],
+ [sdk.skills.Fanaticism, 20],
+ [sdk.skills.Salvation, 5],
+ [sdk.skills.ResistLightning, 15],
+ [sdk.skills.ResistFire, 14],
+ [sdk.skills.ResistCold, 10]
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PalaCombat) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Smite, sdk.skills.Fanaticism, sdk.skills.Smite, sdk.skills.Fanaticism, sdk.skills.BlessedHammer, sdk.skills.Concentration];
- Config.LowManaSkill = [0, sdk.skills.Fanaticism];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- SetUp.belt();
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PalaCombat) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.checkItem({name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword}).have;
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Smite, sdk.skills.Fanaticism,
+ sdk.skills.Smite, sdk.skills.Fanaticism,
+ sdk.skills.BlessedHammer, sdk.skills.Concentration
+ ];
+ Config.LowManaSkill = [0, sdk.skills.Fanaticism];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ SetUp.belt();
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Smite, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.checkItem({ name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword }).have;
+ }
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Smite, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - Grief
+ "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 100000",
+ // Helm - GFace
+ "[name] == wingedhelm && [quality] == set && [flag] != ethereal # [fhr] >= 30 # [tier] == tierscore(item, 100000)",
+ // Belt - Tgods
+ "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 100000)",
+ // Boots - Goblin Toes
+ "[name] == lightplatedboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 50 # [tier] == tierscore(item, 100000)",
+ // Armor - Enigma
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [itemallskills] == 2 # [tier] == 100000",
+ // Shield - HoZ
+ "[name] == gildedshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 150 # [tier] == tierscore(item, 100000)",
+ // Gloves - Drac's
+ "[name] == vampirebonegloves && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 && [strength] >= 12 && [lifeleech] >= 9 # [tier] == tierscore(item, 100000)",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.StartBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.StartBuild.js
index 294088ad..57cf23ce 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.StartBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.StartBuild.js
@@ -5,85 +5,97 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: false,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.Zeal, sdk.skills.HolyFire],
- usefulskills: [(me.checkSkill(sdk.skills.HolyFire, sdk.skills.subindex.SoftPoints) ? sdk.skills.Sacrifice : sdk.skills.Might), sdk.skills.ResistFire],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["vitality", 80],
- ["dexterity", 27],
- ["strength", 47],
- ["vitality", "all"],
- ],
- skills: [
- // Total skills points by respec = 20
- [sdk.skills.Might, 1], // charlevel -> 2
- [sdk.skills.Sacrifice, 1], // charlevel -> 3
- [sdk.skills.HolyFire, 1, false], // charlevel -> 6
- [sdk.skills.ResistFire, 4], // charlevel -> 5
- [sdk.skills.HolyFire, 3], // charlevel -> 8
- [sdk.skills.Smite, 1], // charlevel -> 10
- [sdk.skills.Zeal, 1], // charlevel -> 12
- [sdk.skills.Charge, 1], // charlevel -> 12
- [sdk.skills.Zeal, 4, false], // charlevel -> 15
- [sdk.skills.HolyFire, 6], // charlevel -> 17
- [sdk.skills.ResistFire, 16] // respec at 19
- ],
- active: function () {
- return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.BlessedAim, sdk.skills.subindex.HardPoints);
- },
-};
+(function (module, require) {
+ module.exports = (function () {
+ const build = {
+ AutoBuildTemplate: {},
+ caster: false,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.Zeal, sdk.skills.HolyFire],
+ usefulskills: [
+ (me.checkSkill(sdk.skills.HolyFire, sdk.skills.subindex.SoftPoints)
+ ? sdk.skills.Sacrifice
+ : sdk.skills.Might),
+ sdk.skills.ResistFire
+ ],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["vitality", 80],
+ ["dexterity", 27],
+ ["strength", 47],
+ ["vitality", "all"],
+ ],
+ skills: [
+ // Total skills points by respec = 20
+ [sdk.skills.Might, 1], // charlevel -> 2
+ [sdk.skills.Sacrifice, 1], // charlevel -> 3
+ [sdk.skills.HolyFire, 1, false], // charlevel -> 6
+ [sdk.skills.ResistFire, 4], // charlevel -> 5
+ [sdk.skills.HolyFire, 3], // charlevel -> 8
+ [sdk.skills.Smite, 1], // charlevel -> 10
+ [sdk.skills.Zeal, 1], // charlevel -> 12
+ [sdk.skills.Charge, 1], // charlevel -> 12
+ [sdk.skills.Zeal, 4, false], // charlevel -> 15
+ [sdk.skills.HolyFire, 6], // charlevel -> 17
+ [sdk.skills.ResistFire, 16] // respec at 19
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.SkipEnchant.indexOf("cold enchanted") === -1 && Config.SkipEnchant.push("cold enchanted");
- Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
- Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
- Config.FieldID.UsedSpace = 0;
+ active: function () {
+ return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.BlessedAim, sdk.skills.subindex.HardPoints);
+ },
+ };
- Config.BeltColumn = ["hp", "hp", "hp", "hp"];
- Config.HPBuffer = 8;
- Config.AttackSkill = [-1, sdk.skills.Attack, -1, sdk.skills.Attack, -1, -1, -1];
- Config.LowManaSkill = [sdk.skills.Attack, -1];
- SetUp.belt();
-});
-build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
- Config.SkipEnchant.indexOf("cold enchanted") === -1 && Config.SkipEnchant.push("cold enchanted");
- Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
- Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
- Config.FieldID.UsedSpace = 0;
+ const { buildAutoBuildTempObj } = require("../../Utils/General");
+
+ build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
+ Config.SkipEnchant.indexOf("cold enchanted") === -1 && Config.SkipEnchant.push("cold enchanted");
+ Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
+ Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
+ Config.FieldID.UsedSpace = 0;
- Config.BeltColumn = ["hp", "hp", "hp", "hp"];
- Config.HPBuffer = 8;
- const bossSkill = (me.checkSkill(sdk.skills.Sacrifice, sdk.skills.subindex.HardPoints) ? sdk.skills.Sacrifice : sdk.skills.Attack);
- Config.AttackSkill = [-1, bossSkill, sdk.skills.Might, sdk.skills.Attack, sdk.skills.Might, -1, -1];
- Config.LowManaSkill = [sdk.skills.Attack, sdk.skills.Might];
-});
-build.AutoBuildTemplate[6] = buildAutoBuildTempObj(() => {
- Config.HPBuffer = 8;
- const bossSkill = (me.checkSkill(sdk.skills.Sacrifice, sdk.skills.subindex.HardPoints) ? sdk.skills.Sacrifice : sdk.skills.Attack);
- Config.AttackSkill = [-1, bossSkill, sdk.skills.HolyFire, sdk.skills.Attack, sdk.skills.HolyFire, sdk.skills.Attack, sdk.skills.Might];
- Config.LowManaSkill = [sdk.skills.Attack, sdk.skills.HolyFire];
-});
-build.AutoBuildTemplate[9] = buildAutoBuildTempObj(() => {
- Config.HPBuffer = me.expansion ? 2 : 4;
- Config.MPBuffer = 6;
- Config.AttackSkill[0] = -1;
- Config.AttackSkill[1] = (me.checkSkill(sdk.skills.Sacrifice, sdk.skills.subindex.HardPoints) ? sdk.skills.Sacrifice : sdk.skills.Attack);
- Config.AttackSkill[2] = sdk.skills.HolyFire;
- Config.AttackSkill[3] = sdk.skills.Attack;
- Config.AttackSkill[4] = sdk.skills.HolyFire;
- Config.AttackSkill[5] = sdk.skills.Attack;
- Config.AttackSkill[6] = sdk.skills.Might;
-});
-build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
- if (me.checkSkill(sdk.skills.Zeal, sdk.skills.subindex.HardPoints)) {
- Config.AttackSkill = [-1, sdk.skills.Zeal, sdk.skills.HolyFire, sdk.skills.Zeal, sdk.skills.HolyFire, 0, sdk.skills.Might];
- }
- Config.Charge = true;
-});
+ Config.BeltColumn = ["hp", "hp", "hp", "hp"];
+ Config.HPBuffer = 8;
+ Config.AttackSkill = [-1, sdk.skills.Attack, -1, sdk.skills.Attack, -1, -1, -1];
+ Config.LowManaSkill = [sdk.skills.Attack, -1];
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
+ Config.SkipEnchant.indexOf("cold enchanted") === -1 && Config.SkipEnchant.push("cold enchanted");
+ Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
+ Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
+ Config.FieldID.UsedSpace = 0;
+
+ Config.BeltColumn = ["hp", "hp", "hp", "hp"];
+ Config.HPBuffer = 8;
+ const bossSkill = (me.checkSkill(sdk.skills.Sacrifice, sdk.skills.subindex.HardPoints) ? sdk.skills.Sacrifice : sdk.skills.Attack);
+ Config.AttackSkill = [-1, bossSkill, sdk.skills.Might, sdk.skills.Attack, sdk.skills.Might, -1, -1];
+ Config.LowManaSkill = [sdk.skills.Attack, sdk.skills.Might];
+ });
+ build.AutoBuildTemplate[6] = buildAutoBuildTempObj(() => {
+ Config.HPBuffer = 8;
+ const bossSkill = (me.checkSkill(sdk.skills.Sacrifice, sdk.skills.subindex.HardPoints) ? sdk.skills.Sacrifice : sdk.skills.Attack);
+ Config.AttackSkill = [-1, bossSkill, sdk.skills.HolyFire, sdk.skills.Attack, sdk.skills.HolyFire, sdk.skills.Attack, sdk.skills.Might];
+ Config.LowManaSkill = [sdk.skills.Attack, sdk.skills.HolyFire];
+ });
+ build.AutoBuildTemplate[9] = buildAutoBuildTempObj(() => {
+ Config.HPBuffer = me.expansion ? 2 : 4;
+ Config.MPBuffer = 6;
+ Config.AttackSkill[0] = -1;
+ Config.AttackSkill[1] = (me.checkSkill(sdk.skills.Sacrifice, sdk.skills.subindex.HardPoints) ? sdk.skills.Sacrifice : sdk.skills.Attack);
+ Config.AttackSkill[2] = sdk.skills.HolyFire;
+ Config.AttackSkill[3] = sdk.skills.Attack;
+ Config.AttackSkill[4] = sdk.skills.HolyFire;
+ Config.AttackSkill[5] = sdk.skills.Attack;
+ Config.AttackSkill[6] = sdk.skills.Might;
+ });
+ build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
+ if (me.checkSkill(sdk.skills.Zeal, sdk.skills.subindex.HardPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.Zeal, sdk.skills.HolyFire, sdk.skills.Zeal, sdk.skills.HolyFire, 0, sdk.skills.Might];
+ }
+ Config.Charge = true;
+ });
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.TorchadinBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.TorchadinBuild.js
index ebe21a74..06b30154 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.TorchadinBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.TorchadinBuild.js
@@ -5,123 +5,159 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.Zeal, sdk.skills.Conviction],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.ResistFire, sdk.skills.Salvation],
- precastSkills: [sdk.skills.HolyShield],
- usefulStats: [sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce, sdk.stats.PierceFire],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 103], ["dexterity", 136],
- ["vitality", 300], ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Conviction, 20],
- [sdk.skills.Zeal, 4],
- [sdk.skills.Salvation, 20],
- [sdk.skills.ResistFire, 20],
- [sdk.skills.Redemption, 1],
- [sdk.skills.HolyShield, 15],
- [sdk.skills.Zeal, 10],
- [sdk.skills.Sacrifice, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Final Weapon - HoJ
- "[type] == sword && [flag] == runeword # [holyfireaura] >= 16 # [tier] == 120000",
- // Temporary Weapon - Crescent Moon
- "[type] == sword && [flag] == runeword # [ias] >= 20 && [passiveltngpierce] >= 35 # [tier] == 110000",
- // Temporary Weapon - Voice of Reason
- "[type] == sword && [flag] == runeword # [passivecoldpierce] >= 24 # [tier] == 102500",
- // Final Helm - Upp'ed Vamp Gaze
- "[name] == bonevisage && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 && [lifeleech] >= 6 # [tier] == 100000 + tierscore(item)",
- // Helm - Vamp Gaze
- "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 # [tier] == 100000 + tierscore(item)",
- // Belt - TGods
- "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Armor - Dragon
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [holyfireaura] >= 14 # [tier] == 110000",
- // Shield - Exile
- "[type] == auricshields && [flag] == runeword # [defianceaura] >= 13 # [tier] == 110000",
- // Gloves - Laying of Hands
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.Zeal, sdk.skills.Conviction],
+ usefulskills: [sdk.skills.HolyShield, sdk.skills.ResistFire, sdk.skills.Salvation],
+ precastSkills: [sdk.skills.HolyShield],
+ usefulStats: [sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce, sdk.stats.PierceFire],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 103], ["dexterity", 136],
+ ["vitality", 300], ["dexterity", "block"], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Conviction, 20],
+ [sdk.skills.Zeal, 4],
+ [sdk.skills.Salvation, 20],
+ [sdk.skills.ResistFire, 20],
+ [sdk.skills.Redemption, 1],
+ [sdk.skills.HolyShield, 15],
+ [sdk.skills.Zeal, 10],
+ [sdk.skills.Sacrifice, 20],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.Vigor = false;
- Config.AttackSkill = [-1, sdk.skills.Zeal, sdk.skills.Conviction, sdk.skills.Zeal, sdk.skills.Conviction, -1, -1];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["fire and physical"];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- SetUp.belt();
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Offensive) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return false;
- } else {
- return me.haveAll([{name: sdk.locale.items.HandofJustice}, {name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}]);
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.Vigor = false;
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Zeal, sdk.skills.Conviction,
+ sdk.skills.Zeal, sdk.skills.Conviction,
+ -1, -1
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["fire and physical"];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ SetUp.belt();
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Conviction, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return false;
+ }
+ return (
+ me.haveAll([
+ { name: sdk.locale.items.HandofJustice },
+ { name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor }
+ ])
+ );
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Conviction, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ // autoequip final gear
+ let finalGear = [
+ // Final Weapon - HoJ
+ "[type] == sword && [flag] == runeword # [holyfireaura] >= 16 # [tier] == 120000",
+ // Temporary Weapon - Crescent Moon
+ "[type] == sword && [flag] == runeword # [ias] >= 20 && [passiveltngpierce] >= 35 # [tier] == 110000",
+ // Temporary Weapon - Voice of Reason
+ "[type] == sword && [flag] == runeword # [passivecoldpierce] >= 24 # [tier] == 102500",
+ // Final Helm - Upp'ed Vamp Gaze
+ "[name] == bonevisage && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 && [lifeleech] >= 6 # [tier] == tierscore(item, 100000)",
+ // Helm - Vamp Gaze
+ "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 # [tier] == tierscore(item, 100000)",
+ // Belt - TGods
+ "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Armor - Dragon
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [holyfireaura] >= 14 # [tier] == 110000",
+ // Shield - Exile
+ "[type] == auricshields && [flag] == runeword # [defianceaura] >= 13 # [tier] == 110000",
+ // Gloves - Laying of Hands
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.ZealerBuild.js b/libs/SoloPlay/BuildFiles/paladin/paladin.ZealerBuild.js
index 8763baa9..7b589760 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.ZealerBuild.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.ZealerBuild.js
@@ -5,119 +5,151 @@
*
*/
-const finalBuild = {
- caster: false,
- skillstab: sdk.skills.tabs.PalaCombat,
- wantedskills: [sdk.skills.Zeal, sdk.skills.Fanaticism],
- usefulskills: [sdk.skills.HolyShield, sdk.skills.ResistFire, sdk.skills.ResistLightning],
- precastSkills: [sdk.skills.HolyShield],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 103], ["dexterity", 136], ["vitality", 300],
- ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Fanaticism, 20],
- [sdk.skills.Sacrifice, 20],
- [sdk.skills.Salvation, 1],
- [sdk.skills.Redemption, 1],
- [sdk.skills.Zeal, 10],
- [sdk.skills.HolyShield, 15], // lvl 74 w/o quest skill pts
- [sdk.skills.ResistLightning, 10, false],
- [sdk.skills.ResistFire, 10, false],
- [sdk.skills.ResistCold, 10, false],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Grief
- "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 100000",
- // Final Helm - Upp'ed Vamp Gaze
- "[name] == bonevisage && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 && [lifeleech] >= 6 # [tier] == 100000 + tierscore(item)",
- // Helm -Vamp Gaze
- "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 # [tier] == 100000 + tierscore(item)",
- // Belt - TGods
- "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Boots - Gore Rider
- "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == 110000 + tierscore(item)",
- // Armor - Fortitude
- "[type] == armor && [flag] != ethereal && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [tier] == 110000",
- // Final Shield - Exile
- "[type] == auricshields && [flag] == runeword # [defianceaura] >= 13 # [tier] == 110000",
- // Shield - HoZ
- "[name] == gildedshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 150 # [tier] == 100000 + tierscore(item)",
- // Gloves - Laying of Hand's
- "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
- // Amulet - Highlords
- "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
- // Rings - Raven Frost && Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Armor - Treachery
- "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 6,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: false,
+ skillstab: sdk.skills.tabs.PalaCombat,
+ wantedskills: [sdk.skills.Zeal, sdk.skills.Fanaticism],
+ usefulskills: [sdk.skills.HolyShield, sdk.skills.ResistFire, sdk.skills.ResistLightning],
+ precastSkills: [sdk.skills.HolyShield],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 103], ["dexterity", 136], ["vitality", 300],
+ ["dexterity", "block"], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Fanaticism, 20],
+ [sdk.skills.Sacrifice, 20],
+ [sdk.skills.Salvation, 1],
+ [sdk.skills.Redemption, 1],
+ [sdk.skills.Zeal, 10],
+ [sdk.skills.HolyShield, 15], // lvl 74 w/o quest skill pts
+ [sdk.skills.ResistLightning, 10, false],
+ [sdk.skills.ResistFire, 10, false],
+ [sdk.skills.ResistCold, 10, false],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 6,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PalaCombat) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Zeal, sdk.skills.Fanaticism, sdk.skills.Zeal, sdk.skills.Fanaticism, -1, -1];
- Config.LowManaSkill = [-1, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "rv"];
- SetUp.belt();
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.PalaCombat) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.checkItem({name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword}).have;
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Zeal, sdk.skills.Fanaticism,
+ sdk.skills.Zeal, sdk.skills.Fanaticism,
+ -1, -1
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.BeltColumn = ["hp", "hp", "mp", "rv"];
+ SetUp.belt();
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Fanaticism, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.checkItem({ name: sdk.locale.items.Grief, itemtype: sdk.items.type.Sword }).have;
+ }
+ },
+
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Fanaticism, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ // autoequip final gear
+ let finalGear = [
+ // Weapon - Grief
+ "[type] == sword && [flag] == runeword # [ias] >= 30 && [itemdeadlystrike] == 20 && [passivepoispierce] >= 20 # [tier] == 100000",
+ // Final Helm - Upp'ed Vamp Gaze
+ "[name] == bonevisage && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 && [lifeleech] >= 6 # [tier] == tierscore(item, 100000)",
+ // Helm -Vamp Gaze
+ "[name] == grimhelm && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 100 # [tier] == tierscore(item, 100000)",
+ // Belt - TGods
+ "[name] == warbelt && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Boots - Gore Rider
+ "[name] == warboots && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 160 # [tier] == tierscore(item, 110000)",
+ // Armor - Fortitude
+ "[type] == armor && [flag] != ethereal && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [tier] == 110000",
+ // Final Shield - Exile
+ "[type] == auricshields && [flag] == runeword # [defianceaura] >= 13 # [tier] == 110000",
+ // Shield - HoZ
+ "[name] == gildedshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 150 # [tier] == tierscore(item, 100000)",
+ // Gloves - Laying of Hand's
+ "[name] == bramblemitts && [quality] == set && [flag] != ethereal # [ias] >= 20 # [tier] == 110000",
+ // Amulet - Highlords
+ "[type] == amulet && [quality] == unique # [lightresist] == 35 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] == 20 && [tohit] == 250 # [tier] == 110000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 110000",
+ // Rings - Raven Frost && Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 && [tohit] >= 150 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Armor - Treachery
+ "[type] == armor && [flag] == runeword # [ias] == 45 && [coldresist] == 30 # [merctier] == 50000 + mercscore(item)",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/paladin/paladin.js b/libs/SoloPlay/BuildFiles/paladin/paladin.js
index 1d50d653..7323bc04 100644
--- a/libs/SoloPlay/BuildFiles/paladin/paladin.js
+++ b/libs/SoloPlay/BuildFiles/paladin/paladin.js
@@ -6,38 +6,42 @@
*/
const CharInfo = {
- respecOne: 19,
- respecTwo: 0,
- levelCap: (function() {
- const currentDiff = sdk.difficulty.nameOf(me.diff);
- const softcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 65 : 65,
- "Hell": 100,
- };
- const hardcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 65 : 65,
- "Hell": 100,
- };
+ respecOne: 19,
+ respecTwo: 0,
+ levelCap: (function () {
+ const currentDiff = sdk.difficulty.nameOf(me.diff);
+ const softcoreMode = {
+ "Normal": me.expansion ? 33 : 33,
+ "Nightmare": me.expansion ? 65 : 65,
+ "Hell": 100,
+ };
+ const hardcoreMode = {
+ "Normal": me.expansion ? 36 : 33,
+ "Nightmare": me.expansion ? 71 : 65,
+ "Hell": 100,
+ };
- return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
- })(),
+ return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
+ })(),
- getActiveBuild: function () {
- const nSkills = me.getStat(sdk.stats.NewSkills);
- const currLevel = me.charlvl;
- const justRepeced = (nSkills >= currLevel);
+ getActiveBuild: function () {
+ const nSkills = me.getStat(sdk.stats.NewSkills);
+ const currLevel = me.charlvl;
+ const justRepeced = (nSkills >= currLevel);
- switch (true) {
- case currLevel < this.respecOne:
- case !justRepeced && currLevel > this.respecOne && !me.checkSkill(sdk.skills.Concentration, sdk.skills.subindex.HardPoints):
- return "Start";
- case Check.finalBuild().respec() && justRepeced:
- case Check.finalBuild().active():
- return SetUp.finalBuild;
- default:
- return "Leveling";
- }
- },
+ switch (true) {
+ case currLevel < this.respecOne:
+ case (
+ !justRepeced
+ && currLevel > this.respecOne
+ && !me.checkSkill(sdk.skills.Concentration, sdk.skills.subindex.HardPoints)
+ ):
+ return "Start";
+ case Check.finalBuild().respec() && justRepeced:
+ case Check.finalBuild().active():
+ return SetUp.finalBuild;
+ default:
+ return "Leveling";
+ }
+ },
};
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.BlizzballerBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.BlizzballerBuild.js
index 36b840f2..4452c331 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.BlizzballerBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.BlizzballerBuild.js
@@ -5,154 +5,206 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Cold,
- wantedskills: [sdk.skills.Blizzard, sdk.skills.FireBall, sdk.skills.ColdMastery],
- usefulskills: [sdk.skills.GlacialSpike, sdk.skills.Meteor, sdk.skills.FireMastery, sdk.skills.StaticField],
- precastSkills: [sdk.skills.FrozenArmor],
- usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery, sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["energy", 50], ["strength", 48], ["vitality", 165],
- ["strength", 61], ["vitality", 252], ["strength", 127],
- ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Warmth, 1],
- [sdk.skills.FrozenArmor, 1],
- [sdk.skills.StaticField, 1],
- [sdk.skills.Teleport, 1],
- [sdk.skills.Meteor, 1],
- [sdk.skills.FireMastery, 1, false],
- [sdk.skills.ColdMastery, 1, false],
- [sdk.skills.FireBall, 20],
- [sdk.skills.Blizzard, 20],
- [sdk.skills.GlacialSpike, 20], // lvl 75 w/o quest skills pts
- [sdk.skills.Meteor, 20],
- [sdk.skills.ColdMastery, 5],
- [sdk.skills.FireBolt, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Tals Orb
- "[name] == swirlingcrystal && [quality] == set && [flag] != ethereal # [skilllightningmastery]+[skillfiremastery]+[skillcoldmastery] >= 3 # [tier] == 100000 + tierscore(item)",
- // Helmet - Tals Mask
- "[name] == deathmask && [quality] == set && [flag] != ethereal # [coldresist]+[lightresist]+[fireresist]+[poisonresist] >= 60 # [tier] == 100000 + tierscore(item)",
- // Belt - Tals Belt
- "[name] == meshbelt && [quality] == set && [flag] != ethereal # [itemmagicbonus] >= 10 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Tals Armor
- "[name] == lacqueredplate && [quality] == set # [coldresist] >= 1 # [tier] == 100000",
- // Final Shield - Sanctuary
- "[name] == hyperion && [flag] == runeword # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000",
- // Shield - Mosers
- "[name] == roundshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 180 # [tier] == 50000 + tierscore(item)",
- // Final Gloves - Perfect Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Tals Ammy
- "[name] == amulet && [quality] == set # [lightresist] == 33 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Nagelring
- "[type] == ring && [quality] == unique # [dexterity] == 20 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [itemmagicbonus] == 30 # [tier] == 100000",
- // Rings - Raven Frost
- "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 90000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Shield - 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 100000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Cold,
+ wantedskills: [sdk.skills.Blizzard, sdk.skills.FireBall, sdk.skills.ColdMastery],
+ usefulskills: [sdk.skills.GlacialSpike, sdk.skills.Meteor, sdk.skills.FireMastery, sdk.skills.StaticField],
+ precastSkills: [sdk.skills.FrozenArmor],
+ usefulStats: [
+ sdk.stats.PassiveColdPierce,
+ sdk.stats.PassiveColdMastery,
+ sdk.stats.PassiveFireMastery,
+ sdk.stats.PassiveFirePierce
+ ],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["energy", 50], ["strength", 48], ["vitality", 165],
+ ["strength", 61], ["vitality", 252], ["strength", 127],
+ ["dexterity", "block"], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Warmth, 1],
+ [sdk.skills.FrozenArmor, 1],
+ [sdk.skills.StaticField, 1],
+ [sdk.skills.Teleport, 1],
+ [sdk.skills.Meteor, 1],
+ [sdk.skills.FireMastery, 1, false],
+ [sdk.skills.ColdMastery, 1, false],
+ [sdk.skills.FireBall, 20],
+ [sdk.skills.Blizzard, 20],
+ [sdk.skills.GlacialSpike, 20], // lvl 75 w/o quest skills pts
+ [sdk.skills.Meteor, 20],
+ [sdk.skills.ColdMastery, 5],
+ [sdk.skills.FireBolt, 20],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- SkillerFire: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Fire) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerCold: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ SkillerFire: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Fire) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Blizzard, sdk.skills.FireBall, sdk.skills.Blizzard, sdk.skills.FireBall, sdk.skills.Meteor, sdk.skills.GlacialSpike];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["fire and cold"];
- Config.HPBuffer = me.expansion ? 1 : 5;
- Config.MPBuffer = me.expansion ? 1 : 5;
- }
- },
- },
+ SkillerCold: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.haveAll([
- { name: sdk.locale.items.TalRashasBelt, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasAmulet, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasArmor, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasOrb, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasHelmet, quality: sdk.items.quality.Set },
- ]);
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Blizzard, sdk.skills.FireBall,
+ sdk.skills.Blizzard, sdk.skills.FireBall,
+ sdk.skills.Meteor, sdk.skills.GlacialSpike
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["fire and cold"];
+ Config.HPBuffer = me.expansion ? 1 : 5;
+ Config.MPBuffer = me.expansion ? 1 : 5;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.FireBall, sdk.skills.subindex.HardPoints) === 20 && me.getSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.haveAll([
+ { name: sdk.locale.items.TalRashasBelt, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasAmulet, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasArmor, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasOrb, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasHelmet, quality: sdk.items.quality.Set },
+ ]);
+ }
+ },
+
+ active: function () {
+ return (
+ this.respec()
+ && me.getSkill(sdk.skills.FireBall, sdk.skills.subindex.HardPoints) === 20
+ && me.getSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints) === 20
+ );
+ },
+ };
+
+ // autoequip final gear
+ let finalGear = [
+ // Weapon - Tals Orb
+ "[name] == swirlingcrystal && [quality] == set && [flag] != ethereal # [skilllightningmastery]+[skillfiremastery]+[skillcoldmastery] >= 3 # [tier] == tierscore(item, 100000)",
+ // Helmet - Tals Mask
+ "[name] == deathmask && [quality] == set && [flag] != ethereal # [coldresist]+[lightresist]+[fireresist]+[poisonresist] >= 60 # [tier] == tierscore(item, 100000)",
+ // Belt - Tals Belt
+ "[name] == meshbelt && [quality] == set && [flag] != ethereal # [itemmagicbonus] >= 10 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Tals Armor
+ "[name] == lacqueredplate && [quality] == set # [coldresist] >= 1 # [tier] == 100000",
+ // Final Shield - Sanctuary
+ "[name] == hyperion && [flag] == runeword # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000",
+ // Shield - Mosers
+ "[name] == roundshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 180 # [tier] == tierscore(item, 50000)",
+ // Final Gloves - Perfect Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 && [defense] == 71 # [tier] == 110000",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Tals Ammy
+ "[name] == amulet && [quality] == set # [lightresist] == 33 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Nagelring
+ "[type] == ring && [quality] == unique # [dexterity] == 20 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [itemmagicbonus] == 30 # [tier] == 100000",
+ // Rings - Raven Frost
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 90000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Shield - 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 100000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.BlovaBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.BlovaBuild.js
index b2c64a4b..48d03b74 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.BlovaBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.BlovaBuild.js
@@ -1,148 +1,207 @@
/**
-* @filename Sorceress.BlovaBuild.js
-* @author isid0re, theBGuy
-* @desc Blizzard + Nova based final build
-*
-*/
+ * @filename Sorceress.BlovaBuild.js
+ * @author isid0re, theBGuy
+ * @desc Blizzard + Nova based final build
+ *
+ */
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Cold,
- wantedskills: [sdk.skills.Blizzard, sdk.skills.Nova],
- usefulskills: [sdk.skills.LightningMastery, sdk.skills.ColdMastery, sdk.skills.GlacialSpike],
- precastSkills: [sdk.skills.FrozenArmor],
- usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery, sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 156], ["dexterity", 35], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Warmth, 1],
- [sdk.skills.FrozenArmor, 1],
- [sdk.skills.Teleport, 1],
- [sdk.skills.Blizzard, 1],
- [sdk.skills.Nova, 20],
- [sdk.skills.LightningMastery, 20],
- [sdk.skills.Blizzard, 20],
- [sdk.skills.ColdMastery, 5],
- [sdk.skills.IceBlast, 20],
- [sdk.skills.GlacialSpike, 5],
- [sdk.skills.IceBolt, 14],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - HotO
- "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
- // Helmet - Griffons
- "[name] == diadem && [quality] == unique && [flag] != ethereal # [fcr] == 25 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
- // Shield - Spirit
- "[type] == shield # [fcr] >= 25 && [maxmana] >= 89 # [tier] == 100000 + tierscore(item)",
- // Final Gloves - Perfect Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Final Rings - SoJ & Perfect Bul-Kathos' Wedding Band
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 100000",
- // Ring - Bul-Kathos' Wedding Band
- "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 90000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Final Shield - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Switch Temporary Shield - 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 50000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Cold,
+ wantedskills: [sdk.skills.Blizzard, sdk.skills.Nova],
+ usefulskills: [sdk.skills.LightningMastery, sdk.skills.ColdMastery, sdk.skills.GlacialSpike],
+ precastSkills: [sdk.skills.FrozenArmor],
+ usefulStats: [
+ sdk.stats.PassiveColdPierce,
+ sdk.stats.PassiveColdMastery,
+ sdk.stats.PassiveLightningMastery,
+ sdk.stats.PassiveLightningPierce,
+ ],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 156],
+ ["dexterity", 35],
+ ["vitality", "all"],
+ ],
+ skills: [
+ [sdk.skills.Warmth, 1],
+ [sdk.skills.FrozenArmor, 1],
+ [sdk.skills.Teleport, 1],
+ [sdk.skills.Blizzard, 1],
+ [sdk.skills.Nova, 20],
+ [sdk.skills.LightningMastery, 20],
+ [sdk.skills.Blizzard, 20],
+ [sdk.skills.ColdMastery, 5],
+ [sdk.skills.IceBlast, 20],
+ [sdk.skills.GlacialSpike, 5],
+ [sdk.skills.IceBolt, 14],
+ ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ },
+ },
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ },
+ },
- ResFHR: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResFHR: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ },
+ },
- SkillerLight: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Lightning) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ SkillerLight: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Lightning) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ },
+ },
- SkillerCold: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ SkillerCold: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ },
+ },
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Blizzard, sdk.skills.Nova, sdk.skills.Blizzard, sdk.skills.Nova, -1, sdk.skills.IceBlast];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["lightning and cold"];
- Config.HPBuffer = me.expansion ? 1 : 5;
- Config.MPBuffer = me.expansion ? 1 : 5;
- }
- },
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Blizzard,
+ sdk.skills.Nova,
+ sdk.skills.Blizzard,
+ sdk.skills.Nova,
+ -1,
+ sdk.skills.IceBlast,
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["lightning and cold"];
+ Config.HPBuffer = me.expansion ? 1 : 5;
+ Config.MPBuffer = me.expansion ? 1 : 5;
+ },
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return (Attack.checkInfinity() || (myData.merc.gear.includes(sdk.locale.items.Infinity) && !Misc.poll(() => me.getMerc(), 200, 50)));
- }
- },
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return (
+ Attack.checkInfinity()
+ || (me.data.merc.gear.includes(sdk.locale.items.Infinity) && !Misc.poll(() => me.getMerc(), 200, 50))
+ );
+ }
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Nova, sdk.skills.subindex.HardPoints) === 20 && me.getSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints) >= 1;
- },
-};
+ active: function () {
+ return (
+ this.respec()
+ && me.getSkill(sdk.skills.Nova, sdk.skills.subindex.HardPoints) === 20
+ && me.getSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints) >= 1
+ );
+ },
+ };
+
+ // autoequip final gear
+ let finalGear = [
+ // Weapon - HotO
+ "[type] == mace && [flag] == runeword # [itemallskills] == 3 # [tier] == 100000",
+ // Helmet - Griffons
+ "[name] == diadem && [quality] == unique && [flag] != ethereal # [fcr] == 25 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
+ // Shield - Spirit
+ "[type] == shield # [fcr] >= 25 && [maxmana] >= 89 # [tier] == tierscore(item, 100000)",
+ // Final Gloves - Perfect Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 && [defense] == 71 # [tier] == 110000",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Final Rings - SoJ & Perfect Bul-Kathos' Wedding Band
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] == 5 # [tier] == 100000",
+ // Ring - Bul-Kathos' Wedding Band
+ "[name] == ring && [quality] == unique # [maxstamina] == 50 && [lifeleech] >= 3 # [tier] == 90000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Final Shield - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
+ // Switch Temporary Shield - 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 50000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.ColdBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.ColdBuild.js
index 2905228e..3ff76555 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.ColdBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.ColdBuild.js
@@ -5,140 +5,153 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Cold,
- wantedskills: [sdk.skills.Blizzard, sdk.skills.ColdMastery],
- usefulskills: [sdk.skills.GlacialSpike, sdk.skills.IceBlast, sdk.skills.StaticField],
- precastSkills: [sdk.skills.FrozenArmor],
- usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 48], ["vitality", 165], ["strength", 61],
- ["vitality", 252], ["strength", 127], ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Warmth, 1],
- [sdk.skills.StaticField, 1],
- [sdk.skills.Teleport, 1],
- [sdk.skills.FrozenArmor, 1],
- [sdk.skills.Blizzard, 20],
- [sdk.skills.ColdMastery, 17],
- [sdk.skills.IceBlast, 20], // lvl 66 w/o quest skills pts
- [sdk.skills.GlacialSpike, 20],
- [sdk.skills.IceBolt, 20],
- [sdk.skills.ColdMastery, 20]
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - Tals Orb
- "[name] == swirlingcrystal && [quality] == set && [flag] != ethereal # [skilllightningmastery]+[skillfiremastery]+[skillcoldmastery] >= 3 # [tier] == 100000 + tierscore(item)",
- // Helmet - Tals Mask
- "[name] == deathmask && [quality] == set && [flag] != ethereal # [coldresist]+[lightresist]+[fireresist]+[poisonresist] >= 60 # [tier] == 100000 + tierscore(item)",
- // Belt - Tals Belt
- "[name] == meshbelt && [quality] == set && [flag] != ethereal # [itemmagicbonus] >= 10 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Tals Armor
- "[name] == lacqueredplate && [quality] == set # [coldresist] >= 1 # [tier] == 100000",
- // Final Shield - Sanctuary
- "[name] == hyperion && [flag] == runeword # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000",
- // Shield - Mosers
- "[name] == roundshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 180 # [tier] == 50000 + tierscore(item)",
- // Final Gloves - Perfect Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Tals Ammy
- "[name] == amulet && [quality] == set # [lightresist] == 33 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Nagelring
- "[type] == ring && [quality] == unique # [dexterity] == 20 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [itemmagicbonus] == 30 # [tier] == 100000",
- // Rings - Raven Frost
- "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 90000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Shield - 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 100000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Cold,
+ wantedskills: [sdk.skills.Blizzard, sdk.skills.ColdMastery],
+ usefulskills: [sdk.skills.GlacialSpike, sdk.skills.IceBlast, sdk.skills.StaticField],
+ precastSkills: [sdk.skills.FrozenArmor],
+ usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 48], ["vitality", 165], ["strength", 61],
+ ["vitality", 252], ["strength", 127], ["dexterity", "block"], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Warmth, 1],
+ [sdk.skills.StaticField, 1],
+ [sdk.skills.Teleport, 1],
+ [sdk.skills.FrozenArmor, 1],
+ [sdk.skills.Blizzard, 20],
+ [sdk.skills.ColdMastery, 17],
+ [sdk.skills.IceBlast, 20], // lvl 66 w/o quest skills pts
+ [sdk.skills.GlacialSpike, 20],
+ [sdk.skills.IceBolt, 20],
+ [sdk.skills.ColdMastery, 20]
+ ],
+
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResMf: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ ResMf: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Blizzard, sdk.skills.IceBlast, sdk.skills.Blizzard, sdk.skills.GlacialSpike, -1, -1];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["cold"];
- Config.HPBuffer = me.expansion ? 1 : 5;
- Config.MPBuffer = me.expansion ? 1 : 5;
- }
- },
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [-1, sdk.skills.Blizzard, sdk.skills.IceBlast, sdk.skills.Blizzard, sdk.skills.GlacialSpike, -1, -1];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["cold"];
+ Config.HPBuffer = me.expansion ? 1 : 5;
+ Config.MPBuffer = me.expansion ? 1 : 5;
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.haveAll([
- { name: sdk.locale.items.TalRashasBelt, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasAmulet, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasArmor, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasOrb, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasHelmet, quality: sdk.items.quality.Set },
- ]) && me.hell && me.baal;
- }
- },
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.haveAll([
+ { name: sdk.locale.items.TalRashasBelt, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasAmulet, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasArmor, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasOrb, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasHelmet, quality: sdk.items.quality.Set },
+ ]) && me.hell && me.baal;
+ }
+ },
- active: function () {
- return this.respec() && !me.checkSkill(sdk.skills.Meteor, sdk.skills.subindex.HardPoints);
- },
-};
+ active: function () {
+ return this.respec() && !me.checkSkill(sdk.skills.Meteor, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ let finalGear = [ // autoequip final gear
+ // Weapon - Tals Orb
+ "[name] == swirlingcrystal && [quality] == set && [flag] != ethereal # [skilllightningmastery]+[skillfiremastery]+[skillcoldmastery] >= 3 # [tier] == tierscore(item, 100000)",
+ // Helmet - Tals Mask
+ "[name] == deathmask && [quality] == set && [flag] != ethereal # [coldresist]+[lightresist]+[fireresist]+[poisonresist] >= 60 # [tier] == tierscore(item, 100000)",
+ // Belt - Tals Belt
+ "[name] == meshbelt && [quality] == set && [flag] != ethereal # [itemmagicbonus] >= 10 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Tals Armor
+ "[name] == lacqueredplate && [quality] == set # [coldresist] >= 1 # [tier] == 100000",
+ // Final Shield - Sanctuary
+ "[name] == hyperion && [flag] == runeword # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000",
+ // Shield - Mosers
+ "[name] == roundshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 180 # [tier] == tierscore(item, 50000)",
+ // Final Gloves - Perfect Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 && [defense] == 71 # [tier] == 110000",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Tals Ammy
+ "[name] == amulet && [quality] == set # [lightresist] == 33 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Nagelring
+ "[type] == ring && [quality] == unique # [dexterity] == 20 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [itemmagicbonus] == 30 # [tier] == 100000",
+ // Rings - Raven Frost
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 90000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Shield - 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 100000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.EsorbBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.EsorbBuild.js
index 0fb039ba..b09e4aee 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.EsorbBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.EsorbBuild.js
@@ -5,177 +5,217 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Lightning,
- wantedskills: [sdk.skills.FrozenOrb, sdk.skills.ColdMastery],
- usefulskills: [sdk.skills.Telekinesis, sdk.skills.EnergyShield, sdk.skills.StaticField],
- precastSkills: [sdk.skills.FrozenArmor],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", 125],
- ["energy", 150], ["vitality", 150], ["energy", "all"]
- ],
- expansionStats: [
- ["strength", 48], ["vitality", 165], ["strength", 61],
- ["vitality", 252], ["strength", 127], ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Warmth, 1],
- [sdk.skills.FrozenArmor, 1],
- [sdk.skills.StaticField, 1],
- [sdk.skills.Teleport, 10],
- [sdk.skills.EnergyShield, 8],
- [sdk.skills.Telekinesis, 20],
- [sdk.skills.FrozenOrb, 20],
- [sdk.skills.ColdMastery, 17],
- [sdk.skills.EnergyShield, 10],
- [sdk.skills.StaticField, 20],
- ],
- classicTiers: [
- // Weapon - Spectral Shard
- "[name] == blade && [quality] == unique # [fcr] == 20 && [allres] == 10 # [tier] == 100000",
- // Helm - Tarnhelm
- "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == 100000 + tierscore(item)",
- // Shield
- "[type] == shield && [quality] >= magic # [sorceressskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Amulet
- "[type] == amulet && [quality] >= magic # [sorceressskills] == 2 && [fcr] == 10 # [tier] == 100000 + tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == 100000 + tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- ],
- expansionTiers: [
- // Weapon - Tals Orb
- "[name] == swirlingcrystal && [quality] == set && [flag] != ethereal # [skilllightningmastery]+[skillfiremastery]+[skillcoldmastery] >= 3 # [tier] == 100000 + tierscore(item)",
- // Helmet - Tals Mask
- "[name] == deathmask && [quality] == set && [flag] != ethereal # [coldresist]+[lightresist]+[fireresist]+[poisonresist] >= 60 # [tier] == 100000 + tierscore(item)",
- // Belt - Tals Belt
- "[name] == meshbelt && [quality] == set && [flag] != ethereal # [itemmagicbonus] >= 10 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Tals Armor
- "[name] == lacqueredplate && [quality] == set # [coldresist] >= 1 # [tier] == 100000",
- // Final Shield - Sanctuary
- "[name] == hyperion && [flag] == runeword # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000",
- // Shield - Mosers
- "[name] == roundshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 180 # [tier] == 50000 + tierscore(item)",
- // Final Gloves - Perfect Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Tals Ammy
- "[name] == amulet && [quality] == set # [lightresist] == 33 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Nagelring
- "[type] == ring && [quality] == unique # [dexterity] == 20 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [itemmagicbonus] == 30 # [tier] == 100000",
- // Rings - Raven Frost
- "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 90000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Shield - 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 100000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Lightning,
+ wantedskills: [sdk.skills.FrozenOrb, sdk.skills.ColdMastery],
+ usefulskills: [sdk.skills.Telekinesis, sdk.skills.EnergyShield, sdk.skills.StaticField],
+ precastSkills: [sdk.skills.FrozenArmor],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ skills: [
+ [sdk.skills.Warmth, 1],
+ [sdk.skills.FrozenArmor, 1],
+ [sdk.skills.StaticField, 1],
+ [sdk.skills.Teleport, 10],
+ [sdk.skills.EnergyShield, 8],
+ [sdk.skills.Telekinesis, 20],
+ [sdk.skills.FrozenOrb, 20],
+ [sdk.skills.ColdMastery, 17],
+ [sdk.skills.EnergyShield, 10],
+ [sdk.skills.StaticField, 20],
+ ],
+ stats: [],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- SkillerLight: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Lightning) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerCold: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
-
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.FrozenOrb, sdk.skills.StaticField, sdk.skills.FrozenOrb, sdk.skills.StaticField, -1, -1];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["cold"];
- Config.HPBuffer = me.expansion ? 1 : 5;
- Config.MPBuffer = me.expansion ? 1 : 5;
- }
- },
- },
+ SkillerLight: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Lightning) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.haveAll([
- { name: sdk.locale.items.TalRashasBelt, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasAmulet, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasArmor, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasOrb, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasHelmet, quality: sdk.items.quality.Set },
- ]) && me.hell && me.baal;
- }
- },
+ SkillerCold: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.FrozenOrb, sdk.skills.StaticField,
+ sdk.skills.FrozenOrb, sdk.skills.StaticField,
+ -1, -1
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["cold"];
+ Config.HPBuffer = me.expansion ? 1 : 5;
+ Config.MPBuffer = me.expansion ? 1 : 5;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Telekinesis, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.haveAll([
+ { name: sdk.locale.items.TalRashasBelt, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasAmulet, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasArmor, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasOrb, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasHelmet, quality: sdk.items.quality.Set },
+ ]) && me.hell && me.baal;
+ }
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
+ active: function () {
+ return this.respec() && me.getSkill(sdk.skills.Telekinesis, sdk.skills.subindex.HardPoints) === 20;
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", 125],
+ ["energy", 150], ["vitality", 150], ["energy", "all"]
+ ]
+ : [
+ ["strength", 48], ["vitality", 165], ["strength", 61],
+ ["vitality", 252], ["strength", 127], ["dexterity", "block"], ["vitality", "all"]
+ ];
+
+ let finalGear = me.classic
+ ? [
+ // Weapon - Spectral Shard
+ "[name] == blade && [quality] == unique # [fcr] == 20 && [allres] == 10 # [tier] == 100000",
+ // Helm - Tarnhelm
+ "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == tierscore(item, 100000)",
+ // Shield
+ "[type] == shield && [quality] >= magic # [sorceressskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Amulet
+ "[type] == amulet && [quality] >= magic # [sorceressskills] == 2 && [fcr] == 10 # [tier] == tierscore(item, 100000)",
+ // Boots
+ "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == tierscore(item, 100000)",
+ // Belt
+ "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ ]
+ : [
+ // Weapon - Tals Orb
+ "[name] == swirlingcrystal && [quality] == set && [flag] != ethereal # [skilllightningmastery]+[skillfiremastery]+[skillcoldmastery] >= 3 # [tier] == tierscore(item, 100000)",
+ // Helmet - Tals Mask
+ "[name] == deathmask && [quality] == set && [flag] != ethereal # [coldresist]+[lightresist]+[fireresist]+[poisonresist] >= 60 # [tier] == tierscore(item, 100000)",
+ // Belt - Tals Belt
+ "[name] == meshbelt && [quality] == set && [flag] != ethereal # [itemmagicbonus] >= 10 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Tals Armor
+ "[name] == lacqueredplate && [quality] == set # [coldresist] >= 1 # [tier] == 100000",
+ // Final Shield - Sanctuary
+ "[name] == hyperion && [flag] == runeword # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000",
+ // Shield - Mosers
+ "[name] == roundshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 180 # [tier] == tierscore(item, 50000)",
+ // Final Gloves - Perfect Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 && [defense] == 71 # [tier] == 110000",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Tals Ammy
+ "[name] == amulet && [quality] == set # [lightresist] == 33 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Nagelring
+ "[type] == ring && [quality] == unique # [dexterity] == 20 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [itemmagicbonus] == 30 # [tier] == 100000",
+ // Rings - Raven Frost
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 90000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Shield - 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 100000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.LevelingBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.LevelingBuild.js
index b04fac04..d41efc74 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.LevelingBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.LevelingBuild.js
@@ -5,79 +5,105 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: sdk.skills.tabs.Cold,
- wantedskills: [sdk.skills.Blizzard, sdk.skills.FireBall, sdk.skills.ColdMastery],
- usefulskills: [sdk.skills.GlacialSpike, sdk.skills.Meteor, sdk.skills.FireMastery, sdk.skills.StaticField],
- usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery, sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
- ],
- expansionStats: [
- ["energy", 50], ["strength", 48], ["vitality", 165],
- ["strength", 61], ["vitality", 200], ["strength", 127],
- ["vitality", 252], ["dexterity", "block"], ["vitality", "all"]
- ],
- classicSkills: [
- // Total skills at respec = 70
- [sdk.skills.Warmth, 1], // points left 69
- [sdk.skills.FrozenArmor, 1], // points left 68
- [sdk.skills.StaticField, 6], // points left 62
- [sdk.skills.Teleport, 4], // points left 57
- [sdk.skills.Meteor, 8], // points left 44
- [sdk.skills.FireMastery, 1], // points left 43
- [sdk.skills.ColdMastery, 1], // points left 42
- [sdk.skills.FrozenOrb, 1], // points left 36
- [sdk.skills.FireBall, 10], // points left 27
- [sdk.skills.Blizzard, 20], // points left 8
- [sdk.skills.IceBlast, 12], // points left 0
- [sdk.skills.Meteor, 10],
- [sdk.skills.IceBlast, 20],
- [sdk.skills.ColdMastery, 17],
- [sdk.skills.FireBolt, 20],
- ],
- expansionSkills: [
- // Total skills at respec = 70
- [sdk.skills.Warmth, 1], // points left 69
- [sdk.skills.FrozenArmor, 1], // points left 68
- [sdk.skills.StaticField, 1], // points left 67
- [sdk.skills.Teleport, 1], // points left 65
- [sdk.skills.Meteor, 1], // points left 59
- [sdk.skills.FireMastery, 1], // points left 58
- [sdk.skills.ColdMastery, 1], // points left 57
- [sdk.skills.FrozenOrb, 1], // points left 51
- [sdk.skills.FireBall, 20], // points left 32
- [sdk.skills.Blizzard, 20], // points left 13
- [sdk.skills.IceBlast, 15], // points left 0
- [sdk.skills.Meteor, 15],
- [sdk.skills.IceBlast, 20],
- [sdk.skills.Meteor, 20],
- [sdk.skills.ColdMastery, 5],
- [sdk.skills.FireBolt, 20],
- ],
- stats: undefined,
- skills: undefined,
- active: function () {
- return (me.charlvl > CharInfo.respecOne && me.charlvl > CharInfo.respecTwo && me.checkSkill(sdk.skills.FireMastery, sdk.skills.subindex.HardPoints) && !Check.finalBuild().active());
- },
-};
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Cold,
+ wantedskills: [sdk.skills.Blizzard, sdk.skills.FireBall, sdk.skills.ColdMastery],
+ usefulskills: [sdk.skills.GlacialSpike, sdk.skills.Meteor, sdk.skills.FireMastery, sdk.skills.StaticField],
+ usefulStats: [
+ sdk.stats.PassiveColdPierce,
+ sdk.stats.PassiveColdMastery,
+ sdk.stats.PassiveFireMastery,
+ sdk.stats.PassiveFirePierce
+ ],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [],
+ skills: [],
-// Has to be set after its loaded
-build.stats = me.classic ? build.classicStats : build.expansionStats;
-build.skills = me.classic ? build.classicSkills : build.expansionSkills;
+ active: function () {
+ const { respecOne, respecTwo } = CharInfo;
+ return (
+ me.charlvl > respecOne && me.charlvl > respecTwo
+ && me.checkSkill(sdk.skills.FireMastery, sdk.skills.subindex.HardPoints)
+ && !Check.finalBuild().active()
+ );
+ },
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.Blizzard, sdk.skills.IceBlast, sdk.skills.Blizzard, sdk.skills.IceBlast, sdk.skills.Meteor, sdk.skills.FireBall];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["fire and cold"];
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = me.expansion ? 2 : 5;
- Config.MPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
- SetUp.belt();
-});
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Blizzard, sdk.skills.IceBlast,
+ sdk.skills.Blizzard, sdk.skills.IceBlast,
+ sdk.skills.Meteor, sdk.skills.FireBall,
+ sdk.skills.Nova, sdk.skills.StaticField
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["fire and cold"];
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = me.expansion ? 2 : 5;
+ Config.MPBuffer = me.expansion && me.charlvl < 80 ? 6 : me.classic ? 5 : 2;
+ SetUp.belt();
+ }
+ }
+ },
+ };
+
+ // Has to be set after its loaded
+ build.stats = me.classic
+ ? [
+ ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
+ ] : [
+ ["energy", 50], ["strength", 48], ["vitality", 165],
+ ["strength", 61], ["vitality", 200], ["strength", 127],
+ ["vitality", 252], ["dexterity", "block"], ["vitality", "all"]
+ ];
+
+ build.skills = me.classic
+ ? [
+ // Total skills at respec = 70
+ [sdk.skills.Warmth, 1], // points left 69
+ [sdk.skills.FrozenArmor, 1], // points left 68
+ [sdk.skills.StaticField, 6], // points left 62
+ [sdk.skills.Teleport, 4], // points left 57
+ [sdk.skills.Meteor, 8], // points left 44
+ [sdk.skills.FireMastery, 1], // points left 43
+ [sdk.skills.ColdMastery, 1], // points left 42
+ [sdk.skills.FrozenOrb, 1], // points left 36
+ [sdk.skills.FireBall, 10], // points left 27
+ [sdk.skills.Blizzard, 20], // points left 8
+ [sdk.skills.IceBlast, 12], // points left 0
+ [sdk.skills.Meteor, 10],
+ [sdk.skills.IceBlast, 20],
+ [sdk.skills.ColdMastery, 17],
+ [sdk.skills.FireBolt, 20],
+ ] : [
+ // Total skills at respec = 70
+ [sdk.skills.Warmth, 1], // points left 69
+ [sdk.skills.FrozenArmor, 1], // points left 68
+ [sdk.skills.StaticField, 1], // points left 67
+ [sdk.skills.Teleport, 1], // points left 65
+ [sdk.skills.Meteor, 1], // points left 59
+ [sdk.skills.FireMastery, 1], // points left 58
+ [sdk.skills.ColdMastery, 1], // points left 57
+ [sdk.skills.LightningMastery, 1], // points left 56
+ [sdk.skills.Nova, 1], // points left 55
+ [sdk.skills.FrozenOrb, 1], // points left 51
+ [sdk.skills.FireBall, 20], // points left 32
+ [sdk.skills.Blizzard, 20], // points left 13
+ [sdk.skills.Nova, 5], // points left 8
+ [sdk.skills.IceBlast, 15], // points left 0
+ [sdk.skills.Meteor, 15],
+ [sdk.skills.IceBlast, 20],
+ [sdk.skills.Meteor, 20],
+ [sdk.skills.ColdMastery, 5],
+ [sdk.skills.FireBolt, 20],
+ ];
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.LightningBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.LightningBuild.js
index 29168b57..23410a79 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.LightningBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.LightningBuild.js
@@ -5,142 +5,173 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Lightning,
- wantedskills: [sdk.skills.ChainLightning, sdk.skills.Lightning],
- usefulskills: [sdk.skills.LightningMastery, sdk.skills.ChargedBolt, sdk.skills.Nova],
- precastSkills: [sdk.skills.FrozenArmor],
- usefulStats: [sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["strength", 156], ["dexterity", 35], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Warmth, 1],
- [sdk.skills.StaticField, 1],
- [sdk.skills.Teleport, 1],
- [sdk.skills.FrozenArmor, 1],
- [sdk.skills.ThunderStorm, 1],
- [sdk.skills.LightningMastery, 1],
- [sdk.skills.Lightning, 20],
- [sdk.skills.ChainLightning, 20],
- [sdk.skills.LightningMastery, 20], // lvl 69 w/o quest skill pts
- [sdk.skills.Nova, 20],
- [sdk.skills.ChargedBolt, 20],
- ],
- autoEquipTiers: [ // autoequip final gear
- // Weapon - HotO
- "[type] == mace && [flag] == runeword # [fcr] == 40 # [tier] == 100000",
- // Helmet - Harlequin's Crest
- "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] >= 10 # [tier] == 100000 + tierscore(item)",
- // Belt - Arach's
- "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == 100000 + tierscore(item)",
- // Boots
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)", // War Traveler
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)", // Sandstorm Treks
- // Armor - CoH
- "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
- // Shield - Spirit
- "[type] == shield # [fcr] >= 25 && [maxmana] >= 89 # [tier] == 100000 + tierscore(item)",
- // Final Gloves - Perfect 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - 2x Upp'ed Magefist
- "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Maras
- "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == 100000 + tierscore(item)",
- // Final Rings - SoJ & Perfect Wisp
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- "[name] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == 100000 + tierscore(item)",
- // Rings - Wisp
- "[name] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == 90000 + tierscore(item)",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Final Shield - Spirit
- "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
- // Switch Temporary Shield - 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 50000 + tierscore(item)",
- // Merc Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Lightning,
+ wantedskills: [sdk.skills.ChainLightning, sdk.skills.Lightning],
+ usefulskills: [sdk.skills.LightningMastery, sdk.skills.ChargedBolt, sdk.skills.Nova],
+ precastSkills: [sdk.skills.FrozenArmor],
+ usefulStats: [sdk.stats.PassiveLightningMastery, sdk.stats.PassiveLightningPierce],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["strength", 156], ["dexterity", 35], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.Warmth, 1],
+ [sdk.skills.StaticField, 1],
+ [sdk.skills.Teleport, 1],
+ [sdk.skills.FrozenArmor, 1],
+ [sdk.skills.ThunderStorm, 1],
+ [sdk.skills.LightningMastery, 1],
+ [sdk.skills.Lightning, 20],
+ [sdk.skills.ChainLightning, 20],
+ [sdk.skills.LightningMastery, 20], // lvl 69 w/o quest skill pts
+ [sdk.skills.Nova, 20],
+ [sdk.skills.ChargedBolt, 20],
+ ],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
+ }
+ },
- ResFHR: {
- max: 1,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ & check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
+ }
+ },
- LifeMana: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.MaxHp) === 20 && check.getStat(sdk.stats.MaxMana) === 17);
- }
- },
+ ResFHR: {
+ max: 1,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
+ }
+ },
- Skiller: {
- max: 2,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Lightning) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
+ LifeMana: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.getStat(sdk.stats.MaxHp) === 20 && check.getStat(sdk.stats.MaxMana) === 17);
+ }
+ },
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Lightning, sdk.skills.ChainLightning, sdk.skills.ChainLightning, sdk.skills.Lightning, -1, -1];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["lightning"];
- }
- },
- },
+ Skiller: {
+ max: 2,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (!check.unique && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Lightning) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40);
+ }
+ },
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return (Attack.checkInfinity() || (myData.merc.gear.includes(sdk.locale.items.Infinity) && !Misc.poll(() => me.getMerc(), 200, 50)));
- }
- },
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Lightning, sdk.skills.ChainLightning,
+ sdk.skills.ChainLightning, sdk.skills.Lightning,
+ 1, -1
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["lightning"];
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Lightning, sdk.skills.subindex.HardPoints) === 20 && !me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints);
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return (Attack.checkInfinity()
+ || (
+ me.data.merc.gear.includes(sdk.locale.items.Infinity)
+ && !Misc.poll(() => me.getMerc(), 200, 50)
+ ));
+ }
+ },
+
+ active: function () {
+ return this.respec()
+ && me.getSkill(sdk.skills.Lightning, sdk.skills.subindex.HardPoints) === 20
+ && !me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints);
+ },
+ };
+
+ // autoequip final gear
+ let finalGear = [
+ // Weapon - HotO
+ "[type] == mace && [flag] == runeword # [fcr] == 40 # [tier] == 100000",
+ // Helmet - Harlequin's Crest
+ "[name] == shako && [quality] == unique && [flag] != ethereal # [damageresist] >= 10 # [tier] == tierscore(item, 100000)",
+ // Belt - Arach's
+ "[name] == spiderwebsash && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 90 # [tier] == tierscore(item, 100000)",
+ // Boots
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)", // War Traveler
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)", // Sandstorm Treks
+ // Armor - CoH
+ "[type] == armor && [flag] == runeword && [flag] != ethereal # [fireresist] == 65 && [hpregen] == 7 # [tier] == 100000",
+ // Shield - Spirit
+ "[type] == shield # [fcr] >= 25 && [maxmana] >= 89 # [tier] == tierscore(item, 100000)",
+ // Final Gloves - Perfect 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
+ // Gloves - 2x Upp'ed Magefist
+ "[name] == crusadergauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Maras
+ "[type] == amulet && [quality] == unique # [strength] == 5 && [coldresist] >= 30 # [tier] == tierscore(item, 100000)",
+ // Final Rings - SoJ & Perfect Wisp
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ "[name] == ring && [quality] == unique # [itemabsorblightpercent] == 20 # [tier] == tierscore(item, 110000)",
+ // Rings - Wisp
+ "[name] == ring && [quality] == unique # [itemabsorblightpercent] >= 10 # [tier] == tierscore(item, 100000)",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Final Shield - Spirit
+ "[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000",
+ // Switch Temporary Shield - 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 50000)",
+ // Merc Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.MeteorbBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.MeteorbBuild.js
index de40d1c7..af9e17ae 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.MeteorbBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.MeteorbBuild.js
@@ -5,180 +5,229 @@
*
*/
-const finalBuild = {
- caster: true,
- skillstab: sdk.skills.tabs.Fire,
- wantedskills: [sdk.skills.FrozenOrb, sdk.skills.Meteor, sdk.skills.ColdMastery],
- usefulskills: [sdk.skills.FireBall, sdk.skills.FireMastery, sdk.skills.StaticField],
- precastSkills: [sdk.skills.FrozenArmor],
- usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery, sdk.stats.PassiveFireMastery, sdk.stats.PassiveFirePierce],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
- ],
- expansionStats: [
- ["strength", 48], ["vitality", 165], ["strength", 61],
- ["vitality", 252], ["strength", 127], ["dexterity", "block"], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.Warmth, 1],
- [sdk.skills.FrozenArmor, 1],
- [sdk.skills.StaticField, 1],
- [sdk.skills.Teleport, 1],
- [sdk.skills.FireMastery, 1],
- [sdk.skills.ColdMastery, 1],
- [sdk.skills.FireBall, 14],
- [sdk.skills.Meteor, 20],
- [sdk.skills.FrozenOrb, 20], // 71 points w/o quest skill pts
- [sdk.skills.ColdMastery, 12],
- [sdk.skills.FireBall, 20],
- [sdk.skills.FireMastery, 20],
- [sdk.skills.FireBolt, 20],
- ],
- classicTiers: [
- // Weapon - Spectral Shard
- "[name] == blade && [quality] == unique # [fcr] == 20 && [allres] == 10 # [tier] == 100000",
- // Helm - Tarnhelm
- "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == 100000 + tierscore(item)",
- // Shield
- "[type] == shield && [quality] >= magic # [sorceressskills] == 2 && [allres] >= 16 # [tier] == 100000 + tierscore(item)",
- // Rings - SoJ
- "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
- // Amulet
- "[type] == amulet && [quality] >= magic # [sorceressskills] == 2 && [fcr] == 10 # [tier] == 100000 + tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == 100000 + tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- ],
- expansionTiers: [
- // Weapon - Tals Orb
- "[name] == swirlingcrystal && [quality] == set && [flag] != ethereal # [skilllightningmastery]+[skillfiremastery]+[skillcoldmastery] >= 3 # [tier] == 100000 + tierscore(item)",
- // Helmet - Tals Mask
- "[name] == deathmask && [quality] == set && [flag] != ethereal # [coldresist]+[lightresist]+[fireresist]+[poisonresist] >= 60 # [tier] == 100000 + tierscore(item)",
- // Belt - Tals Belt
- "[name] == meshbelt && [quality] == set && [flag] != ethereal # [itemmagicbonus] >= 10 # [tier] == 100000 + tierscore(item)",
- // Final Boots - Sandstorm Treks
- "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == 100000 + tierscore(item)",
- // Boots - War Traveler
- "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == 5000 + tierscore(item)",
- // Armor - Tals Armor
- "[name] == lacqueredplate && [quality] == set # [coldresist] >= 1 # [tier] == 100000",
- // Final Shield - Sanctuary
- "[name] == hyperion && [flag] == runeword # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000",
- // Shield - Mosers
- "[name] == roundshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 180 # [tier] == 50000 + tierscore(item)",
- // Final Gloves - Perfect Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 # [tier] == 110000",
- // Gloves - Upp'ed Magefist
- "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Gloves - Magefist
- "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == 100000 + tierscore(item)",
- // Amulet - Tals Ammy
- "[name] == amulet && [quality] == set # [lightresist] == 33 # [tier] == 100000",
- // Final Rings - Perfect Raven Frost & Nagelring
- "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 100000",
- "[type] == ring && [quality] == unique # [itemmagicbonus] >= 30 # [tier] == 100000",
- // Rings - Raven Frost
- "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 100000",
- // Switch - CTA
- "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
- // Switch Shield - 1+ all skill
- "[type] == shield # [itemallskills] >= 1 # [secondarytier] == 100000 + tierscore(item)",
- // Merc Final Armor - Fortitude
- "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
- // Merc Final Helmet - Eth Andy's
- "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
- // Merc Helmet - Andy's
- "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
- ],
- stats: undefined,
- autoEquipTiers: undefined,
- charms: {
- ResLife: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MaxHp) === 20);
- }
- },
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Fire,
+ wantedskills: [sdk.skills.FrozenOrb, sdk.skills.Meteor, sdk.skills.ColdMastery],
+ usefulskills: [sdk.skills.FireBall, sdk.skills.FireMastery, sdk.skills.StaticField],
+ precastSkills: [sdk.skills.FrozenArmor],
+ usefulStats: [
+ sdk.stats.PassiveColdPierce,
+ sdk.stats.PassiveColdMastery,
+ sdk.stats.PassiveFireMastery,
+ sdk.stats.PassiveFirePierce
+ ],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ skills: [
+ [sdk.skills.Warmth, 1],
+ [sdk.skills.FrozenArmor, 1],
+ [sdk.skills.StaticField, 1],
+ [sdk.skills.Teleport, 1],
+ [sdk.skills.FireMastery, 1],
+ [sdk.skills.ColdMastery, 1],
+ [sdk.skills.FireBall, 14],
+ [sdk.skills.Meteor, 20],
+ [sdk.skills.FrozenOrb, 20], // 71 points w/o quest skill pts
+ [sdk.skills.ColdMastery, 12],
+ [sdk.skills.FireBall, 20],
+ [sdk.skills.FireMastery, 20],
+ [sdk.skills.FireBolt, 20],
+ ],
+ stats: [],
- ResMf: {
- max: 2,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.MagicBonus) === 7);
- }
- },
+ charms: {
+ ResLife: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MaxHp) === 20
+ );
+ }
+ },
- ResFHR: {
- max: 3,
- have: [],
- classid: sdk.items.SmallCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.allRes === 5 && check.getStat(sdk.stats.FHR) === 5);
- }
- },
+ ResMf: {
+ max: 2,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.MagicBonus) === 7
+ );
+ }
+ },
- SkillerFire: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Fire) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
+ ResFHR: {
+ max: 3,
+ have: [],
+ classid: sdk.items.SmallCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.allRes === 5
+ && check.getStat(sdk.stats.FHR) === 5
+ );
+ }
+ },
- SkillerCold: {
- max: 1,
- have: [],
- classid: sdk.items.GrandCharm,
- stats: function (check) {
- return (!check.unique && check.classid === this.classid && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
- && check.getStat(sdk.stats.MaxHp) >= 40);
- }
- },
- },
-
- AutoBuildTemplate: {
- 1: {
- Update: function () {
- Config.AttackSkill = [-1, sdk.skills.Meteor, sdk.skills.FireBall, sdk.skills.Meteor, sdk.skills.FireBall, sdk.skills.FrozenOrb, sdk.skills.GlacialSpike];
- Config.LowManaSkill = [-1, -1];
- Config.SkipImmune = ["fire and cold"];
- Config.HPBuffer = me.expansion ? 1 : 5;
- Config.MPBuffer = me.expansion ? 1 : 5;
- }
- },
- },
+ SkillerFire: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Fire) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
- respec: function () {
- if (me.classic) {
- return me.charlvl >= 75 && me.diablo;
- } else {
- return me.haveAll([
- { name: sdk.locale.items.TalRashasBelt, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasAmulet, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasArmor, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasOrb, quality: sdk.items.quality.Set },
- { name: sdk.locale.items.TalRashasHelmet, quality: sdk.items.quality.Set },
- ]);
- }
- },
+ SkillerCold: {
+ max: 1,
+ have: [],
+ classid: sdk.items.GrandCharm,
+ /** @param {ItemUnit} check */
+ stats: function (check) {
+ return (
+ !check.unique
+ && check.classid === this.classid
+ && check.getStat(sdk.stats.AddSkillTab, sdk.skills.tabs.Cold) === 1
+ && check.getStat(sdk.stats.MaxHp) >= 40
+ );
+ }
+ },
+ },
+
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Meteor, sdk.skills.FireBall,
+ sdk.skills.Meteor, sdk.skills.FireBall,
+ sdk.skills.FrozenOrb, sdk.skills.GlacialSpike
+ ];
+ Config.LowManaSkill = [-1, -1];
+ Config.SkipImmune = ["fire and cold"];
+ Config.HPBuffer = me.expansion ? 1 : 5;
+ Config.MPBuffer = me.expansion ? 1 : 5;
+ }
+ },
+ },
- active: function () {
- return this.respec() && me.getSkill(sdk.skills.Meteor, sdk.skills.subindex.HardPoints) === 20 && me.getSkill(sdk.skills.FrozenOrb, sdk.skills.subindex.HardPoints) === 20;
- },
-};
+ respec: function () {
+ if (me.classic) {
+ return me.charlvl >= 75 && me.diablo;
+ } else {
+ return me.haveAll([
+ { name: sdk.locale.items.TalRashasBelt, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasAmulet, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasArmor, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasOrb, quality: sdk.items.quality.Set },
+ { name: sdk.locale.items.TalRashasHelmet, quality: sdk.items.quality.Set },
+ ]);
+ }
+ },
-// Has to be set after its loaded
-finalBuild.stats = me.classic ? finalBuild.classicStats : finalBuild.expansionStats;
-finalBuild.autoEquipTiers = me.classic ? finalBuild.classicTiers : finalBuild.expansionTiers;
+ active: function () {
+ return (
+ this.respec()
+ && me.getSkill(sdk.skills.Meteor, sdk.skills.subindex.HardPoints) === 20
+ && me.getSkill(sdk.skills.FrozenOrb, sdk.skills.subindex.HardPoints) === 20
+ );
+ },
+ };
+
+ build.stats = me.classic
+ ? [
+ ["dexterity", 51], ["strength", 80], ["energy", 100], ["vitality", "all"]
+ ]
+ : [
+ ["strength", 48], ["vitality", 165], ["strength", 61],
+ ["vitality", 252], ["strength", 127], ["dexterity", "block"], ["vitality", "all"]
+ ];
+
+ let finalGear = me.classic
+ ? [
+ // Weapon - Spectral Shard
+ "[name] == blade && [quality] == unique # [fcr] == 20 && [allres] == 10 # [tier] == 100000",
+ // Helm - Tarnhelm
+ "[name] == skullcap && [quality] == unique # [itemallskills] == 1 && [itemmagicbonus] >= 25 # [tier] == tierscore(item, 100000)",
+ // Shield
+ "[type] == shield && [quality] >= magic # [sorceressskills] == 2 && [allres] >= 16 # [tier] == tierscore(item, 100000)",
+ // Rings - SoJ
+ "[type] == ring && [quality] == unique # [itemmaxmanapercent] == 25 # [tier] == 100000",
+ // Amulet
+ "[type] == amulet && [quality] >= magic # [sorceressskills] == 2 && [fcr] == 10 # [tier] == tierscore(item, 100000)",
+ // Boots
+ "[type] == boots && [quality] >= magic # [frw] >= 20 && [fhr] == 10 && [coldresist]+[lightresist] >= 10 # [tier] == tierscore(item, 100000)",
+ // Belt
+ "[type] == belt && [quality] >= magic # [fhr] >= 20 && [maxhp] >= 40 && [fireresist]+[lightresist] >= 20 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique # [fcr] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ ]
+ : [
+ // Weapon - Tals Orb
+ "[name] == swirlingcrystal && [quality] == set && [flag] != ethereal # [skilllightningmastery]+[skillfiremastery]+[skillcoldmastery] >= 3 # [tier] == tierscore(item, 100000)",
+ // Helmet - Tals Mask
+ "[name] == deathmask && [quality] == set && [flag] != ethereal # [coldresist]+[lightresist]+[fireresist]+[poisonresist] >= 60 # [tier] == tierscore(item, 100000)",
+ // Belt - Tals Belt
+ "[name] == meshbelt && [quality] == set && [flag] != ethereal # [itemmagicbonus] >= 10 # [tier] == tierscore(item, 100000)",
+ // Final Boots - Sandstorm Treks
+ "[name] == scarabshellboots && [quality] == unique # [strength]+[vitality] >= 20 # [tier] == tierscore(item, 100000)",
+ // Boots - War Traveler
+ "[name] == battleboots && [quality] == unique && [flag] != ethereal # [itemmagicbonus] >= 50 # [tier] == tierscore(item, 5000)",
+ // Armor - Tals Armor
+ "[name] == lacqueredplate && [quality] == set # [coldresist] >= 1 # [tier] == 100000",
+ // Final Shield - Sanctuary
+ "[name] == hyperion && [flag] == runeword # [fhr] >= 20 && [enhanceddefense] >= 130 && [fireresist] >= 50 # [tier] == 100000",
+ // Shield - Mosers
+ "[name] == roundshield && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 180 # [tier] == tierscore(item, 50000)",
+ // Final Gloves - Perfect Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] == 30 && [addfireskills] == 1 && [defense] == 71 # [tier] == 110000",
+ // Gloves - Upp'ed Magefist
+ "[name] == battlegauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Gloves - Magefist
+ "[name] == lightgauntlets && [quality] == unique && [flag] != ethereal # [enhanceddefense] >= 20 && [addfireskills] == 1 # [tier] == tierscore(item, 100000)",
+ // Amulet - Tals Ammy
+ "[name] == amulet && [quality] == set # [lightresist] == 33 # [tier] == 100000",
+ // Final Rings - Perfect Raven Frost & Nagelring
+ "[type] == ring && [quality] == unique # [dexterity] >= 20 # [tier] == 100000",
+ "[type] == ring && [quality] == unique # [itemmagicbonus] >= 30 # [tier] == 100000",
+ // Rings - Raven Frost
+ "[type] == ring && [quality] == unique # [dexterity] >= 15 # [tier] == 100000",
+ // Switch - CTA
+ "[minimumsockets] >= 5 && [flag] == runeword # [plusskillbattleorders] >= 1 # [secondarytier] == 100000",
+ // Switch Shield - 1+ all skill
+ "[type] == shield # [itemallskills] >= 1 # [secondarytier] == tierscore(item, 100000)",
+ // Merc Final Armor - Fortitude
+ "[type] == armor && [flag] == runeword # [enhanceddefense] >= 200 && [enhanceddamage] >= 300 # [merctier] == 100000",
+ // Merc Final Helmet - Eth Andy's
+ "[name] == demonhead && [quality] == unique && [flag] == ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 50000 + mercscore(item)",
+ // Merc Helmet - Andy's
+ "[name] == demonhead && [quality] == unique && [flag] != ethereal # [strength] >= 25 && [enhanceddefense] >= 100 # [merctier] == 40000 + mercscore(item)",
+ ];
+
+ NTIP.buildList(finalGear);
+ NTIP.buildFinalGear(finalGear);
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.StartBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.StartBuild.js
index b6bc6c58..cb0d0753 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.StartBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.StartBuild.js
@@ -5,86 +5,94 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: sdk.skills.tabs.Lightning,
- wantedskills: [sdk.skills.ChargedBolt, sdk.skills.StaticField],
- usefulskills: [sdk.skills.FrozenArmor, sdk.skills.Lightning],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- stats: [
- ["energy", 40], ["vitality", 15], ["energy", 45],
- ["vitality", 20], ["energy", 50], ["strength", 15],
- ["vitality", 25], ["energy", 60], ["vitality", 40],
- ["strength", 35], ["vitality", "all"]
- ],
- skills: [
- [sdk.skills.ChargedBolt, 3, false], // charlvl 4
- [sdk.skills.IceBolt, 1], // charlvl 5
- [sdk.skills.FrozenArmor, 1], // charlvl 6
- [sdk.skills.Telekinesis, 1], // charlvl 7
- [sdk.skills.FrostNova, 1], // charlvl 8
- [sdk.skills.StaticField, 1, false],
- [sdk.skills.IceBlast, 1, false],
- [sdk.skills.StaticField, 4], // charlvl 10
- [sdk.skills.Teleport, 1, false], // charlvl 18
- [sdk.skills.Nova, 7], // charlvl 17
- [sdk.skills.StaticField, 6], // charlvl 20
- [sdk.skills.IceBlast, 1], // charlvl 22
- [sdk.skills.GlacialSpike, 1], // charlvl
- [sdk.skills.IceBlast, 4, false], // charlvl 23
- [sdk.skills.Blizzard, 6, false], // charlvl 29 (never gets here)
- [sdk.skills.ColdMastery, 1, false], // charlvl 30 (never gets here)
- ],
- active: function () {
- return me.charlvl < CharInfo.respecOne && !me.checkSkill(sdk.skills.ColdMastery, sdk.skills.subindex.HardPoints);
- },
-};
+(function (module, require) {
+ module.exports = (function () {
+ const build = {
+ AutoBuildTemplate: {},
+ caster: true,
+ skillstab: sdk.skills.tabs.Lightning,
+ wantedskills: [sdk.skills.ChargedBolt, sdk.skills.StaticField],
+ usefulskills: [sdk.skills.FrozenArmor, sdk.skills.Lightning],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [
+ ["energy", 40], ["vitality", 15], ["energy", 45],
+ ["vitality", 20], ["energy", 50], ["strength", 15],
+ ["vitality", 25], ["energy", 60], ["vitality", 40],
+ ["strength", 35], ["vitality", "all"]
+ ],
+ skills: [
+ [sdk.skills.ChargedBolt, 3, false], // charlvl 4
+ [sdk.skills.IceBolt, 1], // charlvl 5
+ [sdk.skills.FrozenArmor, 1], // charlvl 6
+ [sdk.skills.Telekinesis, 1], // charlvl 7
+ [sdk.skills.FrostNova, 1], // charlvl 8
+ [sdk.skills.StaticField, 1, false],
+ [sdk.skills.IceBlast, 1, false],
+ [sdk.skills.StaticField, 4], // charlvl 10
+ [sdk.skills.Teleport, 1, false], // charlvl 18
+ [sdk.skills.Nova, 7], // charlvl 17
+ [sdk.skills.StaticField, 6], // charlvl 20
+ [sdk.skills.IceBlast, 1], // charlvl 22
+ [sdk.skills.GlacialSpike, 1], // charlvl
+ [sdk.skills.IceBlast, 4, false], // charlvl 23
+ [sdk.skills.Blizzard, 6, false], // charlvl 29 (never gets here)
+ [sdk.skills.ColdMastery, 1, false], // charlvl 30 (never gets here)
+ ],
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
- Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
- Config.FieldID.UsedSpace = 0;
+ active: function () {
+ const { respecOne } = CharInfo;
+ return me.charlvl < respecOne && !me.checkSkill(sdk.skills.ColdMastery, sdk.skills.subindex.HardPoints);
+ },
+ };
- Config.BeltColumn = ["hp", "hp", "hp", "hp"];
- Config.HPBuffer = 4;
- Config.MPBuffer = 10;
- Config.AttackSkill = [-1, sdk.skills.FireBolt, -1, sdk.skills.FireBolt, -1, 0, 0];
- Config.LowManaSkill = [0, 0];
- SetUp.belt();
-});
-build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
- Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
- Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
- Config.FieldID.UsedSpace = 0;
+ const { buildAutoBuildTempObj } = require("../../Utils/General");
+
+ build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
+ Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
+ Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
+ Config.FieldID.UsedSpace = 0;
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.LowManaSkill = [0, 0];
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = 4;
- Config.MPBuffer = 10;
- if (me.checkSkill(sdk.skills.IceBlast, sdk.skills.subindex.SoftPoints)) {
- Config.AttackSkill = [-1, sdk.skills.ChargedBolt, sdk.skills.IceBlast, sdk.skills.ChargedBolt, sdk.skills.IceBlast, sdk.skills.IceBlast, 0];
- } else if (me.checkSkill(sdk.skills.IceBolt, sdk.skills.subindex.SoftPoints)) {
- Config.AttackSkill = [-1, sdk.skills.ChargedBolt, sdk.skills.IceBolt, sdk.skills.ChargedBolt, sdk.skills.IceBolt, sdk.skills.IceBolt, 0];
- } else {
- let secondSkill = Skill.canUse(sdk.skills.FireBolt) ? sdk.skills.FireBolt : sdk.skills.Attack;
- Config.AttackSkill = [-1, sdk.skills.ChargedBolt, secondSkill, sdk.skills.ChargedBolt, secondSkill, 0, 0];
- }
- SetUp.belt();
-});
-build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
- Config.HPBuffer = 4;
- Config.MPBuffer = 8;
- Config.AttackSkill = [-1, sdk.skills.Nova, sdk.skills.ChargedBolt, sdk.skills.Nova, sdk.skills.ChargedBolt, sdk.skills.FrostNova, sdk.skills.IceBlast];
- Config.DodgeHP = 50;
- Config.DodgeRange = me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.SoftPoints) ? 15 : 7;
-});
-build.AutoBuildTemplate[24] = buildAutoBuildTempObj(() => {
- if (me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints)) {
- Config.AttackSkill = [-1, sdk.skills.Blizzard, sdk.skills.Nova, sdk.skills.Blizzard, sdk.skills.Nova, sdk.skills.Nova, sdk.skills.ChargedBolt];
- }
-});
+ Config.BeltColumn = ["hp", "hp", "hp", "hp"];
+ Config.HPBuffer = 4;
+ Config.MPBuffer = 10;
+ Config.AttackSkill = [-1, sdk.skills.FireBolt, -1, sdk.skills.FireBolt, -1, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[2] = buildAutoBuildTempObj(() => {
+ Config.ScanShrines.indexOf(sdk.shrines.Combat) === -1 && Config.ScanShrines.push(sdk.shrines.Combat);
+ Config.FieldID.Enabled = !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed);
+ Config.FieldID.UsedSpace = 0;
+
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.LowManaSkill = [0, 0];
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = 4;
+ Config.MPBuffer = 10;
+ if (me.checkSkill(sdk.skills.IceBlast, sdk.skills.subindex.SoftPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.ChargedBolt, sdk.skills.IceBlast, sdk.skills.ChargedBolt, sdk.skills.IceBlast, sdk.skills.IceBlast, 0];
+ } else if (me.checkSkill(sdk.skills.IceBolt, sdk.skills.subindex.SoftPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.ChargedBolt, sdk.skills.IceBolt, sdk.skills.ChargedBolt, sdk.skills.IceBolt, sdk.skills.IceBolt, 0];
+ } else {
+ let secondSkill = Skill.canUse(sdk.skills.FireBolt) ? sdk.skills.FireBolt : sdk.skills.Attack;
+ Config.AttackSkill = [-1, sdk.skills.ChargedBolt, secondSkill, sdk.skills.ChargedBolt, secondSkill, 0, 0];
+ }
+ SetUp.belt();
+ });
+ build.AutoBuildTemplate[12] = buildAutoBuildTempObj(() => {
+ Config.HPBuffer = 4;
+ Config.MPBuffer = 8;
+ Config.AttackSkill = [-1, sdk.skills.Nova, sdk.skills.ChargedBolt, sdk.skills.Nova, sdk.skills.ChargedBolt, sdk.skills.FrostNova, sdk.skills.IceBlast];
+ Config.DodgeHP = 50;
+ Config.DodgeRange = me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.SoftPoints) ? 15 : 7;
+ });
+ build.AutoBuildTemplate[24] = buildAutoBuildTempObj(() => {
+ if (me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints)) {
+ Config.AttackSkill = [-1, sdk.skills.Blizzard, sdk.skills.Nova, sdk.skills.Blizzard, sdk.skills.Nova, sdk.skills.Nova, sdk.skills.ChargedBolt];
+ }
+ });
+
+ return build;
+ })();
+})(module, require);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.SteppingBuild.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.SteppingBuild.js
index dede47a3..78b5ccc2 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.SteppingBuild.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.SteppingBuild.js
@@ -5,73 +5,101 @@
*
*/
-let build = {
- AutoBuildTemplate: {},
- caster: true,
- skillstab: sdk.skills.tabs.Cold,
- wantedskills: [sdk.skills.Blizzard, sdk.skills.GlacialSpike, sdk.skills.ColdMastery],
- usefulskills: [sdk.skills.IceBlast, sdk.skills.Warmth, sdk.skills.StaticField],
- usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery],
- mercDiff: sdk.difficulty.Nightmare,
- mercAct: 2,
- mercAuraWanted: "Holy Freeze",
- classicStats: [
- ["energy", 60], ["vitality", 40], ["strength", 55],
- ["energy", 80], ["vitality", 80], ["strength", 80],
- ["energy", 100], ["vitality", "all"]
- ],
- expansionStats: [
- ["energy", 69], ["strength", 48], ["vitality", 165],
- ["strength", 61], ["vitality", 200], ["strength", 100],
- ["vitality", 252], ["dexterity", "block"], ["vitality", "all"]
- ],
- classicSkills: [
- // Total skills at respec = 27 (assume no izual quest points)
- [sdk.skills.Warmth, 1], // points left 26
- [sdk.skills.FrozenArmor, 1], // points left 25
- [sdk.skills.StaticField, 6], // points left 19
- [sdk.skills.Teleport, 4], // points left 14
- [sdk.skills.Blizzard, 3], // points left 7
- [sdk.skills.IceBlast, 8], // points left 0
- [sdk.skills.ColdMastery, 1, false],
- [sdk.skills.FrozenOrb, 1, false],
- [sdk.skills.Blizzard, 20, false],
- [sdk.skills.IceBlast, 20, false],
- [sdk.skills.ColdMastery, 5],
- [sdk.skills.GlacialSpike, 20, false],
- ],
- expansionSkills: [
- // Total skills at respec = 27 (assume no izual quest points)
- [sdk.skills.Warmth, 1], // points left 24
- [sdk.skills.FrozenArmor, 1], // points left 23
- [sdk.skills.StaticField, 1], // points left 22
- [sdk.skills.Teleport, 4], // points left 17
- [sdk.skills.Blizzard, 1], // points left 12
- [sdk.skills.IceBlast, 15], // points left 0
- [sdk.skills.ColdMastery, 1, false],
- [sdk.skills.FrozenOrb, 1, false],
- [sdk.skills.Blizzard, 20, false],
- [sdk.skills.IceBlast, 20, false],
- [sdk.skills.ColdMastery, 5],
- [sdk.skills.GlacialSpike, 20, false],
- ],
- stats: undefined,
- skills: undefined,
- active: function () {
- return me.charlvl > CharInfo.respecOne && me.charlvl < CharInfo.respecTwo && me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.Nova, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.FireMastery, sdk.skills.subindex.HardPoints);
- },
-};
+(function (module) {
+ module.exports = (function () {
+ const build = {
+ caster: true,
+ skillstab: sdk.skills.tabs.Cold,
+ wantedskills: [sdk.skills.Blizzard, sdk.skills.GlacialSpike, sdk.skills.ColdMastery],
+ usefulskills: [sdk.skills.IceBlast, sdk.skills.Warmth, sdk.skills.StaticField],
+ usefulStats: [sdk.stats.PassiveColdPierce, sdk.stats.PassiveColdMastery],
+ wantedMerc: MercData[sdk.skills.HolyFreeze],
+ stats: [],
+ skills: [],
-// Has to be set after its loaded
-build.stats = me.classic ? build.classicStats : build.expansionStats;
-build.skills = me.classic ? build.classicSkills : build.expansionSkills;
+ active: function () {
+ const { respecOne, respecTwo } = CharInfo;
+ return (
+ me.charlvl > respecOne && me.charlvl < respecTwo
+ && me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints)
+ && !me.checkSkill(sdk.skills.Nova, sdk.skills.subindex.HardPoints)
+ && !me.checkSkill(sdk.skills.FireMastery, sdk.skills.subindex.HardPoints)
+ );
+ },
-build.AutoBuildTemplate[1] = buildAutoBuildTempObj(() => {
- Config.AttackSkill = [-1, sdk.skills.Blizzard, sdk.skills.IceBlast, sdk.skills.Blizzard, sdk.skills.IceBlast, -1, -1];
- Config.BeltColumn = ["hp", "hp", "mp", "mp"];
- Config.HPBuffer = me.expansion && !me.normal ? 2 : 5;
- Config.MPBuffer = (me.expansion && !me.normal || Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum === sdk.locale.items.Insight) ? 2 : 5;
- Config.SkipImmune = ["cold"];
- SetUp.belt();
-});
+ AutoBuildTemplate: {
+ 1: {
+ Update: function () {
+ Config.AttackSkill = [
+ -1,
+ sdk.skills.Blizzard, sdk.skills.IceBlast,
+ sdk.skills.Blizzard, sdk.skills.IceBlast,
+ (me.checkSkill(sdk.skills.Lightning, sdk.skills.subindex.HardPoints)
+ ? sdk.skills.Lightning
+ : sdk.skills.StaticField
+ ), -1
+ ];
+ Config.BeltColumn = ["hp", "hp", "mp", "mp"];
+ Config.HPBuffer = me.expansion && !me.normal ? 2 : 5;
+ Config.MPBuffer = (
+ me.expansion
+ && !me.normal || Item.getMercEquipped(sdk.body.RightArm).prefixnum === sdk.locale.items.Insight
+ ) ? 2 : 5;
+ Config.SkipImmune = ["cold"];
+ SetUp.belt();
+ }
+ }
+ },
+ };
+
+ // Has to be set after its loaded
+ build.stats = me.classic
+ ? [
+ ["energy", 60], ["vitality", 40], ["strength", 55],
+ ["energy", 80], ["vitality", 80], ["strength", 80],
+ ["energy", 100], ["vitality", "all"]
+ ] : [
+ ["energy", 69], ["strength", 48], ["vitality", 165],
+ ["strength", 61], ["vitality", 200], ["strength", 100],
+ ["vitality", 252], ["dexterity", "block"], ["vitality", "all"]
+ ];
+
+ build.skills = me.classic
+ ? [
+ // Total skills at respec = 27 (assume no izual quest points)
+ [sdk.skills.Warmth, 1], // points left 26
+ [sdk.skills.FrozenArmor, 1], // points left 25
+ [sdk.skills.StaticField, 6], // points left 19
+ [sdk.skills.Teleport, 4], // points left 14
+ [sdk.skills.Blizzard, 3], // points left 7
+ [sdk.skills.IceBlast, 8], // points left 0
+ [sdk.skills.ColdMastery, 1, false],
+ [sdk.skills.FrozenOrb, 1, false],
+ [sdk.skills.Blizzard, 20, false],
+ [sdk.skills.IceBlast, 20, false],
+ [sdk.skills.ColdMastery, 5],
+ [sdk.skills.GlacialSpike, 20, false],
+ ] : [
+ // Total skills at respec = 27 (assume no izual quest points)
+ [sdk.skills.Warmth, 1], // points left 24
+ [sdk.skills.FrozenArmor, 1], // points left 23
+ [sdk.skills.StaticField, 1], // points left 22
+ [sdk.skills.Teleport, 4], // points left 17
+ [sdk.skills.Blizzard, 1], // points left 12
+ [sdk.skills.IceBlast, 15], // points left 0
+ [sdk.skills.ColdMastery, 1, false],
+ [sdk.skills.FrozenOrb, 1, false],
+ [sdk.skills.Blizzard, 20, false],
+ [sdk.skills.ChainLightning, 1],
+ [sdk.skills.LightningMastery, 1],
+ [sdk.skills.Lightning, 5, false],
+ [sdk.skills.LightningMastery, 20],
+ // [sdk.skills.IceBlast, 20, false],
+ // [sdk.skills.ColdMastery, 5],
+ // [sdk.skills.GlacialSpike, 20, false],
+ ];
+
+ return build;
+ })();
+})(module);
diff --git a/libs/SoloPlay/BuildFiles/sorceress/sorceress.js b/libs/SoloPlay/BuildFiles/sorceress/sorceress.js
index 9f19bd0c..1f94e46e 100644
--- a/libs/SoloPlay/BuildFiles/sorceress/sorceress.js
+++ b/libs/SoloPlay/BuildFiles/sorceress/sorceress.js
@@ -6,40 +6,40 @@
*/
const CharInfo = {
- respecOne: me.expansion ? 26 : 26,
- respecTwo: me.expansion ? 63 : 60,
- levelCap: (function() {
- const currentDiff = sdk.difficulty.nameOf(me.diff);
- const softcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 64 : 60,
- "Hell": 100,
- };
- const hardcoreMode = {
- "Normal": me.expansion ? 33 : 33,
- "Nightmare": me.expansion ? 67 : 67,
- "Hell": 100,
- };
+ respecOne: me.expansion ? 26 : 26,
+ respecTwo: me.expansion ? 63 : 60,
+ levelCap: (function () {
+ const currentDiff = sdk.difficulty.nameOf(me.diff);
+ const softcoreMode = {
+ "Normal": me.expansion ? 33 : 33,
+ "Nightmare": me.expansion ? 64 : 60,
+ "Hell": 100,
+ };
+ const hardcoreMode = {
+ "Normal": me.expansion ? 36 : 33,
+ "Nightmare": me.expansion ? 71 : 67,
+ "Hell": 100,
+ };
- return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
- })(),
+ return me.softcore ? softcoreMode[currentDiff] : hardcoreMode[currentDiff];
+ })(),
- getActiveBuild: function () {
- const nSkills = me.getStat(sdk.stats.NewSkills);
- const currLevel = me.charlvl;
- const justRepeced = (nSkills >= currLevel);
+ getActiveBuild: function () {
+ const nSkills = me.getStat(sdk.stats.NewSkills);
+ const currLevel = me.charlvl;
+ const justRepeced = (nSkills >= currLevel);
- switch (true) {
- case currLevel < this.respecOne && !me.checkSkill(sdk.skills.ColdMastery, sdk.skills.subindex.HardPoints):
- return "Start";
- case currLevel >= this.respecOne && currLevel < this.respecTwo && justRepeced:
- case currLevel >= this.respecOne && currLevel < this.respecTwo && me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.Nova, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.FireMastery, sdk.skills.subindex.HardPoints):
- return "Stepping";
- case Check.finalBuild().respec() && justRepeced:
- case Check.finalBuild().active():
- return SetUp.finalBuild;
- default:
- return "Leveling";
- }
- },
+ switch (true) {
+ case currLevel < this.respecOne && !me.checkSkill(sdk.skills.ColdMastery, sdk.skills.subindex.HardPoints):
+ return "Start";
+ case currLevel >= this.respecOne && currLevel < this.respecTwo && justRepeced:
+ case currLevel >= this.respecOne && currLevel < this.respecTwo && me.checkSkill(sdk.skills.Blizzard, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.Nova, sdk.skills.subindex.HardPoints) && !me.checkSkill(sdk.skills.FireMastery, sdk.skills.subindex.HardPoints):
+ return "Stepping";
+ case Check.finalBuild().respec() && justRepeced:
+ case Check.finalBuild().active():
+ return SetUp.finalBuild;
+ default:
+ return "Leveling";
+ }
+ },
};
diff --git a/libs/SoloPlay/Config/Amazon.js b/libs/SoloPlay/Config/Amazon.js
index 6373fd01..f0409585 100644
--- a/libs/SoloPlay/Config/Amazon.js
+++ b/libs/SoloPlay/Config/Amazon.js
@@ -16,292 +16,315 @@
* 4. Save the profile and start
*/
-function LoadConfig () {
- includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
- includeIfNotIncluded("SoloPlay/Functions/Globals.js");
-
- SetUp.include();
-
- /* Script */
- Scripts.SoloPlay = true;
- SetUp.config();
-
- /* Chicken configuration. */
- Config.LifeChicken = me.hardcore ? 45 : 10;
- Config.ManaChicken = 0;
- Config.MercChicken = 0;
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.TownMP = 0;
-
- /* Potions configuration. */
- Config.UseHP = me.hardcore ? 90 : 75;
- Config.UseRejuvHP = me.hardcore ? 65 : 40;
- Config.UseMP = me.hardcore ? 75 : 55;
- Config.UseMercHP = 75;
-
- /* Belt configuration. */
- Config.BeltColumn = ["hp", "mp", "mp", "rv"];
- SetUp.belt();
-
- /* Pickit configuration. */
- Config.PickRange = 40;
- // Config.PickitFiles.push("kolton.nip");
- // Config.PickitFiles.push("LLD.nip");
-
- /* Gambling configuration. */
- Config.Gamble = true;
- Config.GambleGoldStart = 1250000;
- Config.GambleGoldStop = 750000;
- Config.GambleItems.push("Amulet");
- Config.GambleItems.push("Ring");
- Config.GambleItems.push("Circlet");
- Config.GambleItems.push("Coronet");
-
- /* AutoMule configuration. */
- Config.AutoMule.Trigger = [];
- Config.AutoMule.Force = [];
- Config.AutoMule.Exclude = [
- "[name] >= Elrune && [name] <= Lemrune",
- ];
-
- /* AutoEquip configuration. */
- Config.AutoEquip = true;
-
- // AutoEquip setup
- const levelingTiers = [
- // Weapon
- "([type] == javelin || [type] == amazonjavelin) && [quality] >= normal && [flag] != ethereal && [wsm] <= 10 && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[name] == ceremonialjavelin && [quality] == unique && [flag] == ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Helmet
- "([type] == circlet || [type] == helm) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.normal && [type] == belt && [quality] >= lowquality && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Armor
- "[type] == armor && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Gloves
- "[type] == gloves && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Amulet
- "[type] == amulet && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Rings
- "[type] == ring && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- ];
-
- const expansionTiers = [
- // Switch
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 72 # [secondarytier] == 25000", // Weaken charged wand
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 91 # [secondarytier] == 50000 + chargeditemscore(item, 91)", // Lower Resist charged wand
- // Charms
- "[name] == smallcharm && [quality] == magic # # [invoquantity] == 8 && [charmtier] == charmscore(item)",
- "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- ];
-
- NTIP.arrayLooping(levelingTiers);
- me.expansion && NTIP.arrayLooping(expansionTiers);
-
- if (["Witchyzon", "Wfzon", "Faithbowzon"].indexOf(SetUp.currentBuild) === -1) {
- NTIP.addLine("[type] == shield && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)");
- NTIP.addLine("me.classic && [type] == shield && [quality] >= normal # [itemchargedskill] >= 0 # [tier] == tierscore(item)");
- }
-
- /* Attack configuration. */
- Config.AttackSkill = [0, 0, 0, 0, 0];
- Config.LowManaSkill = [-1, -1];
- Config.MaxAttackCount = 1000;
- Config.BossPriority = false;
- Config.ClearType = 0;
- Config.ClearPath = {Range: (Pather.canTeleport() ? 30 : 20), Spectype: sdk.monsters.spectype.All};
-
- // Class specific config
- Config.LightningFuryDelay = 10; // Lightning fury interval in seconds. LF is treated as timed skill.
- Config.SummonValkyrie = true; // Summon Valkyrie
-
- /* Gear */
- let finalGear = Check.finalBuild().finalGear;
- !!finalGear && NTIP.arrayLooping(finalGear);
-
- Config.imbueables = [
- {name: sdk.items.MaidenJavelin, condition: () => me.normal && me.expansion},
- {name: sdk.items.CeremonialJavelin, condition: () => !me.normal && (me.charlvl < 48 || me.trueStr < 107 || me.trueDex < 151) && me.expansion},
- {name: sdk.items.MatriarchalJavelin, condition: () => (Item.getEquippedItem(sdk.body.RightArm).tier < 100000 && me.trueStr >= 107 && me.trueDex >= 151 && me.expansion)},
- {name: sdk.items.Belt, condition: () => (me.normal && (Item.getEquippedItem(sdk.body.RightArm).tier > 100000 || me.classic))},
- {name: sdk.items.MeshBelt, condition: () => (!me.normal && me.charlvl < 46 && me.trueStr > 58 && (Item.getEquippedItem(sdk.body.RightArm).tier > 100000 || me.classic))},
- {name: sdk.items.SpiderwebSash, condition: () => (!me.normal && me.trueStr > 50 && (Item.getEquippedItem(sdk.body.RightArm).tier > 100000 || me.classic))},
- ];
-
- let imbueArr = SetUp.imbueItems();
-
- !me.smith && NTIP.arrayLooping(imbueArr);
-
- if (Item.getEquippedItem(sdk.body.RightArm).tier < 100000) {
- Config.GambleItems.push("Javelin");
- Config.GambleItems.push("Pilum");
- Config.GambleItems.push("Short Spear");
- Config.GambleItems.push("Throwing Spear");
- }
-
- switch (me.gametype) {
- case sdk.game.gametype.Classic:
- // Res shield
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 487) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PDiamondShield.js");
- }
-
- break;
- case sdk.game.gametype.Expansion:
- NTIP.addLine("[name] >= Vexrune && [name] <= Zodrune");
-
- // basicSocketables located in Globals
- Config.socketables = Config.socketables.concat(basicSocketables.all);
- Config.socketables.push(addSocketableObj(sdk.items.Bill, [], [],
- me.normal, (item) => item.ilvl >= 26 && item.isBaseType
- ));
- Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
-
- Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
- Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
-
- /* Crafting */
- // Going with Blood but TODO is test HitPower vs Blood
- if (Item.getEquippedItem(sdk.body.Neck).tier < 100000) {
- Config.Recipes.push([Recipe.Blood.Amulet]);
- }
-
- if (Item.getEquippedItem(sdk.body.RingLeft).tier < 100000) {
- Config.Recipes.push([Recipe.Blood.Ring]);
- }
-
- // FinalBuild specific setup
- switch (SetUp.finalBuild) {
- case "Witchyzon":
- case "Faithbowzon":
- case "Wfzon":
- (["Witchyzon", "Wfzon", "Faithbowzon"].includes(SetUp.currentBuild)) && NTIP.addLine("[type] == bowquiver # # [maxquantity] == 1");
-
- if (SetUp.finalBuild === "Wfzon") {
- if (!Check.haveItem(sdk.items.HydraBow, "unique", "Windforce")) {
- NTIP.addLine("[name] == hydrabow && [quality] == unique # [manaleech] >= 6 # [maxquantity] == 1");
- }
-
- Config.socketables.push(addSocketableObj(sdk.items.HydraBow, [sdk.items.runes.Shael], [sdk.items.runes.Amn],
- true, (item) => item.unique
- ));
- }
-
- if ((SetUp.finalBuild === "Faithbowzon") && !me.checkItem({name: sdk.locale.items.Faith, classid: sdk.items.GrandMatronBow}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Faith.js");
- }
-
- if (!Check.haveItem(sdk.items.DiamondBow, "unique", "Witchwild String")
- && (SetUp.finalBuild === "Witchyzon" || ["Wfzon", "Faithbowzon"].indexOf(SetUp.currentBuild) === -1)) {
- NTIP.addLine("[name] == shortsiegebow && [quality] == unique # [fireresist] == 40 # [maxquantity] == 1");
- // Witchyzon only - keep the bow but don't upgrade it until we have our wanted belt
- if ((SetUp.finalBuild === "Witchyzon" && Check.haveItem("vampirefangbelt", "unique", "Nosferatu's Coil")) || ["Wfzon", "Faithbowzon"].includes(SetUp.finalBuild)) {
- Config.Recipes.push([Recipe.Unique.Weapon.ToElite, "Short Siege Bow", Roll.NonEth]);
- }
- }
-
- // Spirit shield - while lvling and Wf final switch
- if ((me.ladder || Developer.addLadderRW) && (Item.getEquippedItem(5).tier < 1000
- && (["Witchyzon", "Wfzon", "Faithbowzon"].indexOf(SetUp.currentBuild) === -1)
- || (SetUp.finalBuild === "Wfzon" && Item.getEquippedItem(12).prefixnum !== sdk.locale.items.Spirit))) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
- }
-
- Config.socketables.push(addSocketableObj(sdk.items.DiamondBow, [sdk.items.runes.Nef, sdk.items.runes.Shael], [sdk.items.gems.Perfect.Skull],
- false, (item) => item.unique
- ));
- Config.socketables.push(addSocketableObj(sdk.items.BoneVisage, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && item.getStat(sdk.stats.LifeLeech) === 8 && item.getStat(sdk.stats.DamageResist) === 20 && !item.ethereal
- ));
-
- break;
- case "Javazon":
- Config.SkipImmune = ["lightning and physical"];
-
- if (me.checkSkill(sdk.skills.ChargedStrike, sdk.skills.subindex.HardPoints)) {
- // "Monster name": [-1, -1],
- Config.CustomAttack = {
- "Fire Tower": [sdk.skills.ChargedStrike, -1],
- };
- }
-
- // Infinity
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
- }
-
- // Spirit shield
- if ((me.ladder || Developer.addLadderRW) && (Item.getEquippedItem(sdk.body.LeftArm).tier < 1000 || Item.getEquippedItem(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
- }
-
- break;
- default:
- break;
- }
-
- // Call to Arms
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
- }
-
- // Chains of Honor
- if (!me.checkItem({name: sdk.locale.items.ChainsofHonor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
- }
-
- // Merc Insight
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).tier < 3600) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
- }
-
- // Lore
- if (Item.getEquippedItem(sdk.body.Head).tier < 250) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
- }
-
- // Ancients' Pledge
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500 && ["Witchyzon", "Wfzon", "Faithbowzon"].indexOf(SetUp.currentBuild) === -1) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
- }
-
- // Treachery
- if (Item.getEquippedItem(sdk.body.Armor).tier < 634) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Treachery.js");
- }
-
- // Merc Doom
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== 20532 && SetUp.finalBuild !== "Javazon") {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercDoom.js");
- }
-
- // Merc Fortitude
- if (Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
-
- // Merc Treachery
- if (Item.getEquippedItemMerc(sdk.body.Armor).tier < 15000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
- }
-
- // Smoke
- if (Item.getEquippedItem(sdk.body.Armor).tier < 634) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
- }
-
- // Stealth
- if (Item.getEquippedItem(sdk.body.Armor).tier < 233) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
- }
-
- SoloWants.buildList();
-
- break;
- }
-}
+(function LoadConfig () {
+ includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
+ includeIfNotIncluded("SoloPlay/Functions/Globals.js");
+
+ const LADDER_ENABLED = (me.ladder || Developer.addLadderRW);
+
+ SetUp.include();
+ SetUp.config();
+
+ /* Pickit configuration. */
+ Config.PickRange = 40;
+ // Config.PickitFiles.push("kolton.nip");
+ // Config.PickitFiles.push("LLD.nip");
+
+ /* Gambling configuration. */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Config.GambleItems.push("Amulet");
+ }
+ if (me.equipped.get(sdk.body.RingLeft).tier < 100000
+ || me.equipped.get(sdk.body.RingRight).tier < 100000) {
+ Config.GambleItems.push("Ring");
+ }
+ if (me.equipped.get(sdk.body.Head).tier < 100000) {
+ Config.GambleItems.push("Circlet");
+ Config.GambleItems.push("Coronet");
+ }
+
+ if (me.equipped.get(sdk.body.RightArm).tier < 100000) {
+ Config.GambleItems.push("Javelin");
+ Config.GambleItems.push("Pilum");
+ Config.GambleItems.push("Short Spear");
+ Config.GambleItems.push("Throwing Spear");
+ }
+ // AutoEquip setup
+ const levelingTiers = [
+ // Weapon
+ "([type] == javelin || [type] == amazonjavelin) && [quality] >= normal && [flag] != ethereal && [wsm] <= 10 && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // "[name] == ceremonialjavelin && [quality] == unique && [flag] == ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)", // too many issues with eth titans
+ ];
+
+ const expansionTiers = [
+ // Switch
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 72 # [secondarytier] == 25000", // Weaken charged wand
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 91 # [secondarytier] == 50000 + chargeditemscore(item, 91)", // Lower Resist charged wand
+ // Charms
+ "[name] == smallcharm && [quality] == magic # # [invoquantity] == 8 && [charmtier] == charmscore(item)",
+ "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ ];
+
+ NTIP.buildList(levelingTiers);
+ me.expansion && NTIP.buildList(expansionTiers);
+
+ if (["Witchyzon", "Wfzon", "Faithbowzon"].indexOf(SetUp.currentBuild) === -1) {
+ NTIP.addLine("[type] == shield && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)");
+ NTIP.addLine("([type] == shield) && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 2) # [tier] == tierscore(item)");
+ NTIP.addLine("me.classic && [type] == shield && [quality] >= normal # [itemchargedskill] >= 0 # [tier] == tierscore(item)");
+ }
+
+ /* Attack configuration. */
+ Config.AttackSkill = [0, 0, 0, 0, 0];
+ Config.LowManaSkill = [-1, -1];
+ Config.MaxAttackCount = 1000;
+ Config.BossPriority = false;
+ Config.ClearType = 0;
+ Config.ClearPath = { Range: (Pather.canTeleport() ? 30 : 20), Spectype: sdk.monsters.spectype.All };
+
+ // Class specific config
+ Config.LightningFuryDelay = 10; // Lightning fury interval in seconds. LF is treated as timed skill.
+ Config.SummonValkyrie = true; // Summon Valkyrie
+
+ Config.imbueables = (function () {
+ /**
+ * @param {number} name
+ * @param {function(): boolean} condition
+ */
+ const _imbueObj = (name, condition) => ({ name: name, condition: condition });
+
+ return [
+ _imbueObj(
+ sdk.items.MaidenJavelin,
+ function () {
+ return me.normal && me.expansion;
+ }
+ ),
+ _imbueObj(
+ sdk.items.CeremonialJavelin,
+ function () {
+ return !me.normal && (me.charlvl < 48 || me.trueStr < 107 || me.trueDex < 151) && me.expansion;
+ }
+ ),
+ _imbueObj(
+ sdk.items.MatriarchalJavelin,
+ function () {
+ return me.equipped.get(sdk.body.RightArm).tier < 100000
+ && me.trueStr >= 107 && me.trueDex >= 151 && me.expansion;
+ }
+ ),
+ _imbueObj(
+ sdk.items.Belt,
+ function () {
+ return me.normal && (me.equipped.get(sdk.body.RightArm).tier > 100000 || me.classic);
+ }
+ ),
+ _imbueObj(
+ sdk.items.MeshBelt,
+ function () {
+ return !me.normal && me.charlvl < 46 && me.trueStr > 58
+ && (me.equipped.get(sdk.body.RightArm).tier > 100000 || me.classic);
+ }
+ ),
+ _imbueObj(
+ sdk.items.SpiderwebSash,
+ function () {
+ return !me.normal && me.trueStr > 50
+ && (me.equipped.get(sdk.body.RightArm).tier > 100000 || me.classic);
+ }
+ ),
+ ].filter((item) => item.condition());
+ })();
+
+ let imbueArr = SetUp.imbueItems();
+
+ !me.smith && NTIP.buildList(imbueArr);
+
+ switch (me.gametype) {
+ case sdk.game.gametype.Classic:
+ // Res shield
+ if (me.equipped.get(sdk.body.LeftArm).tier < 487) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PDiamondShield.js");
+ }
+
+ break;
+ case sdk.game.gametype.Expansion:
+ NTIP.addLine("[name] >= Vexrune && [name] <= Zodrune");
+ const { basicSocketables, addSocketableObj } = require("../Utils/General");
+
+ Config.socketables = Config.socketables.concat(basicSocketables.all);
+ Config.socketables.push(addSocketableObj(
+ sdk.items.Bill, [], [],
+ me.normal, (item) => item.ilvl >= 26 && item.isBaseType
+ ));
+ Config.socketables.push(addSocketableObj(
+ sdk.items.Shako,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+
+ Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
+ Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
+
+ /* Crafting */
+ // Going with Blood but TODO is test HitPower vs Blood
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Config.Recipes.push([Recipe.Blood.Amulet]);
+ }
+
+ if (me.equipped.get(sdk.body.RingLeft).tier < 100000) {
+ Config.Recipes.push([Recipe.Blood.Ring]);
+ }
+
+ // FinalBuild specific setup
+ switch (SetUp.finalBuild) {
+ case "Witchyzon":
+ case "Faithbowzon":
+ case "Wfzon":
+ if (["Witchyzon", "Wfzon", "Faithbowzon"].includes(SetUp.currentBuild)) {
+ NTIP.addLine("[type] == bowquiver # # [maxquantity] == 1");
+ }
+
+ if (SetUp.finalBuild === "Wfzon") {
+ if (!me.checkItem({ name: sdk.locale.items.Windforce, classid: sdk.items.HydraBow }).have) {
+ NTIP.addLine("[name] == hydrabow && [quality] == unique # [manaleech] >= 6 # [maxquantity] == 1");
+ }
+
+ Config.socketables.push(addSocketableObj(
+ sdk.items.HydraBow,
+ [sdk.items.runes.Shael], [sdk.items.runes.Amn],
+ true,
+ (item) => item.unique
+ ));
+ }
+
+ if ((SetUp.finalBuild === "Faithbowzon")
+ && !me.checkItem({ name: sdk.locale.items.Faith, classid: sdk.items.GrandMatronBow }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Faith.js");
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.WitchwildString, classid: sdk.items.DiamondBow }).have
+ && (SetUp.finalBuild === "Witchyzon" || ["Wfzon", "Faithbowzon"].indexOf(SetUp.currentBuild) === -1)) {
+ NTIP.addLine("[name] == shortsiegebow && [quality] == unique # [fireresist] == 40 # [maxquantity] == 1");
+ // Witchyzon only - keep the bow but don't upgrade it until we have our wanted belt
+ if (["Wfzon", "Faithbowzon"].includes(SetUp.finalBuild)
+ || (
+ SetUp.finalBuild === "Witchyzon"
+ && me.checkItem({ name: sdk.locale.items.NosferatusCoil }).have
+ )) {
+ Config.Recipes.push([Recipe.Unique.Weapon.ToElite, "Short Siege Bow", Roll.NonEth]);
+ }
+ }
+
+ // Spirit shield - while lvling and Wf final switch
+ if ((LADDER_ENABLED) && (me.equipped.get(5).tier < 1000
+ && (["Witchyzon", "Wfzon", "Faithbowzon"].indexOf(SetUp.currentBuild) === -1)
+ || (SetUp.finalBuild === "Wfzon" && me.equipped.get(12).prefixnum !== sdk.locale.items.Spirit))) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
+ }
+
+ Config.socketables.push(addSocketableObj(
+ sdk.items.DiamondBow,
+ [sdk.items.runes.Nef, sdk.items.runes.Shael], [sdk.items.gems.Perfect.Skull],
+ false,
+ (item) => item.unique
+ ));
+ Config.socketables.push(addSocketableObj(
+ sdk.items.BoneVisage,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true,
+ (item) => (item.unique && item.getStat(sdk.stats.LifeLeech) === 8
+ && item.getStat(sdk.stats.DamageResist) === 20 && !item.ethereal)
+ ));
+
+ break;
+ case "Javazon":
+ Config.SkipImmune = ["lightning and physical"];
+
+ if (me.checkSkill(sdk.skills.ChargedStrike, sdk.skills.subindex.HardPoints)) {
+ // "Monster name": [-1, -1],
+ Config.CustomAttack = {
+ "Fire Tower": [sdk.skills.ChargedStrike, -1],
+ };
+ }
+
+ // Infinity
+ if ((LADDER_ENABLED)
+ && Item.getMercEquipped(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
+ }
+
+ // Spirit shield
+ if (LADDER_ENABLED
+ && (me.equipped.get(sdk.body.LeftArm).tier < 1000
+ || me.equipped.get(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ // Call to Arms
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
+ }
+
+ // Chains of Honor
+ if (!me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
+ }
+
+ // Merc Insight
+ if ((LADDER_ENABLED) && Item.getMercEquipped(sdk.body.RightArm).tier < 3600) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
+ }
+
+ // Lore
+ if (me.equipped.get(sdk.body.Head).tier < 250) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
+ }
+
+ // Ancients' Pledge
+ if (me.equipped.get(sdk.body.LeftArm).tier < 500 && ["Witchyzon", "Wfzon", "Faithbowzon"].indexOf(SetUp.currentBuild) === -1) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
+ }
+
+ // Treachery
+ if (me.equipped.get(sdk.body.Armor).tier < 634) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Treachery.js");
+ }
+
+ // Merc Doom
+ if ((LADDER_ENABLED) && Item.getMercEquipped(sdk.body.RightArm).prefixnum !== 20532 && SetUp.finalBuild !== "Javazon") {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercDoom.js");
+ }
+
+ // Merc Fortitude
+ if (Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+
+ // Merc Treachery
+ if (Item.getMercEquipped(sdk.body.Armor).tier < 15000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
+ }
+
+ // Smoke
+ if (me.equipped.get(sdk.body.Armor).tier < 634) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
+ }
+
+ // Stealth
+ if (me.equipped.get(sdk.body.Armor).tier < 233) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
+ }
+
+ SoloWants.buildList();
+
+ break;
+ }
+
+ return true;
+})();
diff --git a/libs/SoloPlay/Config/Assassin.js b/libs/SoloPlay/Config/Assassin.js
index 1e85c619..9d1b8ce9 100644
--- a/libs/SoloPlay/Config/Assassin.js
+++ b/libs/SoloPlay/Config/Assassin.js
@@ -14,252 +14,287 @@
* 4. Save the profile and start
*/
-function LoadConfig () {
- includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
- includeIfNotIncluded("SoloPlay/Functions/Globals.js");
-
- SetUp.include();
-
- /* Script */
- Scripts.SoloPlay = true;
- SetUp.config();
-
- /* Chicken configuration. */
- Config.LifeChicken = me.hardcore ? 45 : 10;
- Config.ManaChicken = 0;
- Config.MercChicken = 0;
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.TownMP = 0;
-
- /* Potions configuration. */
- Config.UseHP = me.hardcore ? 90 : 75;
- Config.UseRejuvHP = me.hardcore ? 65 : 40;
- Config.UseMP = me.hardcore ? 75 : 55;
- Config.UseMercHP = 75;
-
- /* Belt configuration. */
- Config.BeltColumn = ["hp", "mp", "mp", "rv"];
- SetUp.belt();
-
- /* Pickit configuration. */
- Config.PickRange = 40;
- // Config.PickitFiles.push("kolton.nip");
- // Config.PickitFiles.push("LLD.nip");
- NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
-
- /* Gambling configuration. */
- Config.Gamble = true;
- Config.GambleGoldStart = 2000000;
- Config.GambleGoldStop = 750000;
- Config.GambleItems.push("Amulet");
- Config.GambleItems.push("Ring");
- //Config.GambleItems.push("Circlet");
- //Config.GambleItems.push("Coronet");
-
- /* AutoMule configuration. */
- Config.AutoMule.Trigger = [];
- Config.AutoMule.Force = [];
- Config.AutoMule.Exclude = [
- "[name] >= Elrune && [name] <= Lemrune",
- ];
-
- /* AutoEquip configuration. */
- Config.AutoEquip = true;
-
- // AutoEquip setup
- const levelingTiers = [
- // Weapon
- "([type] == knife || [type] == sword && [flag] == runeword || ([type] == handtohand || [type] == assassinclaw) && [quality] >= magic) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Helmet
- "([type] == helm || [type] == circlet) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.normal && [type] == belt && [quality] >= lowquality && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Srmor
- "[type] == armor && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Shield
- "[type] == shield && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Gloves
- "[type] == gloves && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Amulet
- "[type] == amulet && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Rings
- "[type] == ring && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Switch
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 91 # [secondarytier] == 50000 + chargeditemscore(item, 91)", // Lower Resist charged wand
- // Charms
- "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- ];
-
- NTIP.arrayLooping(levelingTiers);
-
- /* Attack configuration. */
- Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
- Config.LowManaSkill = [0, 0];
- Config.MaxAttackCount = 1000;
- Config.BossPriority = me.normal;
- Config.ClearType = 0;
- Config.ClearPath = {Range: (Pather.canTeleport() ? 30 : 20), Spectype: 0};
-
- /* Class specific configuration. */
- Config.UseTraps = true;
- Config.Traps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.DeathSentry, sdk.skills.DeathSentry];
- Config.BossTraps = [sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry, sdk.skills.LightningSentry];
-
- Config.SummonShadow = me.checkSkill(sdk.skills.ShadowMaster, sdk.skills.subindex.HardPoints) ? "Master" : 0;
- Config.UseFade = me.checkSkill(sdk.skills.Fade, sdk.skills.subindex.HardPoints);
- Config.UseBoS = me.checkSkill(sdk.skills.BurstofSpeed, sdk.skills.subindex.HardPoints);
- Config.UseVenom = false;
- Config.UseCloakofShadows = me.checkSkill(sdk.skills.CloakofShadows, sdk.skills.subindex.HardPoints);
- Config.AggressiveCloak = false;
-
- /* Dodge configuration. */
- Config.Dodge = me.checkSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints);
- Config.DodgeRange = 10;
- Config.DodgeHP = 75;
-
- /* Gear */
- let finalGear = Check.finalBuild().finalGear;
- !!finalGear && NTIP.arrayLooping(finalGear);
-
- Config.imbueables = [
- {name: sdk.items.Claws, condition: () => (me.normal)},
- {name: sdk.items.HandScythe, condition: () => (!me.normal && Item.getEquippedItem(sdk.body.RightArm).tier < 777 && (me.trueStr < 79 || me.trueDex < 79))},
- {name: sdk.items.GreaterTalons, condition: () => (Item.getEquippedItem(sdk.body.RightArm).tier < 777 && me.trueStr >= 79 && me.trueDex >= 79)},
- {name: sdk.items.Belt, condition: () => (me.normal && (Item.getEquippedItem(sdk.body.RightArm).tier > 777))},
- {name: sdk.items.MeshBelt, condition: () => (!me.normal && me.charlvl < 46 && me.trueStr > 58 && (Item.getEquippedItem(sdk.body.RightArm).tier > 777))},
- {name: sdk.items.SpiderwebSash, condition: () => (!me.normal && me.trueStr > 50 && (Item.getEquippedItem(sdk.body.RightArm).tier > 777))},
- ].filter((item) => item.condition());
-
- let imbueArr = SetUp.imbueItems();
-
- !me.smith && NTIP.arrayLooping(imbueArr);
-
- // basicSocketables located in Globals
- Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
- Config.socketables.push(addSocketableObj(sdk.items.Monarch, [], [],
- !me.hell, (item) => !Check.haveBase("monarch", 4) && item.ilvl >= 41 && item.isBaseType && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
-
- switch (SetUp.finalBuild) {
- case "Whirlsin":
- Config.socketables.push(addSocketableObj(sdk.items.WingedHelm, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
-
- // Pride
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== sdk.locale.items.Pride) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercPride.js");
- }
-
- // Chaos
- if (!me.checkItem({name: sdk.locale.items.Chaos}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Chaos.js");
- }
-
- // Fury
- if (!me.checkItem({name: sdk.locale.items.Fury}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Fury.js");
- }
-
- // Fortitude
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Fortitude, itemtype: sdk.items.type.Armor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Fortitude.js");
- }
-
- break;
- default:
- Config.socketables.push(addSocketableObj(sdk.items.Demonhead, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
-
- // Infinity
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
- }
-
- // Heart of the Oak
- if (!me.checkItem({name: sdk.locale.items.HeartoftheOak}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
- }
-
- // Enigma
- if (!me.checkItem({name: sdk.locale.items.Enigma}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
- }
-
- break;
- }
-
- Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
- Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
-
- /* Crafting */
- if (Item.getEquippedItem(sdk.body.Neck).tier < 100000) {
- Config.Recipes.push([Recipe.Caster.Amulet]);
- }
-
- if (Item.getEquippedItem(sdk.body.RingLeft).tier < 100000) {
- Config.Recipes.push([Recipe.Caster.Ring]);
- }
-
- // Call to Arms
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
- }
-
- // Spirit Sword
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItem(sdk.body.RightArm).tier < 777) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
- }
-
- // Spirit shield
- if ((me.ladder || Developer.addLadderRW) && (Item.getEquippedItem(sdk.body.LeftArm).tier < 1000 || Item.getEquippedItem(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
- }
-
- // Merc Insight
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).tier < 3600) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
- }
-
- // Lore
- if (Item.getEquippedItem(sdk.body.Head).tier < 315) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
- }
-
- // Ancients' Pledge
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
- }
-
- // Merc Fortitude
- if (Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
-
- // Merc Treachery
- if (Item.getEquippedItemMerc(sdk.body.Armor).tier < 15000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
- }
-
- // Smoke
- if (Item.getEquippedItem(sdk.body.Armor).tier < 450) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
- }
-
- // Stealth
- if (Item.getEquippedItem(sdk.body.Armor).tier < 233) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
- }
-
- SoloWants.buildList();
-}
+(function LoadConfig () {
+ includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
+ includeIfNotIncluded("SoloPlay/Functions/Globals.js");
+
+ const LADDER_ENABLED = (me.ladder || Developer.addLadderRW);
+
+ SetUp.include();
+ SetUp.config();
+
+ /* Pickit configuration. */
+ Config.PickRange = 40;
+ // Config.PickitFiles.push("kolton.nip");
+ // Config.PickitFiles.push("LLD.nip");
+ NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
+
+ /* Gambling configuration. */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Config.GambleItems.push("Amulet");
+ }
+ if (me.equipped.get(sdk.body.RingLeft).tier < 100000
+ || me.equipped.get(sdk.body.RingRight).tier < 100000) {
+ Config.GambleItems.push("Ring");
+ }
+ // Config.GambleItems.push("Circlet");
+ // Config.GambleItems.push("Coronet");
+
+ /* AutoEquip configuration. */
+ Config.AutoEquip = true;
+
+ // AutoEquip setup
+ const levelingTiers = [
+ // Weapon
+ "([type] == knife || [type] == sword && [flag] == runeword || ([type] == handtohand || [type] == assassinclaw) && [quality] >= magic) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Shield
+ "[type] == shield && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Switch
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 91 # [secondarytier] == 50000 + chargeditemscore(item, 91)", // Lower Resist charged wand
+ // Charms
+ "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ // non runeword white items
+ "([type] == shield) && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 2) # [tier] == tierscore(item)",
+ ];
+
+ NTIP.buildList(levelingTiers);
+
+ /* Attack configuration. */
+ Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ Config.MaxAttackCount = 1000;
+ Config.BossPriority = me.normal;
+ Config.ClearType = 0;
+ Config.ClearPath = { Range: (Pather.canTeleport() ? 30 : 20), Spectype: 0 };
+
+ /* Class specific configuration. */
+ Config.UseTraps = true;
+ Config.Traps = [
+ sdk.skills.LightningSentry,
+ sdk.skills.LightningSentry,
+ sdk.skills.LightningSentry,
+ sdk.skills.DeathSentry,
+ sdk.skills.DeathSentry
+ ];
+ Config.BossTraps = [
+ sdk.skills.LightningSentry,
+ sdk.skills.LightningSentry,
+ sdk.skills.LightningSentry,
+ sdk.skills.LightningSentry,
+ sdk.skills.LightningSentry
+ ];
+
+ Config.SummonShadow = me.checkSkill(sdk.skills.ShadowMaster, sdk.skills.subindex.HardPoints) ? "Master" : 0;
+ Config.UseFade = me.checkSkill(sdk.skills.Fade, sdk.skills.subindex.HardPoints);
+ Config.UseBoS = me.checkSkill(sdk.skills.BurstofSpeed, sdk.skills.subindex.HardPoints);
+ Config.UseVenom = false;
+ Config.UseCloakofShadows = me.checkSkill(sdk.skills.CloakofShadows, sdk.skills.subindex.HardPoints);
+ Config.AggressiveCloak = false;
+
+ /* Dodge configuration. */
+ Config.Dodge = me.checkSkill(sdk.skills.LightningSentry, sdk.skills.subindex.HardPoints);
+ Config.DodgeRange = 10;
+ Config.DodgeHP = 75;
+
+ Config.imbueables = (function () {
+ /**
+ * @param {number} name
+ * @param {function(): boolean} condition
+ */
+ const _imbueObj = (name, condition) => ({ name: name, condition: condition });
+
+ return [
+ _imbueObj(
+ sdk.items.Claws,
+ function () {
+ return me.normal;
+ }
+ ),
+ _imbueObj(
+ sdk.items.HandScythe,
+ function () {
+ return !me.normal && me.equipped.get(sdk.body.RightArm).tier < 777 && (me.trueStr < 79 || me.trueDex < 79);
+ }
+ ),
+ _imbueObj(
+ sdk.items.GreaterTalons,
+ function () {
+ return me.equipped.get(sdk.body.RightArm).tier < 777 && me.trueStr >= 79 && me.trueDex >= 79;
+ }
+ ),
+ _imbueObj(
+ sdk.items.Belt,
+ function () {
+ return me.normal && me.equipped.get(sdk.body.RightArm).tier > 777;
+ }
+ ),
+ _imbueObj(
+ sdk.items.MeshBelt,
+ function () {
+ return !me.normal && me.charlvl < 46 && me.trueStr > 58 && me.equipped.get(sdk.body.RightArm).tier > 777;
+ }
+ ),
+ _imbueObj(
+ sdk.items.SpiderwebSash,
+ function () {
+ return !me.normal && me.trueStr > 50 && me.equipped.get(sdk.body.RightArm).tier > 777;
+ }
+ ),
+ ].filter((item) => item.condition());
+ })();
+
+ let imbueArr = SetUp.imbueItems();
+
+ !me.smith && NTIP.buildList(imbueArr);
+
+ const { basicSocketables, addSocketableObj } = require("../Utils/General");
+
+ Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
+ Config.socketables.push(addSocketableObj(sdk.items.Monarch, [], [],
+ !me.hell,
+ /** @param {ItemUnit} item */
+ function (item) {
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.Monarch,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+ return !me.getOwned(wanted).length && item.ilvl >= 41 && item.isBaseType && !item.ethereal;
+ }
+ ));
+ Config.socketables.push(addSocketableObj(
+ sdk.items.Shako,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+
+ switch (SetUp.finalBuild) {
+ case "Whirlsin":
+ Config.socketables.push(addSocketableObj(
+ sdk.items.WingedHelm,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+
+ // Pride
+ if ((LADDER_ENABLED) && Item.getMercEquipped(sdk.body.RightArm).prefixnum !== sdk.locale.items.Pride) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercPride.js");
+ }
+
+ // Chaos
+ if (!me.checkItem({ name: sdk.locale.items.Chaos }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Chaos.js");
+ }
+
+ // Fury
+ if (!me.checkItem({ name: sdk.locale.items.Fury }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Fury.js");
+ }
+
+ // Fortitude
+ if ((LADDER_ENABLED) && !me.checkItem({ name: sdk.locale.items.Fortitude, itemtype: sdk.items.type.Armor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Fortitude.js");
+ }
+
+ break;
+ default:
+ Config.socketables.push(addSocketableObj(
+ sdk.items.Demonhead,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+
+ // Infinity
+ if ((LADDER_ENABLED) && Item.getMercEquipped(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
+ }
+
+ // Heart of the Oak
+ if (!me.checkItem({ name: sdk.locale.items.HeartoftheOak }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
+ }
+
+ // Enigma
+ if (!me.checkItem({ name: sdk.locale.items.Enigma }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
+ }
+
+ break;
+ }
+
+ Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
+ Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
+
+ /* Crafting */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Config.Recipes.push([Recipe.Caster.Amulet]);
+ }
+
+ if (me.equipped.get(sdk.body.RingLeft).tier < 100000) {
+ Config.Recipes.push([Recipe.Caster.Ring]);
+ }
+
+ // Call to Arms
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
+ }
+
+ // Spirit Sword
+ if ((LADDER_ENABLED) && me.equipped.get(sdk.body.RightArm).tier < 777) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
+ }
+
+ // Spirit shield
+ if ((LADDER_ENABLED)
+ && (me.equipped.get(sdk.body.LeftArm).tier < 1000
+ || me.equipped.get(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
+ }
+
+ // Merc Insight
+ if ((LADDER_ENABLED) && Item.getMercEquipped(sdk.body.RightArm).tier < 3600) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
+ }
+
+ // Lore
+ if (me.equipped.get(sdk.body.Head).tier < 315) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
+ }
+
+ // Ancients' Pledge
+ if (me.equipped.get(sdk.body.LeftArm).tier < 500) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
+ }
+
+ // Merc Fortitude
+ if (Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+
+ // Merc Treachery
+ if (Item.getMercEquipped(sdk.body.Armor).tier < 15000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
+ }
+
+ // Smoke
+ if (me.equipped.get(sdk.body.Armor).tier < 450) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
+ }
+
+ // Stealth
+ if (me.equipped.get(sdk.body.Armor).tier < 233) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
+ }
+
+ SoloWants.buildList();
+
+ return true;
+})();
diff --git a/libs/SoloPlay/Config/Barbarian.js b/libs/SoloPlay/Config/Barbarian.js
index 3f661829..ceebf1da 100644
--- a/libs/SoloPlay/Config/Barbarian.js
+++ b/libs/SoloPlay/Config/Barbarian.js
@@ -16,369 +16,414 @@
* 4. Save the profile and start
*/
-function LoadConfig () {
- includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
- includeIfNotIncluded("SoloPlay/Functions/Globals.js");
-
- SetUp.include();
-
- /* Script */
- Scripts.SoloPlay = true;
- SetUp.config();
-
- /* Chicken configuration. */
- Config.LifeChicken = me.hardcore ? 45 : 10;
- Config.ManaChicken = 0;
- Config.MercChicken = 0;
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.TownMP = 0;
-
- /* Potions configuration. */
- Config.UseHP = me.hardcore ? 90 : 75;
- Config.UseRejuvHP = me.hardcore ? 65 : 40;
- Config.UseMP = me.hardcore ? 75 : 45;
- Config.UseMercHP = 75;
-
- /* Belt configuration. */
- Config.BeltColumn = ["hp", "mp", "mp", "rv"];
- SetUp.belt();
-
- /* Pickit configuration. */
- Config.PickRange = 40;
- 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.PickitFiles.push("kolton.nip");
- // Config.PickitFiles.push("LLD.nip");
-
- /* Gambling configuration. */
- Config.Gamble = true;
- Config.GambleGoldStart = 1250000;
- Config.GambleGoldStop = 750000;
- Config.GambleItems.push("Amulet");
- Config.GambleItems.push("Ring");
-
- /* AutoMule configuration. */
- Config.AutoMule.Trigger = [];
- Config.AutoMule.Force = [];
- Config.AutoMule.Exclude = [
- "[name] >= Elrune && [name] <= Lemrune",
- ];
-
- /* AutoEquip configuration. */
- Config.AutoEquip = true;
-
- // AutoEquip setup
- const levelingTiers = [
- // Weapon
- "me.charlvl < 12 && [type] == sword && ([quality] >= normal || [flag] == runeword) && [flag] != ethereal && [wsm] <= 20 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[type] == sword && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [wsm] <= 10 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[name] == phaseblade && [quality] == unique && [flag] == ethereal # [enhanceddamage] >= 100 && [ias] == 30 && [magicdamagereduction] >= 7 # [tier] == tierscore(item)",
- // Helmet
- "([type] == helm || [type] == primalhelm) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[type] == primalhelm && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && [sockets] == 1 # [tier] == tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.normal && [type] == belt && [quality] >= lowquality && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Armor
- "[type] == armor && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Gloves
- "[type] == gloves && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Amulet
- "[type] == amulet && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Rings
- "[type] == ring && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- ];
-
- const expansionTiers = [
- // Charms
- "[name] == smallcharm && [quality] == magic # # [invoquantity] == 8 && [charmtier] == charmscore(item)",
- "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- ];
-
- NTIP.arrayLooping(levelingTiers);
- me.expansion && NTIP.arrayLooping(expansionTiers);
-
- /* Attack configuration. */
- Config.AttackSkill = [-1, 0, 0, 0, 0];
- Config.LowManaSkill = me.getSkill(sdk.skills.DoubleSwing, sdk.skills.subindex.SoftPoints) >= 9 ? [sdk.skills.DoubleSwing, 0] : [0, -1];
- Config.MaxAttackCount = 1000;
- Config.BossPriority = me.normal;
- Config.ClearType = 0;
- Config.ClearPath = {Range: (Pather.canTeleport() ? 30 : 10), Spectype: 0};
-
- // Class specific config
- Config.FindItem = true; // Use Find Item skill on corpses after clearing.
- Config.FindItemSwitch = false; // Switch to non-primary slot when using Find Item skills
-
- /* Gear */
- let finalGear = Check.finalBuild().finalGear;
- !!finalGear && NTIP.arrayLooping(finalGear);
-
- Config.imbueables = [
- {name: sdk.items.AvengerGuard, condition: () => (me.normal && me.expansion)},
- {name: sdk.items.SlayerGuard, condition: () => (!me.normal && me.trueStr >= 118 && me.expansion)},
- {name: sdk.items.CarnageHelm, condition: () => (Item.getEquippedItem(sdk.body.Head).tier < 100000 && me.trueStr >= 106 && me.expansion)},
- {name: sdk.items.Belt, condition: () => (me.normal && (Item.getEquippedItem(sdk.body.Head).tier > 100000 || me.classic))},
- {name: sdk.items.MeshBelt, condition: () => (!me.normal && me.charlvl < 46 && me.trueStr > 58 && (Item.getEquippedItem(sdk.body.RightArm).tier > 100000 || me.classic))},
- {name: sdk.items.SpiderwebSash, condition: () => (!me.normal && me.trueStr > 50 && (Item.getEquippedItem(sdk.body.RightArm).tier > 100000 || me.classic))},
- ].filter((item) => item.condition());
-
- let imbueArr = SetUp.imbueItems();
-
- !me.smith && NTIP.arrayLooping(imbueArr);
-
- switch (me.gametype) {
- case sdk.game.gametype.Classic:
- break;
- case sdk.game.gametype.Expansion:
- NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
-
- // basicSocketables located in Globals
- Config.socketables = Config.socketables.concat(basicSocketables.all);
- Config.socketables.push(addSocketableObj(sdk.items.Flamberge, [], [],
- true, (item) => me.normal && Item.getEquippedItem(sdk.body.LeftArm).tier < 600 && !Check.haveBase("sword", 5) && !me.checkItem({name: sdk.locale.items.Honor}).have && item.ilvl >= 41 && item.isBaseType && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.Zweihander, [], [],
- true, (item) => Item.getEquippedItem(sdk.body.LeftArm).tier < 1000 && !Check.haveBase("sword", 5) && !me.checkItem({name: sdk.locale.items.Honor}).have && item.ilvl >= 41 && item.isBaseType && !item.ethereal
- ));
-
- if (SetUp.finalBuild !== "Immortalwhirl") {
- Config.socketables.push(addSocketableObj(sdk.items.SlayerGuard, [sdk.items.runes.Cham], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
- }
-
- if (["Immortalwhirl", "Singer"].indexOf(SetUp.finalBuild) === -1) {
- // Grief
- if ((me.ladder || Developer.addLadderRW) && (!me.checkItem({name: sdk.locale.items.Grief}).have || (SetUp.finalBuild === "Whirlwind" && Item.getEquippedItem(sdk.body.LeftArm).prefixnum !== sdk.locale.items.Grief))) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Grief.js");
- }
-
- // Fortitude
- if ((me.ladder || Developer.addLadderRW) && SetUp.finalBuild !== "Uberconc" && me.checkItem({name: sdk.locale.items.Grief}).have && !me.checkItem({name: sdk.locale.items.Fortitude, itemtype: sdk.items.type.Armor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Fortitude.js");
- }
-
- // Doom
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== 20532) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercDoom.js");
- }
- }
-
- // FinalBuild specific setup
- switch (SetUp.finalBuild) {
- case "Uberconc":
- if (me.checkItem({name: sdk.locale.items.Grief}).have && SetUp.finalBuild === "Uberconc") {
- // Add Stormshield
- NTIP.addLine("[name] == monarch && [quality] == unique && [flag] != ethereal # [damageresist] >= 35 # [tier] == 100000");
- }
-
- // Chains of Honor
- if (!me.checkItem({name: sdk.locale.items.ChainsofHonor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
- }
-
- break;
- case "Frenzy":
- // Breathe of the Dying
- if (!me.checkItem({name: sdk.locale.items.BreathoftheDying}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/BreathoftheDying.js");
- }
-
- break;
- case "Singer":
- // Heart of the Oak
- if (Item.getEquippedItem(sdk.body.LeftArm).prefixnum !== sdk.locale.items.HeartoftheOak && me.checkItem({name: sdk.locale.items.Enigma}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
- }
-
- // Enigma
- if (!me.checkItem({name: sdk.locale.items.Enigma}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
- }
-
- break;
- case "Immortalwhirl":
- // Infinity
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
- }
-
- Config.socketables.push(addSocketableObj(sdk.items.AvengerGuard, [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
- false, (item) => item.set && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.OgreMaul, [sdk.items.runes.Shael], [],
- false, (item) => item.set && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.SacredArmor, [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.set && !item.ethereal
- ));
-
- Check.itemSockables(sdk.items.OgreMaul, "set", "Immortal King's Stone Crusher");
-
- break;
- case "Whirlwind":
- break;
- default:
- break;
- }
-
- /* Crafting */
- if (Item.getEquippedItem(sdk.body.Neck).tier < 100000) {
- Check.currentBuild().caster ? Config.Recipes.push([Recipe.Caster.Amulet]) : Config.Recipes.push([Recipe.Blood.Amulet]);
- }
-
- if (Item.getEquippedItem(sdk.body.RingLeft).tier < 100000) {
- Check.currentBuild().caster ? Config.Recipes.push([Recipe.Caster.Ring]) : Config.Recipes.push([Recipe.Blood.Ring]);
- }
-
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 1370) {
- if (me.rawStrength >= 150 && me.rawDexterity >= 88) {
- // Upgrade Bloodletter to Elite
- Config.Recipes.push([Recipe.Unique.Weapon.ToElite, "Gladius", Roll.NonEth]);
- }
-
- if (me.rawStrength >= 25 && me.rawDexterity >= 136) {
- // Upgrade Ginther's Rift to Elite
- Config.Recipes.push([Recipe.Unique.Weapon.ToElite, "dimensionalblade", Roll.Eth]);
- }
-
- if (!Check.haveItem("falcata", "unique", "Bloodletter")) {
- NTIP.addLine("[name] == PulRune # # [maxquantity] == 1");
- NTIP.addLine("[name] == perfectemerald # # [maxquantity] == 1");
- // Bloodletter
- NTIP.addLine("[name] == gladius && [quality] == unique && [flag] != ethereal # [enhanceddamage] >= 140 && [ias] >= 20 # [maxquantity] == 1");
- // upped Bloodletter
- NTIP.addLine("[name] == falcata && [quality] == unique && [flag] != ethereal # [enhanceddamage] >= 140 && [ias] >= 20 # [maxquantity] == 1");
- }
-
- if (!Check.haveItem("dimensionalblade", "unique", "Ginther's Rift")) {
- NTIP.addLine("[name] == PulRune # # [maxquantity] == 1");
- NTIP.addLine("[name] == perfectemerald # # [maxquantity] == 1");
-
- // Have Pul rune before looking for eth ginther's
- if (me.getItem(sdk.items.runes.Pul)) {
- // Eth Ginther's Rift
- NTIP.addLine("[name] == dimensionalblade && [quality] == unique && [flag] == ethereal # [enhanceddamage] >= 100 && [ias] == 30 && [magicdamagereduction] >= 7 # [maxquantity] == 1");
- }
-
- // upped Ginther's Rift
- NTIP.addLine("[name] == phaseblade && [quality] == unique && [flag] == ethereal # [enhanceddamage] >= 100 && [ias] == 30 && [magicdamagereduction] >= 7 # [maxquantity] == 1");
- }
- }
-
- // Lawbringer - Amn/Lem/Ko
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 1370) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lawbringer.js");
- }
-
- // Voice Of Reason - Lem/Ko/El/Eld
- if (Item.getEquippedItem(sdk.body.RightArm).tier > 1100 && Item.getEquippedItem(sdk.body.LeftArm).tier < 1270 && !Check.haveItem("ring", "unique", "Raven Frost")) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/VoiceOfReason.js");
- }
-
- // Crescent Moon - Shael/Um/Tir
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 1100) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CrescentMoon.js");
- }
-
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 1200) {
- // Cube to Ko Rune
- if (!me.getItem(sdk.items.runes.Ko)) {
- Config.Recipes.push([Recipe.Rune, "Hel Rune"]);
- Config.Recipes.push([Recipe.Rune, "Io Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
- }
-
- // Cube to Lem Rune
- if (!me.getItem(sdk.items.runes.Lem)) {
- Config.Recipes.push([Recipe.Rune, "Dol Rune"]);
- Config.Recipes.push([Recipe.Rune, "Io Rune"]);
- Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
- Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
- }
- }
-
- // Honor - Amn/El/Ith/Tir/Sol
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 1050) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Honor.js");
- }
-
- // Merc Insight
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).tier < 3600) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
- }
-
- // Lore
- if (Item.getEquippedItem(sdk.body.Head).tier < 100000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
- }
-
- // Merc Fortitude
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
-
- // Merc Treachery
- if (Item.getEquippedItemMerc(sdk.body.Armor).tier < 15000 && Item.getEquippedItem(sdk.body.RightArm).tier > 1100) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
- }
-
- // Treachery
- if (Item.getEquippedItem(sdk.body.Armor).tier < 634 && Item.getEquippedItem(sdk.body.RightArm).tier > 1100) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Treachery.js");
- }
-
- // Smoke
- if (Item.getEquippedItem(sdk.body.Armor).tier < 350) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
- }
-
- // Duress
- if (Item.getEquippedItem(sdk.body.Armor).tier < 600 && (me.checkItem({name: sdk.locale.items.CrescentMoon}).have || Item.getEquippedItem(sdk.body.LeftArm).tier > 900)) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Duress.js");
- }
-
- // Myth
- if (Item.getEquippedItem(sdk.body.Armor).tier < 340) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Myth.js");
- }
-
- // Kings Grace - Amn/Ral/Thul
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 770) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/KingsGrace.js");
- }
-
- // Steel - Tir/El
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Steel.js");
- }
-
- // Spirit Sword
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItem(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
- }
-
- // Malice - IthElEth
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 175) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Malice.js");
- }
-
- // Stealth
- if (Item.getEquippedItem(sdk.body.Armor).tier < 233) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
- }
-
- /*if (Item.getEquippedItem(sdk.body.Gloves).tier < 233) {
- NTIP.addLine("[name] == heavygloves && [flag] != ethereal && [quality] == magic # [itemchargedskill] >= 0 # [maxquantity] == 1");
- Config.Recipes.push([Recipe.Blood.Gloves, "Heavy Gloves"]); // Craft Blood Gloves
- }*/
-
- Check.itemSockables(sdk.items.Ataghan, "unique", "Djinn Slayer");
- SoloWants.buildList();
-
- break;
- }
-}
+(function LoadConfig () {
+ includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
+ includeIfNotIncluded("SoloPlay/Functions/Globals.js");
+
+ const LADDER_ENABLED = (me.ladder || Developer.addLadderRW);
+
+ SetUp.include();
+ SetUp.config();
+
+ /* Pickit configuration. */
+ Config.PickRange = 40;
+ 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.PickitFiles.push("kolton.nip");
+ // Config.PickitFiles.push("LLD.nip");
+
+ /* Gambling configuration. */
+ Config.GambleItems.push("Amulet");
+ Config.GambleItems.push("Ring");
+
+ /* AutoEquip configuration. */
+ Config.AutoEquip = true;
+
+ // AutoEquip setup
+ const levelingTiers = [
+ // Weapon
+ "me.charlvl < 12 && [type] == sword && ([quality] >= normal || [flag] == runeword) && [flag] != ethereal && [wsm] <= 20 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "[type] == sword && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [wsm] <= 10 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "[name] == phaseblade && [quality] == unique && [flag] == ethereal # [enhanceddamage] >= 100 && [ias] == 30 && [magicdamagereduction] >= 7 # [tier] == tierscore(item)",
+ // Helmet
+ "([type] == primalhelm) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "([type] == primalhelm) && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 3) # [tier] == tierscore(item)",
+ ];
+
+ const expansionTiers = [
+ // Charms
+ "[name] == smallcharm && [quality] == magic # # [invoquantity] == 8 && [charmtier] == charmscore(item)",
+ "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ ];
+
+ NTIP.buildList(levelingTiers);
+ me.expansion && NTIP.buildList(expansionTiers);
+
+ /* Attack configuration. */
+ Config.AttackSkill = [-1, 0, 0, 0, 0];
+ Config.LowManaSkill = me.getSkill(sdk.skills.DoubleSwing, sdk.skills.subindex.SoftPoints) >= 9
+ ? [sdk.skills.DoubleSwing, 0]
+ : [0, -1];
+ Config.MaxAttackCount = 1000;
+ Config.BossPriority = me.normal;
+ Config.ClearType = 0;
+ Config.ClearPath = { Range: (Pather.canTeleport() ? 30 : 10), Spectype: 0 };
+
+ // Class specific config
+ Config.FindItem = true; // Use Find Item skill on corpses after clearing.
+ Config.FindItemSwitch = false; // Switch to non-primary slot when using Find Item skills
+
+ Config.imbueables = (function () {
+ /**
+ * @param {number} name
+ * @param {function(): boolean} condition
+ */
+ const _imbueObj = (name, condition) => ({ name: name, condition: condition });
+
+ return [
+ _imbueObj(
+ sdk.items.AvengerGuard,
+ function () {
+ return me.normal && me.expansion;
+ }
+ ),
+ _imbueObj(
+ sdk.items.SlayerGuard,
+ function () {
+ return !me.normal && me.trueStr >= 118 && me.expansion;
+ }
+ ),
+ _imbueObj(
+ sdk.items.CarnageHelm,
+ function () {
+ return me.equipped.get(sdk.body.Head).tier < 100000 && me.trueStr >= 106 && me.expansion;
+ }
+ ),
+ _imbueObj(
+ sdk.items.Belt,
+ function () {
+ return me.normal && (me.equipped.get(sdk.body.Head).tier > 100000 || me.classic);
+ }
+ ),
+ _imbueObj(
+ sdk.items.MeshBelt,
+ function () {
+ return !me.normal && me.charlvl < 46 && me.trueStr > 58
+ && (me.equipped.get(sdk.body.RightArm).tier > 100000 || me.classic);
+ }
+ ),
+ _imbueObj(
+ sdk.items.SpiderwebSash,
+ function () {
+ return !me.normal && me.trueStr > 50 && (me.equipped.get(sdk.body.RightArm).tier > 100000 || me.classic);
+ }
+ ),
+ ].filter((item) => item.condition());
+ })();
+
+ let imbueArr = SetUp.imbueItems();
+
+ !me.smith && NTIP.buildList(imbueArr);
+
+ switch (me.gametype) {
+ case sdk.game.gametype.Classic:
+ break;
+ case sdk.game.gametype.Expansion:
+ NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
+ const { basicSocketables, addSocketableObj } = require("../Utils/General");
+
+ /** @param {ItemUnit} item */
+ const honorCheck = function (item) {
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Sword,
+ mode: sdk.items.mode.inStorage,
+ sockets: 5,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+ return (item.ilvl >= 41 && item.isBaseType && !item.ethereal
+ && !me.getOwned(wanted).length
+ && !me.checkItem({ name: sdk.locale.items.Honor }).have);
+ };
+
+ Config.socketables = Config.socketables.concat(basicSocketables.all);
+ Config.socketables.push(addSocketableObj(sdk.items.Flamberge, [], [],
+ true,
+ /** @param {ItemUnit} item */
+ function (item) {
+ return me.normal && me.equipped.get(sdk.body.LeftArm).tier < 600 && honorCheck(item);
+ }
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.Zweihander, [], [],
+ true,
+ /** @param {ItemUnit} item */
+ function (item) {
+ return me.equipped.get(sdk.body.LeftArm).tier < 1000 && honorCheck(item);
+ }
+ ));
+
+ if (SetUp.finalBuild !== "Immortalwhirl") {
+ Config.socketables.push(addSocketableObj(sdk.items.SlayerGuard,
+ [sdk.items.runes.Cham], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+ }
+
+ if (["Immortalwhirl", "Singer"].indexOf(SetUp.finalBuild) === -1) {
+ // Grief
+ if (LADDER_ENABLED
+ && (
+ !me.checkItem({ name: sdk.locale.items.Grief }).have
+ || (SetUp.finalBuild === "Whirlwind" && me.equipped.get(sdk.body.LeftArm).prefixnum !== sdk.locale.items.Grief)
+ )) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Grief.js");
+ }
+
+ // Fortitude
+ if (LADDER_ENABLED && SetUp.finalBuild !== "Uberconc"
+ && me.checkItem({ name: sdk.locale.items.Grief }).have
+ && !me.checkItem({ name: sdk.locale.items.Fortitude, itemtype: sdk.items.type.Armor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Fortitude.js");
+ }
+
+ // Doom
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).prefixnum !== 20532) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercDoom.js");
+ }
+ }
+
+ // FinalBuild specific setup
+ switch (SetUp.finalBuild) {
+ case "Uberconc":
+ if (me.checkItem({ name: sdk.locale.items.Grief }).have && SetUp.finalBuild === "Uberconc") {
+ // Add Stormshield
+ NTIP.addLine("[name] == monarch && [quality] == unique && [flag] != ethereal # [damageresist] >= 35 # [tier] == 100000");
+ }
+
+ // Chains of Honor
+ if (!me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
+ }
+
+ break;
+ case "Frenzy":
+ // Breathe of the Dying
+ if (!me.checkItem({ name: sdk.locale.items.BreathoftheDying }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/BreathoftheDying.js");
+ }
+
+ break;
+ case "Singer":
+ // Heart of the Oak
+ if (me.equipped.get(sdk.body.LeftArm).prefixnum !== sdk.locale.items.HeartoftheOak
+ && me.checkItem({ name: sdk.locale.items.Enigma }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
+ }
+
+ // Enigma
+ if (!me.checkItem({ name: sdk.locale.items.Enigma }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
+ }
+
+ break;
+ case "Immortalwhirl":
+ // Infinity
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
+ }
+
+ Config.socketables.push(addSocketableObj(sdk.items.AvengerGuard,
+ [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
+ false, (item) => item.set && !item.ethereal
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.OgreMaul,
+ [sdk.items.runes.Shael], [],
+ false, (item) => item.set && !item.ethereal
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.SacredArmor,
+ [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.set && !item.ethereal
+ ));
+
+ Check.itemSockables(sdk.items.OgreMaul, "set", "Immortal King's Stone Crusher");
+
+ break;
+ case "Whirlwind":
+ break;
+ default:
+ break;
+ }
+
+ /* Crafting */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Check.currentBuild().caster
+ ? Config.Recipes.push([Recipe.Caster.Amulet])
+ : Config.Recipes.push([Recipe.Blood.Amulet]);
+ }
+
+ if (me.equipped.get(sdk.body.RingLeft).tier < 100000) {
+ Check.currentBuild().caster
+ ? Config.Recipes.push([Recipe.Caster.Ring])
+ : Config.Recipes.push([Recipe.Blood.Ring]);
+ }
+
+ if (me.equipped.get(sdk.body.LeftArm).tier < 1370) {
+ if (me.rawStrength >= 150 && me.rawDexterity >= 88) {
+ // Upgrade Bloodletter to Elite
+ Config.Recipes.push([Recipe.Unique.Weapon.ToElite, "Gladius", Roll.NonEth]);
+ }
+
+ if (me.rawStrength >= 25 && me.rawDexterity >= 136) {
+ // Upgrade Ginther's Rift to Elite
+ Config.Recipes.push([Recipe.Unique.Weapon.ToElite, "dimensionalblade", Roll.Eth]);
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.Bloodletter, classid: sdk.items.Falcata }).have) {
+ NTIP.addLine("[name] == PulRune # # [maxquantity] == 1");
+ NTIP.addLine("[name] == perfectemerald # # [maxquantity] == 1");
+ // Bloodletter
+ NTIP.addLine("[name] == gladius && [quality] == unique && [flag] != ethereal # [enhanceddamage] >= 140 && [ias] >= 20 # [maxquantity] == 1");
+ // upped Bloodletter
+ NTIP.addLine("[name] == falcata && [quality] == unique && [flag] != ethereal # [enhanceddamage] >= 140 && [ias] >= 20 # [maxquantity] == 1");
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.GinthersRift, classid: sdk.items.DimensionalBlade }).have) {
+ NTIP.addLine("[name] == PulRune # # [maxquantity] == 1");
+ NTIP.addLine("[name] == perfectemerald # # [maxquantity] == 1");
+
+ // Have Pul rune before looking for eth ginther's
+ if (me.getItem(sdk.items.runes.Pul)) {
+ // Eth Ginther's Rift
+ NTIP.addLine("[name] == dimensionalblade && [quality] == unique && [flag] == ethereal # [enhanceddamage] >= 100 && [ias] == 30 && [magicdamagereduction] >= 7 # [maxquantity] == 1");
+ }
+
+ // upped Ginther's Rift
+ NTIP.addLine("[name] == phaseblade && [quality] == unique && [flag] == ethereal # [enhanceddamage] >= 100 && [ias] == 30 && [magicdamagereduction] >= 7 # [maxquantity] == 1");
+ }
+ }
+
+ // Lawbringer - Amn/Lem/Ko
+ if (me.equipped.get(sdk.body.LeftArm).tier < 1370) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lawbringer.js");
+ }
+
+ // Voice Of Reason - Lem/Ko/El/Eld
+ if (me.equipped.get(sdk.body.RightArm).tier > 1100
+ && me.equipped.get(sdk.body.LeftArm).tier < 1270
+ && !me.checkItem({ name: sdk.locale.items.Ravenfrost }).have
+ ) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/VoiceOfReason.js");
+ }
+
+ // Crescent Moon - Shael/Um/Tir
+ if (me.equipped.get(sdk.body.LeftArm).tier < 1100) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CrescentMoon.js");
+ }
+
+ if (me.equipped.get(sdk.body.LeftArm).tier < 1200) {
+ // Cube to Ko Rune
+ if (!me.getItem(sdk.items.runes.Ko)) {
+ Config.Recipes.push([Recipe.Rune, "Hel Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Io Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
+ }
+
+ // Cube to Lem Rune
+ if (!me.getItem(sdk.items.runes.Lem)) {
+ Config.Recipes.push([Recipe.Rune, "Dol Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Io Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Lum Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ko Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Fal Rune"]);
+ }
+ }
+
+ // Honor - Amn/El/Ith/Tir/Sol
+ if (me.equipped.get(sdk.body.LeftArm).tier < 1050) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Honor.js");
+ }
+
+ // Merc Insight
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).tier < 3600) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
+ }
+
+ // Lore
+ if (me.equipped.get(sdk.body.Head).tier < 100000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
+ }
+
+ // Merc Fortitude
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+
+ // Merc Treachery
+ if (Item.getMercEquipped(sdk.body.Armor).tier < 15000 && me.equipped.get(sdk.body.RightArm).tier > 1100) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
+ }
+
+ // Treachery
+ if (me.equipped.get(sdk.body.Armor).tier < 634 && me.equipped.get(sdk.body.RightArm).tier > 1100) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Treachery.js");
+ }
+
+ // Smoke
+ if (me.equipped.get(sdk.body.Armor).tier < 350) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
+ }
+
+ // Duress
+ if (me.equipped.get(sdk.body.Armor).tier < 600
+ && (
+ me.checkItem({ name: sdk.locale.items.CrescentMoon }).have
+ || me.equipped.get(sdk.body.LeftArm).tier > 900
+ )) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Duress.js");
+ }
+
+ // Myth
+ if (me.equipped.get(sdk.body.Armor).tier < 340) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Myth.js");
+ }
+
+ // Kings Grace - Amn/Ral/Thul
+ if (me.equipped.get(sdk.body.LeftArm).tier < 770) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/KingsGrace.js");
+ }
+
+ // Steel - Tir/El
+ if (me.equipped.get(sdk.body.LeftArm).tier < 500) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Steel.js");
+ }
+
+ // Spirit Sword
+ if (LADDER_ENABLED && me.equipped.get(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
+ }
+
+ // Malice - IthElEth
+ if (me.equipped.get(sdk.body.LeftArm).tier < 175) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Malice.js");
+ }
+
+ // Stealth
+ if (me.equipped.get(sdk.body.Armor).tier < 233) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
+ }
+
+ /*if (me.equipped.get(sdk.body.Gloves).tier < 233) {
+ NTIP.addLine("[name] == heavygloves && [flag] != ethereal && [quality] == magic # [itemchargedskill] >= 0 # [maxquantity] == 1");
+ Config.Recipes.push([Recipe.Blood.Gloves, "Heavy Gloves"]); // Craft Blood Gloves
+ }*/
+
+ Check.itemSockables(sdk.items.Ataghan, "unique", "Djinn Slayer");
+ SoloWants.buildList();
+
+ break;
+ }
+
+ return true;
+})();
diff --git a/libs/SoloPlay/Config/Druid.js b/libs/SoloPlay/Config/Druid.js
index 34071673..30366063 100644
--- a/libs/SoloPlay/Config/Druid.js
+++ b/libs/SoloPlay/Config/Druid.js
@@ -16,281 +16,297 @@
* 4. Save the profile and start
*/
-function LoadConfig () {
- includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
- includeIfNotIncluded("SoloPlay/Functions/Globals.js");
-
- SetUp.include();
-
- /* Script */
- Scripts.SoloPlay = true;
- SetUp.config();
-
- /* Chicken configuration. */
- Config.LifeChicken = me.hardcore ? 45 : 10;
- Config.ManaChicken = 0;
- Config.MercChicken = 0;
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.TownMP = 0;
-
- /* Potions configuration. */
- Config.UseHP = me.hardcore ? 90 : 75;
- Config.UseRejuvHP = me.hardcore ? 65 : 40;
- Config.UseMP = me.hardcore ? 75 : 55;
- Config.UseMercHP = 75;
-
- /* Belt configuration. */
- Config.BeltColumn = ["hp", "mp", "mp", "rv"];
- SetUp.belt();
-
- /* Pickit configuration. */
- Config.PickRange = 40;
- // Config.PickitFiles.push("kolton.nip");
- // Config.PickitFiles.push("LLD.nip");
- NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
-
- /* Gambling configuration. */
- Config.Gamble = true;
- Config.GambleGoldStart = 2000000;
- Config.GambleGoldStop = 750000;
- Config.GambleItems.push("amulet");
- Config.GambleItems.push("ring");
- Config.GambleItems.push("circlet");
- Config.GambleItems.push("coronet");
-
- /* AutoMule configuration. */
- Config.AutoMule.Trigger = [];
- Config.AutoMule.Force = [];
- Config.AutoMule.Exclude = [
- "[name] >= Elrune && [name] <= Lemrune",
- ];
-
- /* AutoEquip configuration. */
- Config.AutoEquip = true;
-
- // AutoEquip setup
- const levelingTiers = [
- // Weapon
- "([type] == wand || [type] == sword || [type] == mace || [type] == knife) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Helmet
- "([type] == helm || [type] == circlet || [type] == pelt) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[type] == pelt && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && [sockets] == 1 # [tier] == tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.normal && [type] == belt && [quality] >= lowquality && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Armor
- "[type] == armor && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Shield
- "[type] == shield && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Gloves
- "[type] == gloves && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Amulet
- "[type] == amulet && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Rings
- "[type] == ring && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Switch
- // Charms
- "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- ];
-
- NTIP.arrayLooping(levelingTiers);
-
- let switchTiers = (["Wolf", "Plaguewolf"].includes(SetUp.currentBuild)
- ? [
- "[name] == elderstaff && [quality] == unique # [itemallskills] >= 2 # [secondarytier] == tierscore(item)", // Ondal's
- "[name] == archonstaff && [quality] == unique # [itemallskills] == 5 # [secondarytier] == tierscore(item)" // Mang Song's
- ]
- : [
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 72 # [secondarytier] == 25000", // Weaken charged wand
- "[name] == beardedaxe && [quality] == unique # [itemchargedskill] == 87 # [secondarytier] == 50000", // Spellsteel Decrepify charged axe
- ]);
-
- NTIP.arrayLooping(switchTiers);
-
- /* Attack configuration. */
- Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
- Config.LowManaSkill = [0, 0];
- Config.MaxAttackCount = 1000;
- Config.BossPriority = me.normal;
- Config.ClearType = 0;
- Config.ClearPath = {Range: (Pather.canTeleport() ? 30 : 10), Spectype: 0};
-
- /* Class specific configuration. */
- /* Wereform */
- Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear
-
- /* Summons */
- Config.SummonRaven = false;
- Config.SummonVine = 0; // 0 = disabled, 1 / "Poison Creeper", 2 / "Carrion Vine", 3 / "Solar Creeper"
- Config.SummonSpirit = 0; // 0 = disabled, 1 / "Oak Sage", 2 / "Heart of Wolverine", 3 / "Spirit of Barbs"
- Config.SummonAnimal = 0; // 0 = disabled, 1 or "Spirit Wolf" = summon spirit wolf, 2 or "Dire Wolf" = summon dire wolf, 3 or "Grizzly" = summon grizzly
-
- /* Gear */
- let finalGear = Check.finalBuild().finalGear;
- !!finalGear && NTIP.arrayLooping(finalGear);
-
- Config.imbueables = [
- {name: sdk.items.SpiritMask, condition: () => (me.normal)},
- {name: sdk.items.TotemicMask, condition: () => (!me.normal && Item.getEquippedItem(sdk.body.Head).tier < 100000 && (me.charlvl < 66 || me.trueStr < 118))},
- {name: sdk.items.DreamSpirit, condition: () => (Item.getEquippedItem(sdk.body.Head).tier < 100000 && me.trueStr >= 118)},
- {name: sdk.items.Belt, condition: () => (me.normal && (Item.getEquippedItem(sdk.body.Head).tier > 100000))},
- {name: sdk.items.MeshBelt, condition: () => (!me.normal && me.charlvl < 46 && me.trueStr > 58 && (Item.getEquippedItem(sdk.body.Head).tier > 100000))},
- {name: sdk.items.SpiderwebSash, condition: () => (!me.normal && me.trueStr > 50 && (Item.getEquippedItem(sdk.body.Head).tier > 100000))},
- ].filter((item) => item.condition());
-
- let imbueArr = SetUp.imbueItems();
-
- !me.smith && NTIP.arrayLooping(imbueArr);
-
- // basicSocketables located in Globals
- Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
- Config.socketables.push(addSocketableObj(sdk.items.Monarch, [], [],
- !me.hell, (item) => !Check.haveBase("monarch", 4) && item.ilvl >= 41 && item.isBaseType && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.TotemicMask, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
-
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- // Spirit on swap
- NTIP.addLine("[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000");
- }
-
- /* Crafting */
- if (Item.getEquippedItem(sdk.body.Neck).tier < 100000) {
- Check.currentBuild().caster ? Config.Recipes.push([Recipe.Caster.Amulet]) : Config.Recipes.push([Recipe.Blood.Amulet]);
- }
-
- if (Item.getEquippedItem(sdk.body.RingLeft).tier < 100000) {
- Check.currentBuild().caster ? Config.Recipes.push([Recipe.Caster.Ring]) : Config.Recipes.push([Recipe.Blood.Ring]);
- }
-
- // FinalBuild specific setup
- switch (SetUp.finalBuild) {
- case "Wind":
- case "Elemental":
- // Call to Arms
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
- }
-
- if (SetUp.finalBuild === "Elemental") {
- Config.socketables.push(addSocketableObj(sdk.items.SkySpirit, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && item.getStat(sdk.stats.PassiveFirePierce) === 20 && !item.ethereal
- ));
-
- // Phoenix Shield
- if ((me.ladder || Developer.addLadderRW) && SetUp.finalBuild === "Elemental" && me.checkItem({name: sdk.locale.items.Enigma}).have && !me.checkItem({name: sdk.locale.items.Phoenix, itemtype: sdk.items.type.Shield}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PhoneixShield.js");
- }
- }
-
- // Heart of the Oak
- if (!me.checkItem({name: sdk.locale.items.HeartoftheOak}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
- }
-
- // Enigma
- if (!me.checkItem({name: sdk.locale.items.Enigma}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
- }
-
- // upgrade magefist
- if (Item.getEquippedItem(sdk.body.Gloves).tier < 110000) {
- Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]);
- Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth, "magefist"]);
- }
-
- break;
- case "Wolf":
- case "Plaguewolf":
- // Chains of Honor
- if (!me.checkItem({name: sdk.locale.items.ChainsofHonor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
- }
-
- if (SetUp.finalBuild === "Plaguewolf") {
- // Grief
- if (!me.checkItem({name: sdk.locale.items.Grief}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Grief.js");
- }
- } else {
- // Make sure to have CoH first
- if (me.checkItem({name: sdk.locale.items.ChainsofHonor}).have) {
- // Upgrade Ribcracker to Elite
- Config.Recipes.push([Recipe.Unique.Weapon.ToElite, "quarterstaff", Roll.NonEth]);
- }
-
- // Don't have upgraded Ribcracker
- if (!Check.haveItem("stalagmite", "unique", "Ribcracker")) {
- // Perfect ribcracker
- NTIP.addLine("[name] == quarterstaff && [quality] == unique # [enhanceddamage] == 300 && [ias] >= 50 # [maxquantity] == 1");
- // Perfect upped ribcracker
- NTIP.addLine("[name] == stalagmite && [quality] == unique # [enhanceddamage] == 300 && [ias] >= 50 # [maxquantity] == 1");
- }
- }
-
- break;
- default:
- break;
- }
-
- Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
- Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
- Check.itemSockables(sdk.items.TotemicMask, "unique", "Jalal's Mane");
-
- // Spirit Sword
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItem(sdk.body.RightArm).tier < 777) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
- }
-
- // Spirit Shield
- if ((me.ladder || Developer.addLadderRW) && (Item.getEquippedItem(sdk.body.LeftArm).tier < 1000 || Item.getEquippedItem(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
- }
-
- // Merc Insight
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).tier < 3600) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
- }
-
- // Lore
- if (Item.getEquippedItem(sdk.body.Head).tier < 100000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
- }
-
- // Ancients' Pledge
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
- }
-
- // Merc Fortitude
- if (Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
-
- // Merc Treachery
- if (Item.getEquippedItemMerc(sdk.body.Armor).tier < 15000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
- }
-
- // Smoke
- if (Item.getEquippedItem(sdk.body.Armor).tier < 634) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
- }
-
- // Stealth
- if (Item.getEquippedItem(sdk.body.Armor).tier < 233) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
- }
-
- SoloWants.buildList();
-}
+(function LoadConfig () {
+ includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
+ includeIfNotIncluded("SoloPlay/Functions/Globals.js");
+
+ const LADDER_ENABLED = (me.ladder || Developer.addLadderRW);
+
+ SetUp.include();
+ SetUp.config();
+
+ /* Pickit configuration. */
+ Config.PickRange = 40;
+ // Config.PickitFiles.push("kolton.nip");
+ // Config.PickitFiles.push("LLD.nip");
+ NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
+
+ /* Gambling configuration. */
+ Config.GambleItems.push("amulet");
+ Config.GambleItems.push("ring");
+ Config.GambleItems.push("circlet");
+ Config.GambleItems.push("coronet");
+
+ // AutoEquip setup
+ const levelingTiers = [
+ // Weapon
+ "([type] == wand || [type] == sword || [type] == mace || [type] == knife) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Helmet
+ "([type] == pelt) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "([type] == pelt) && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 3) # [tier] == tierscore(item)",
+ // Shield
+ "[type] == shield && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Charms
+ "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ ];
+
+ NTIP.buildList(levelingTiers);
+
+ let switchTiers = (["Wolf", "Plaguewolf"].includes(SetUp.currentBuild)
+ ? [
+ "[name] == elderstaff && [quality] == unique # [itemallskills] >= 2 # [secondarytier] == tierscore(item)", // Ondal's
+ "[name] == archonstaff && [quality] == unique # [itemallskills] == 5 # [secondarytier] == tierscore(item)" // Mang Song's
+ ]
+ : [
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 72 # [secondarytier] == 25000", // Weaken charged wand
+ "[name] == beardedaxe && [quality] == unique # [itemchargedskill] == 87 # [secondarytier] == 50000", // Spellsteel Decrepify charged axe
+ ]);
+
+ NTIP.buildList(switchTiers);
+
+ /* Attack configuration. */
+ Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ Config.MaxAttackCount = 1000;
+ Config.BossPriority = me.normal;
+ Config.ClearType = 0;
+ Config.ClearPath = { Range: (Pather.canTeleport() ? 30 : 10), Spectype: 0 };
+
+ /* Class specific configuration. */
+ /* Wereform */
+ Config.Wereform = false; // 0 / false - don't shapeshift, 1 / "Werewolf" - change to werewolf, 2 / "Werebear" - change to werebear
+
+ /* Summons */
+ Config.SummonRaven = false;
+ Config.SummonVine = 0; // 0 = disabled, 1 / "Poison Creeper", 2 / "Carrion Vine", 3 / "Solar Creeper"
+ Config.SummonSpirit = 0; // 0 = disabled, 1 / "Oak Sage", 2 / "Heart of Wolverine", 3 / "Spirit of Barbs"
+ Config.SummonAnimal = 0; // 0 = disabled, 1 or "Spirit Wolf" = summon spirit wolf, 2 or "Dire Wolf" = summon dire wolf, 3 or "Grizzly" = summon grizzly
+
+ Config.imbueables = (function () {
+ /**
+ * @param {number} name
+ * @param {function(): boolean} condition
+ */
+ const _imbueObj = (name, condition) => ({ name: name, condition: condition });
+
+ return [
+ _imbueObj(
+ sdk.items.SpiritMask,
+ function () {
+ return me.normal;
+ }
+ ),
+ _imbueObj(
+ sdk.items.TotemicMask,
+ function () {
+ return !me.normal && me.equipped.get(sdk.body.Head).tier < 100000 && (me.charlvl < 66 || me.trueStr < 118);
+ }
+ ),
+ _imbueObj(
+ sdk.items.DreamSpirit,
+ function () {
+ return me.equipped.get(sdk.body.Head).tier < 100000 && me.trueStr >= 118;
+ }
+ ),
+ _imbueObj(
+ sdk.items.Belt,
+ function () {
+ return me.normal && me.equipped.get(sdk.body.Head).tier > 100000;
+ }
+ ),
+ _imbueObj(
+ sdk.items.MeshBelt,
+ function () {
+ return !me.normal && me.charlvl < 46 && me.trueStr > 58
+ && (me.equipped.get(sdk.body.Head).tier > 100000);
+ }
+ ),
+ _imbueObj(
+ sdk.items.SpiderwebSash,
+ function () {
+ return !me.normal && me.trueStr > 50 && (me.equipped.get(sdk.body.Head).tier > 100000);
+ }
+ ),
+ ].filter((item) => item.condition());
+ })();
+
+ let imbueArr = SetUp.imbueItems();
+
+ !me.smith && NTIP.buildList(imbueArr);
+
+ const { basicSocketables, addSocketableObj } = require("../Utils/General");
+
+ Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
+ Config.socketables.push(addSocketableObj(sdk.items.Monarch, [], [],
+ !me.hell,
+ /** @param {ItemUnit} item */
+ function (item) {
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.Monarch,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+ return !me.getOwned(wanted).length && item.ilvl >= 41 && item.isBaseType && !item.ethereal;
+ }
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.TotemicMask, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ // Spirit on swap
+ NTIP.addLine("[name] == monarch && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000");
+ }
+
+ /* Crafting */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Check.currentBuild().caster
+ ? Config.Recipes.push([Recipe.Caster.Amulet])
+ : Config.Recipes.push([Recipe.Blood.Amulet]);
+ }
+
+ if (me.equipped.get(sdk.body.RingLeft).tier < 100000) {
+ Check.currentBuild().caster
+ ? Config.Recipes.push([Recipe.Caster.Ring])
+ : Config.Recipes.push([Recipe.Blood.Ring]);
+ }
+
+ // FinalBuild specific setup
+ switch (SetUp.finalBuild) {
+ case "Wind":
+ case "Elemental":
+ // Call to Arms
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
+ }
+
+ if (SetUp.finalBuild === "Elemental") {
+ Config.socketables.push(addSocketableObj(sdk.items.SkySpirit,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && item.getStat(sdk.stats.PassiveFirePierce) === 20 && !item.ethereal
+ ));
+
+ // Phoenix Shield
+ if (LADDER_ENABLED && SetUp.finalBuild === "Elemental" && me.checkItem({ name: sdk.locale.items.Enigma }).have && !me.checkItem({ name: sdk.locale.items.Phoenix, itemtype: sdk.items.type.Shield }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PhoneixShield.js");
+ }
+ }
+
+ // Heart of the Oak
+ if (!me.checkItem({ name: sdk.locale.items.HeartoftheOak }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
+ }
+
+ // Enigma
+ if (!me.checkItem({ name: sdk.locale.items.Enigma }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
+ }
+
+ // upgrade magefist
+ if (me.equipped.get(sdk.body.Gloves).tier < 110000) {
+ Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]);
+ Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth, "magefist"]);
+ }
+
+ break;
+ case "Wolf":
+ case "Plaguewolf":
+ // Chains of Honor
+ if (!me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
+ }
+
+ if (SetUp.finalBuild === "Plaguewolf") {
+ // Grief
+ if (!me.checkItem({ name: sdk.locale.items.Grief }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Grief.js");
+ }
+ } else {
+ // Make sure to have CoH first
+ if (me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have) {
+ // Upgrade Ribcracker to Elite
+ Config.Recipes.push([Recipe.Unique.Weapon.ToElite, "quarterstaff", Roll.NonEth]);
+ }
+
+ // Don't have upgraded Ribcracker
+ if (!me.checkItem({ name: sdk.locale.items.Ribcracker, classid: sdk.items.Stalagmite }).have) {
+ // Perfect ribcracker
+ NTIP.addLine("[name] == quarterstaff && [quality] == unique # [enhanceddamage] == 300 && [ias] >= 50 # [maxquantity] == 1");
+ // Perfect upped ribcracker
+ NTIP.addLine("[name] == stalagmite && [quality] == unique # [enhanceddamage] == 300 && [ias] >= 50 # [maxquantity] == 1");
+ }
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
+ Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
+ Check.itemSockables(sdk.items.TotemicMask, "unique", "Jalal's Mane");
+
+ // Spirit Sword
+ if (LADDER_ENABLED && me.equipped.get(sdk.body.RightArm).tier < 777) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
+ }
+
+ // Spirit Shield
+ if (LADDER_ENABLED
+ && (
+ me.equipped.get(sdk.body.LeftArm).tier < 1000
+ || me.equipped.get(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit
+ )) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
+ }
+
+ // Merc Insight
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).tier < 3600) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
+ }
+
+ // Lore
+ if (me.equipped.get(sdk.body.Head).tier < 100000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
+ }
+
+ // Ancients' Pledge
+ if (me.equipped.get(sdk.body.LeftArm).tier < 500) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
+ }
+
+ // Merc Fortitude
+ if (Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+
+ // Merc Treachery
+ if (Item.getMercEquipped(sdk.body.Armor).tier < 15000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
+ }
+
+ // Smoke
+ if (me.equipped.get(sdk.body.Armor).tier < 634) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
+ }
+
+ // Stealth
+ if (me.equipped.get(sdk.body.Armor).tier < 233) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
+ }
+
+ SoloWants.buildList();
+
+ return true;
+})();
diff --git a/libs/SoloPlay/Config/Necromancer.js b/libs/SoloPlay/Config/Necromancer.js
index 44bbf700..c23443bb 100644
--- a/libs/SoloPlay/Config/Necromancer.js
+++ b/libs/SoloPlay/Config/Necromancer.js
@@ -15,252 +15,259 @@
* 4. Save the profile and start
*/
-function LoadConfig () {
- includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
- includeIfNotIncluded("SoloPlay/Functions/Globals.js");
-
- SetUp.include();
-
- /* Script */
- Scripts.SoloPlay = true;
- SetUp.config();
-
- /* Chicken configuration. */
- Config.LifeChicken = me.hardcore ? 45 : 10;
- Config.ManaChicken = 0;
- Config.MercChicken = 0;
- Config.IronGolemChicken = 30;
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.TownMP = 0;
-
- /* Potions configuration. */
- Config.UseHP = me.hardcore ? 90 : 75;
- Config.UseRejuvHP = me.hardcore ? 65 : 40;
- Config.UseMP = me.hardcore ? 75 : 55;
- Config.UseMercHP = 75;
-
- /* Belt configuration. */
- Config.BeltColumn = ["hp", "mp", "mp", "rv"];
- SetUp.belt();
-
- /* Pickit configuration. */
- Config.PickRange = 40;
- // Config.PickitFiles.push("kolton.nip");
- // Config.PickitFiles.push("LLD.nip");
-
- /* Gambling configuration. */
- Config.Gamble = true;
- Config.GambleGoldStart = 2000000;
- Config.GambleGoldStop = 750000;
- Config.GambleItems.push("Amulet");
- Config.GambleItems.push("Ring");
- Config.GambleItems.push("Circlet");
- Config.GambleItems.push("Coronet");
-
- /* AutoMule configuration. */
- Config.AutoMule.Trigger = [];
- Config.AutoMule.Force = [];
- Config.AutoMule.Exclude = [
- "[name] >= Elrune && [name] <= Lemrune",
- ];
-
- /* AutoEquip configuration. */
- Config.AutoEquip = true;
-
- // AutoEquip setup
- const levelingTiers = [
- // Weapon
- "([type] == wand || [type] == sword || [type] == knife) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[type] == wand && [quality] >= normal && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 && [sockets] != 2 # [tier] == tierscore(item)",
- // Helmet
- "([type] == helm || [type] == circlet) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.normal && [type] == belt && [quality] >= lowquality && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Armor
- "[type] == armor && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Shield
- "([type] == shield && ([quality] >= magic || [flag] == runeword) || [type] == voodooheads) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[type] == voodooheads && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && [sockets] == 1 # [tier] == tierscore(item)",
- "me.classic && [type] == shield && [quality] >= normal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Gloves
- "[type] == gloves && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Amulet
- "[type] == amulet && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Rings
- "[type] == ring && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- ];
-
- const expansionTiers = [
- "me.charlvl < 33 && [name] == smallcharm && [quality] == magic # [maxmana] >= 1 # [invoquantity] == 4 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # [fireresist]+[lightresist]+[coldresist]+[poisonresist] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- ];
-
- NTIP.arrayLooping(levelingTiers);
- me.expansion && NTIP.arrayLooping(expansionTiers);
-
- /* Attack configuration. */
- Skill.usePvpRange = true;
- Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
- Config.LowManaSkill = [0, 0];
- Config.MaxAttackCount = 1000;
- Config.BossPriority = me.normal;
- Config.ClearType = 0;
- Config.ClearPath = {Range: (Pather.canTeleport() ? 30 : 20), Spectype: 0};
-
- /* Class specific configuration. */
- Config.Dodge = Check.haveItem("armor", "runeword", "Enigma");
- Config.DodgeRange = Check.haveItem("armor", "runeword", "Enigma") ? 10 : 5;
- Config.DodgeHP = 90; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge.
-
- /* Summons. */
- Config.ReviveUnstackable = true;
- Config.ActiveSummon = me.charlvl < 10 || SetUp.currentBuild === "Summon";
- Config.Golem = me.checkSkill(sdk.skills.ClayGolem, sdk.skills.subindex.HardPoints) ? "Clay" : "None";
- Config.Skeletons = (me.charlvl > 10 && SetUp.currentBuild !== "Summon") ? 0 : "max";
- Config.SkeletonMages = (me.charlvl > 10 && SetUp.currentBuild !== "Summon") ? 0 : "max";
- Config.Revives = (me.charlvl > 10 && SetUp.currentBuild !== "Summon") ? 0 : "max";
-
- /* Skill Specific */
- Config.PoisonNovaDelay = 1; // In Seconds
- Config.ExplodeCorpses = me.checkSkill(sdk.skills.CorpseExplosion, sdk.skills.subindex.HardPoints) ? sdk.skills.CorpseExplosion : me.checkSkill(sdk.skills.PoisonExplosion, sdk.skills.subindex.HardPoints) ? sdk.skills.PoisonExplosion : 0;
-
- /* Gear */
- let finalGear = Check.finalBuild().finalGear;
- !!finalGear && NTIP.arrayLooping(finalGear);
-
- Config.imbueables = [
- {name: sdk.items.DemonHead, condition: () => (me.normal && me.expansion)},
- {name: sdk.items.HierophantTrophy, condition: () => (!me.normal && (me.charlvl < 66 || me.trueStr < 106) && me.expansion)},
- {name: sdk.items.BloodlordSkull, condition: () => (Item.getEquippedItem(sdk.body.LeftArm).tier < 1000 && me.expansion)},
- {name: sdk.items.Belt, condition: () => (me.normal && (Item.getEquippedItem(sdk.body.LeftArm).tier > 1000 || me.classic))},
- {name: sdk.items.MeshBelt, condition: () => (!me.normal && me.charlvl < 46 && me.trueStr > 58 && (Item.getEquippedItem(sdk.body.LeftArm).tier > 1000 || me.classic))},
- {name: sdk.items.SpiderwebSash, condition: () => (!me.normal && me.trueStr > 50 && (Item.getEquippedItem(sdk.body.LeftArm).tier > 1000 || me.classic))},
- ].filter((item) => item.condition());
-
- let imbueArr = SetUp.imbueItems();
-
- !me.smith && NTIP.arrayLooping(imbueArr);
-
- switch (me.gametype) {
- case sdk.game.gametype.Classic:
- // Res shield
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 487) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PDiamondShield.js");
- }
-
- break;
- case sdk.game.gametype.Expansion:
- NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
-
- // basicSocketables located in Globals
- Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
- Config.socketables.push(addSocketableObj(sdk.items.Monarch, [], [],
- !me.hell, (item) => !Check.haveBase("monarch", 4) && item.ilvl >= 41 && item.isBaseType && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
-
- /* Crafting */
- if (Item.getEquippedItem(sdk.body.Neck).tier < 100000) {
- Config.Recipes.push([Recipe.Caster.Amulet]);
- }
-
- // upgrade magefist
- if (Item.getEquippedItem(sdk.body.Gloves).tier < 110000) {
- Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]);
- Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth, "magefist"]);
- }
-
- Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
- Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
-
- /* Crafting */
- if (Item.getEquippedItem(sdk.body.Neck).tier < 100000) {
- Config.Recipes.push([Recipe.Caster.Amulet]);
- }
-
- if (Item.getEquippedItem(sdk.body.RingLeft).tier < 100000) {
- Config.Recipes.push([Recipe.Caster.Ring]);
- }
-
- // Call to Arms
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
- }
-
- // White
- if (SetUp.currentBuild !== "Summon") {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/White.js");
- }
-
- // Rhyme
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 650) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Rhyme.js");
- }
-
- // Enigma
- if (!me.checkItem({name: sdk.locale.items.Enigma}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
- }
-
- // Spirit Sword
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItem(sdk.body.RightArm).tier < 777) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
- }
-
- // Spirit shield
- if ((me.ladder || Developer.addLadderRW) && (Item.getEquippedItem(sdk.body.LeftArm).tier < 1000 || Item.getEquippedItem(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
- }
-
- // Merc Insight
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).tier < 3600) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
- }
-
- if (!me.haveSome([{name: sdk.locale.items.Enigma}, {name: sdk.locale.items.Bone}]) && Item.getEquippedItem(sdk.body.Armor).tier < 650) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Bone.js");
- }
-
- // Lore
- if (Item.getEquippedItem(sdk.body.Head).tier < 315) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
- }
-
- // Ancients' Pledge
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
- }
-
- // Merc Fortitude
- if (Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
-
- // Merc Treachery
- if (Item.getEquippedItemMerc(sdk.body.Armor).tier < 15000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
- }
-
- // Smoke
- if (Item.getEquippedItem(sdk.body.Armor).tier < 450) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
- }
-
- // Stealth
- if (Item.getEquippedItem(sdk.body.Armor).tier < 233) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
- }
-
- SoloWants.buildList();
-
- break;
- }
-}
+(function LoadConfig () {
+ includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
+ includeIfNotIncluded("SoloPlay/Functions/Globals.js");
+
+ const LADDER_ENABLED = (me.ladder || Developer.addLadderRW);
+
+ SetUp.include();
+ SetUp.config();
+
+ /* Necro specific Chicken configuration. */
+ Config.IronGolemChicken = 30;
+
+ /* Pickit configuration. */
+ Config.PickRange = 40;
+ // Config.PickitFiles.push("kolton.nip");
+ // Config.PickitFiles.push("LLD.nip");
+
+ /* Gambling configuration. */
+ Config.GambleItems.push("Amulet");
+ Config.GambleItems.push("Ring");
+ Config.GambleItems.push("Circlet");
+ Config.GambleItems.push("Coronet");
+
+ // AutoEquip setup
+ const levelingTiers = [
+ // Weapon
+ "([type] == wand || [type] == sword || [type] == knife) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "[type] == wand && [quality] >= normal && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 && [sockets] != 2 # [tier] == tierscore(item)",
+ // Shield
+ "([type] == shield && ([quality] >= magic || [flag] == runeword) || [type] == voodooheads) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "[type] == voodooheads && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && [sockets] == 1 # [tier] == tierscore(item)",
+ "me.classic && [type] == shield && [quality] >= normal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // non runeword white items
+ "([type] == shield) && [quality] >= normal && [flag] != ethereal && [flag] != runeword # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 2) # [tier] == tierscore(item)",
+ ];
+
+ const expansionTiers = [
+ "me.charlvl < 33 && [name] == smallcharm && [quality] == magic # [maxmana] >= 1 # [invoquantity] == 4 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # [fireresist]+[lightresist]+[coldresist]+[poisonresist] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ ];
+
+ NTIP.buildList(levelingTiers);
+ me.expansion && NTIP.buildList(expansionTiers);
+
+ /* Attack configuration. */
+ Skill.usePvpRange = true;
+ Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ Config.MaxAttackCount = 1000;
+ Config.BossPriority = me.normal;
+ Config.ClearType = 0;
+ Config.ClearPath = { Range: (Pather.canTeleport() ? 30 : 20), Spectype: 0 };
+
+ /* Class specific configuration. */
+ Config.Dodge = me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have;
+ Config.DodgeRange = me.checkItem({ name: sdk.locale.items.Enigma, itemtype: sdk.items.type.Armor }).have ? 10 : 5;
+ Config.DodgeHP = 90; // Dodge only if HP percent is less than or equal to Config.DodgeHP. 100 = always dodge.
+
+ /* Summons. */
+ Config.ReviveUnstackable = true;
+ Config.ActiveSummon = me.charlvl < 10 || SetUp.currentBuild === "Summon";
+ Config.Golem = me.checkSkill(sdk.skills.ClayGolem, sdk.skills.subindex.HardPoints) ? "Clay" : "None";
+ Config.Skeletons = (me.charlvl > 10 && SetUp.currentBuild !== "Summon") ? 0 : "max";
+ Config.SkeletonMages = (me.charlvl > 10 && SetUp.currentBuild !== "Summon") ? 0 : "max";
+ Config.Revives = (me.charlvl > 10 && SetUp.currentBuild !== "Summon") ? 0 : "max";
+
+ /* Skill Specific */
+ Config.PoisonNovaDelay = 1; // In Seconds
+ Config.ExplodeCorpses = me.checkSkill(sdk.skills.CorpseExplosion, sdk.skills.subindex.HardPoints)
+ ? sdk.skills.CorpseExplosion
+ : me.checkSkill(sdk.skills.PoisonExplosion, sdk.skills.subindex.HardPoints)
+ ? sdk.skills.PoisonExplosion
+ : 0;
+
+ Config.imbueables = (function () {
+ /**
+ * @param {number} name
+ * @param {function(): boolean} condition
+ */
+ const _imbueObj = (name, condition) => ({ name: name, condition: condition });
+
+ return [
+ _imbueObj(
+ sdk.items.DemonHead,
+ () => (me.normal && me.expansion)
+ ),
+ _imbueObj(
+ sdk.items.HierophantTrophy,
+ () => (!me.normal && (me.charlvl < 66 || me.trueStr < 106) && me.expansion)
+ ),
+ _imbueObj(
+ sdk.items.BloodlordSkull,
+ () => (me.equipped.get(sdk.body.LeftArm).tier < 1000 && me.expansion)
+ ),
+ _imbueObj(
+ sdk.items.Belt,
+ () => (me.normal && (me.equipped.get(sdk.body.LeftArm).tier > 1000 || me.classic))
+ ),
+ _imbueObj(
+ sdk.items.MeshBelt,
+ () => (!me.normal && me.charlvl < 46 && me.trueStr > 58
+ && (me.equipped.get(sdk.body.LeftArm).tier > 1000 || me.classic))
+ ),
+ _imbueObj(
+ sdk.items.SpiderwebSash,
+ () => (!me.normal && me.trueStr > 50
+ && (me.equipped.get(sdk.body.LeftArm).tier > 1000 || me.classic))
+ ),
+ ].filter((item) => item.condition());
+ })();
+
+ let imbueArr = SetUp.imbueItems();
+
+ !me.smith && NTIP.buildList(imbueArr);
+
+ switch (me.gametype) {
+ case sdk.game.gametype.Classic:
+ // Res shield
+ if (me.equipped.get(sdk.body.LeftArm).tier < 487) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PDiamondShield.js");
+ }
+
+ break;
+ case sdk.game.gametype.Expansion:
+ NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
+ const { basicSocketables, addSocketableObj } = require("../Utils/General");
+
+ Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
+ Config.socketables.push(addSocketableObj(sdk.items.Monarch, [], [],
+ !me.hell,
+ /** @param {ItemUnit} item */
+ function (item) {
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.Monarch,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+ return !me.getOwned(wanted).length && item.ilvl >= 41 && item.isBaseType && !item.ethereal;
+ }
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+
+ /* Crafting */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Config.Recipes.push([Recipe.Caster.Amulet]);
+ }
+
+ // upgrade magefist
+ if (me.equipped.get(sdk.body.Gloves).tier < 110000) {
+ Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]);
+ Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth, "magefist"]);
+ }
+
+ Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
+ Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
+
+ /* Crafting */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Config.Recipes.push([Recipe.Caster.Amulet]);
+ }
+
+ if (me.equipped.get(sdk.body.RingLeft).tier < 100000) {
+ Config.Recipes.push([Recipe.Caster.Ring]);
+ }
+
+ // Call to Arms
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ NTIP.addLine("[type] == staff && [quality] == Magic # [itemchargedskill] == 54 # [secondarytier] == 50000 + chargeditemscore(item, 54)");
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
+ }
+
+ // White
+ if (SetUp.currentBuild !== "Summon") {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/White.js");
+ }
+
+ // Rhyme
+ if (me.equipped.get(sdk.body.LeftArm).tier < 650) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Rhyme.js");
+ }
+
+ // Enigma
+ if (!me.checkItem({ name: sdk.locale.items.Enigma }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
+ }
+
+ // Spirit Sword
+ if (LADDER_ENABLED && me.equipped.get(sdk.body.RightArm).tier < 777) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
+ }
+
+ // Spirit shield
+ if (LADDER_ENABLED
+ && (me.equipped.get(sdk.body.LeftArm).tier < 1000
+ || (me.equipped.get(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)
+ && me.equipped.get(sdk.body.RightArmSecondary).prefixnum === sdk.locale.items.CalltoArms)) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
+ }
+
+ // Merc Insight
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).tier < 3600) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
+ }
+
+ if (!me.haveSome([{ name: sdk.locale.items.Enigma }, { name: sdk.locale.items.Bone }])
+ && me.equipped.get(sdk.body.Armor).tier < 650) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Bone.js");
+ }
+
+ // Lore
+ if (me.equipped.get(sdk.body.Head).tier < 315) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
+ }
+
+ // Ancients' Pledge
+ if (me.equipped.get(sdk.body.LeftArm).tier < 500) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
+ }
+
+ // Merc Fortitude
+ if (Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+
+ // Merc Treachery
+ if (Item.getMercEquipped(sdk.body.Armor).tier < 15000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
+ }
+
+ // Smoke
+ if (me.equipped.get(sdk.body.Armor).tier < 450) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
+ }
+
+ // Stealth
+ if (me.equipped.get(sdk.body.Armor).tier < 233) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
+ }
+
+ SoloWants.buildList();
+
+ break;
+ }
+
+ return true;
+})();
diff --git a/libs/SoloPlay/Config/Paladin.js b/libs/SoloPlay/Config/Paladin.js
index 0dcc86cc..31af1011 100644
--- a/libs/SoloPlay/Config/Paladin.js
+++ b/libs/SoloPlay/Config/Paladin.js
@@ -1,7 +1,6 @@
/**
* @filename Paladin.js
* @author theBGuy
-* @credit isid0re
* @desc Config Settings for SoloPlay Paladin
*
* @FinalBuild
@@ -21,513 +20,594 @@
*/
// todo: clean-up how cubing to runes is handled
-// move shared config settings into its own function call
-// make and initialize me.equippedItems object so don't have to do repeated Item.getEquippedItem(whatever) call
-
-function LoadConfig () {
- includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
- includeIfNotIncluded("SoloPlay/Functions/Globals.js");
-
- SetUp.include();
-
- /* Script */
- Scripts.SoloPlay = true;
- SetUp.config();
-
- /* Chicken configuration. */
- Config.LifeChicken = me.hardcore ? 45 : 10;
- Config.ManaChicken = 0;
- Config.MercChicken = 0;
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.TownMP = 0;
-
- /* Potions configuration. */
- Config.UseHP = me.hardcore ? 90 : 75;
- Config.UseRejuvHP = me.hardcore ? 65 : 40;
- Config.UseMP = me.hardcore ? 75 : 55;
- Config.UseMercHP = 75;
-
- /* Belt configuration. */
- Config.BeltColumn = ["hp", "mp", "mp", "rv"];
- SetUp.belt();
-
- /* Pickit configuration. */
- Config.PickRange = 40;
- // Config.PickitFiles.push("kolton.nip");
- // Config.PickitFiles.push("LLD.nip");
-
- /* Gambling configuration. */
- Config.Gamble = true;
- Config.GambleGoldStart = 2000000;
- Config.GambleGoldStop = 750000;
- Config.GambleItems.push("Amulet");
- Config.GambleItems.push("Ring");
- Config.GambleItems.push("Circlet");
- Config.GambleItems.push("Coronet");
-
- /* AutoMule configuration. */
- Config.AutoMule.Trigger = [];
- Config.AutoMule.Force = [];
- Config.AutoMule.Exclude = [
- "[name] >= Elrune && [name] <= Lemrune",
- ];
-
- /* AutoEquip configuration. */
- Config.AutoEquip = true;
-
- // AutoEquip setup
- const levelingTiers = [
- // Weapon
- "([type] == scepter || [type] == mace || [type] == sword || [type] == knife) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[type] == scepter && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && [sockets] == 1 # [tier] == tierscore(item)",
- // Helmet
- "([type] == helm || [type] == circlet) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.normal && [type] == belt && [quality] >= lowquality && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Armor
- "[type] == armor && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Shield
- "([type] == shield || [type] == auricshields) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "[type] == auricshields && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && [sockets] == 1 # [tier] == tierscore(item)",
- "me.classic && [type] == shield && [quality] >= normal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Gloves
- "[type] == gloves && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Amulet
- "[type] == amulet && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Rings
- "[type] == ring && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- ];
-
- const expansionTiers = [
- // Switch
- "[type] == wand && [quality] >= normal # [itemchargedskill] == 72 # [secondarytier] == 25000", // Weaken charged wand
- "[name] == beardedaxe && [quality] == unique # [itemchargedskill] == 87 # [secondarytier] == 50000", // Spellsteel Decrepify charged axe
- // Charms
- "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "me.charlvl < 40 && [name] == smallcharm && [quality] == magic ## [invoquantity] == 4 && [charmtier] == charmscore(item)",
- "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- ];
-
- NTIP.arrayLooping(levelingTiers);
- me.expansion && NTIP.arrayLooping(expansionTiers);
-
- /* Attack configuration. */
- Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
- Config.LowManaSkill = [0, 0];
- Config.MaxAttackCount = 1000;
- Config.BossPriority = me.normal;
- Config.ClearType = 0;
- Config.ClearPath = {Range: (Pather.canTeleport() ? 30 : 10), Spectype: 0};
-
- /* Class specific configuration. */
- Config.AvoidDolls = true;
- Config.Vigor = true;
- Config.Charge = true;
- Config.Redemption = [45, 25];
-
- /* Gear */
- let finalGear = Check.finalBuild().finalGear;
- !!finalGear && NTIP.arrayLooping(finalGear);
-
- // Maybe add auric shield?
- Config.imbueables = [
- {name: sdk.items.WarScepter, condition: () => me.normal},
- {name: sdk.items.DivineScepter, condition: () => (!me.normal && (me.trueStr < 125 || me.trueDex < 60))},
- {name: sdk.items.MightyScepter, condition: () => (Item.getEquippedItem(sdk.body.RightArm).tier < 777 && (me.trueStr >= 125 || me.trueDex >= 60))},
- {name: sdk.items.Belt, condition: () => (me.normal && (Item.getEquippedItem(sdk.body.RightArm).tier > 777 || me.classic))},
- {name: sdk.items.MeshBelt, condition: () => (!me.normal && me.charlvl < 46 && me.trueStr > 58 && (Item.getEquippedItem(sdk.body.RightArm).tier > 777 || me.classic))},
- {name: sdk.items.SpiderwebSash, condition: () => (!me.normal && me.trueStr > 50 && (Item.getEquippedItem(sdk.body.RightArm).tier > 777 || me.classic))},
- ];
-
- let imbueArr = SetUp.imbueItems();
-
- !me.smith && NTIP.arrayLooping(imbueArr);
-
- switch (me.gametype) {
- case sdk.game.gametype.Classic:
- // Res shield
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 487) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PDiamondShield.js");
- }
-
- break;
- case sdk.game.gametype.Expansion:
- NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
-
- // basicSocketables located in Globals
- Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
- Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
-
- /* Crafting */
- if (Item.getEquippedItem(sdk.body.Neck).tier < 100000) {
- Check.currentBuild().caster ? Config.Recipes.push([Recipe.Caster.Amulet]) : Config.Recipes.push([Recipe.Blood.Amulet]);
- }
-
- if (Item.getEquippedItem(sdk.body.RingLeft).tier < 100000) {
- Check.currentBuild().caster ? Config.Recipes.push([Recipe.Caster.Ring]) : Config.Recipes.push([Recipe.Blood.Ring]);
- }
-
- if (Item.getEquippedItem(sdk.body.Gloves).tier < 110000) {
- Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]);
- }
-
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- // Spirit on swap
- NTIP.addLine("[type] == auricshields && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000");
- }
-
- // FinalBuild specific setup
- let dreamerCheck;
-
- switch (SetUp.finalBuild) {
- case "Smiter":
- case "Zealer":
- // Grief
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Grief}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Grief.js");
- }
-
- if (SetUp.finalBuild === "Zealer") {
- Config.socketables.push(addSocketableObj(sdk.items.GrimHelm, [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && item.getStat(sdk.stats.DamageResist) === 20 && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.BoneVisage, [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && item.getStat(sdk.stats.DamageResist) === 20 && !item.ethereal && item.fname.toLowerCase().includes("vampire gaze")
- ));
-
- Check.itemSockables(sdk.items.GrimHelm, "unique", "Vampire Gaze");
- Check.itemSockables(sdk.items.BoneVisage, "unique", "Vampire Gaze");
-
- if (!Check.haveItem("bonevisage", "unique", "Vampire Gaze")) {
- // Upgrade Vamp Gaze to Elite
- Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Grim Helm", Roll.NonEth]);
- }
-
- // Exile
- if (!me.checkItem({name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Exile.js");
- }
-
- // Fortitude
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Fortitude, itemtype: sdk.items.type.Armor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Fortitude.js");
- }
- }
-
- break;
- case "Hammerdin":
- // Heart of the Oak
- if (!me.checkItem({name: sdk.locale.items.HeartoftheOak}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
- }
-
- // Spirit Shield
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItem(sdk.body.LeftArm).tier < 110000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
- }
-
- break;
- case "Auradin":
- dreamerCheck = me.haveAll([{name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}, {name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}]);
-
- // Dream Shield
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DreamShield.js");
- }
-
- // Dream Helm
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DreamHelm.js");
- }
-
- if ((me.ladder || Developer.addLadderRW) && !dreamerCheck) {
- // Cube to Jah rune
- if (!me.getItem(sdk.items.runes.Jah)) {
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
-
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- }
-
- }
-
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- // Cube to Mal rune
- if (!me.getItem(sdk.items.runes.Mal) && Item.getEquippedItem(sdk.body.RightArm).tier >= 110000) {
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- }
-
- // Cube to Ohm rune
- if (!me.getItem(sdk.items.runes.Ohm)) {
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- }
- }
-
- // Dragon Armor
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}).have && dreamerCheck) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DragonArmor.js");
- }
-
- if (!me.checkItem({name: sdk.locale.items.HandofJustice}).have && dreamerCheck) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HandOfJustice.js");
-
- // Azurewrath
- NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 230 && [sanctuaryaura] >= 10 # [tier] == 115000");
- }
-
- if (!me.haveAll([{name: sdk.locale.items.CrescentMoon}, {name: sdk.locale.items.HandofJustice}]) && dreamerCheck) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CrescentMoon.js");
-
- // Lightsabre
- NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 150 && [itemabsorblightpercent] == 25 # [tier] == 105000");
- }
-
- if (!me.checkItem({name: sdk.locale.items.VoiceofReason}).have && !me.haveSome([{name: sdk.locale.items.CrescentMoon}, {name: sdk.locale.items.HandofJustice}]) && dreamerCheck) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/VoiceOfReason.js");
- }
-
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude
- && me.haveAll([{name: sdk.locale.items.HandofJustice}, {name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor},
- {name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}, {name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}])) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
-
- break;
- case "Sancdreamer":
- dreamerCheck = me.haveAll([{name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}, {name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}]);
-
- // Dream Shield
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DreamShield.js");
- }
-
- // Dream Helm
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DreamHelm.js");
- }
-
- if ((me.ladder || Developer.addLadderRW) && !dreamerCheck) {
- // Cube to Jah rune
- if (!me.getItem(sdk.items.runes.Jah)) {
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- }
-
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- }
- }
-
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- // Cube to Mal rune
- if (!me.getItem(sdk.items.runes.Mal) && Item.getEquippedItem(sdk.body.RightArm).tier >= 110000) {
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- }
-
- // Cube to Ohm rune
- if (!me.getItem(sdk.items.runes.Ohm)) {
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- }
- }
-
- // Chains of Honor
- if (!me.checkItem({name: sdk.locale.items.ChainsofHonor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
- }
-
- if (!me.checkItem({name: sdk.locale.items.LastWish}).have && dreamerCheck) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/LastWish.js");
- }
-
- if (!me.haveAll([{name: sdk.locale.items.CrescentMoon}, {name: sdk.locale.items.LastWish}]) && dreamerCheck) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CrescentMoon.js");
-
- // Lightsabre
- NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 150 && [itemabsorblightpercent] == 25 # [tier] == 105000");
- }
-
- if (!me.checkItem({name: sdk.locale.items.VoiceofReason}).have && !me.haveSome([{name: sdk.locale.items.CrescentMoon}, {name: sdk.locale.items.LastWish}]) && dreamerCheck) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/VoiceOfReason.js");
- }
-
- if ((me.ladder || Developer.addLadderRW) && me.haveAll([{name: sdk.locale.items.LastWish}, {name: sdk.locale.items.ChainsofHonor},
- {name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields}, {name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm}])) {
- // Infinity
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
- if (!isIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js")) {
- include("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
- }
- }
- // Merc Fortitude
- if (Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
- if (!isIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js")) {
- include("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
- }
- }
-
- break;
- case "Torchadin":
- // Dragon Armor
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DragonArmor.js");
- }
-
- if (!me.checkItem({name: sdk.locale.items.HandofJustice}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HandOfJustice.js");
-
- // Azurewrath
- NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 230 && [sanctuaryaura] >= 10 # [tier] == 115000");
- }
-
- // Exile
- if ((me.ladder || Developer.addLadderRW) && !me.checkItem({name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Exile.js");
- }
-
- if ((me.ladder || Developer.addLadderRW) && !me.haveAll([{name: sdk.locale.items.HandofJustice},
- {name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields}, {name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}])) {
- // Cube to Cham rune
- if (!me.getItem(sdk.items.runes.Cham) || !me.getItem(sdk.items.runes.Sur) || !me.getItem(sdk.items.runes.Lo)) {
- if (me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
-
- if (me.checkItem({name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields}).have) {
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
- } else if (!me.checkItem({name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields}).have && !me.getItem(sdk.items.runes.Ohm)) {
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- }
- }
-
- if (me.checkItem({name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}).have) {
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
- } else if ((!me.haveAll([{name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}, {name: sdk.locale.items.HandofJustice}]) && !me.getItem(sdk.items.runes.Sur))) {
- Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
- }
-
- Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
- Config.Recipes.push([Recipe.Rune, "Jah Rune"]);
- }
- }
-
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- // Cube to Mal rune
- if (!me.getItem(sdk.items.runes.Mal) && Item.getEquippedItem(sdk.body.RightArm).tier >= 110000) {
- Config.Recipes.push([Recipe.Rune, "Um Rune"]);
- }
-
- // Cube to Ohm rune
- if (!me.getItem(sdk.items.runes.Ohm)) {
- Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
- Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
- }
- }
-
- if (!me.haveAll([{name: sdk.locale.items.CrescentMoon}, {name: sdk.locale.items.HandofJustice}])) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CrescentMoon.js");
-
- // Lightsabre
- NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 150 && [itemabsorblightpercent] == 25 # [tier] == 105000");
- }
-
- if (!me.checkItem({name: sdk.locale.items.VoiceofReason}).have && !me.haveSome([{name: sdk.locale.items.CrescentMoon}, {name: sdk.locale.items.HandofJustice}])) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/VoiceOfReason.js");
- }
-
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude
- && me.haveAll([{name: sdk.locale.items.HandofJustice}, {name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor}, {name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields}])) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
- break;
- default:
- break;
- }
-
- Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
- Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
-
- // Call to Arms
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
- }
-
- // Enigma - Don't make if not Smiter or Hammerdin
- if (!me.checkItem({name: sdk.locale.items.Enigma}).have && ["Hammerdin", "Smiter"].indexOf(SetUp.finalBuild) > -1) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
- }
-
- // Spirit Sword
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItem(sdk.body.RightArm).tier < 777) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
- }
-
- // Spirit Shield
- if ((me.ladder || Developer.addLadderRW) && (Item.getEquippedItem(sdk.body.LeftArm).tier < 1000 || Item.getEquippedItem(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
- }
-
- // Merc Insight
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).tier < 3600) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
- }
-
- // Lore
- if (Item.getEquippedItem(sdk.body.Head).tier < 315) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
- }
-
- // Ancients' Pledge
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
- }
-
- // Merc Fortitude
- if (Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude && ["Hammerdin", "Smiter"].indexOf(SetUp.finalBuild) > -1) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
-
- // Merc Treachery
- if (Item.getEquippedItemMerc(sdk.body.Armor).tier < 15000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
- }
-
- // Smoke
- if (Item.getEquippedItem(sdk.body.Armor).tier < 450) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
- }
-
- // Stealth
- if (Item.getEquippedItem(sdk.body.Armor).tier < 233) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
- }
-
- SoloWants.buildList();
-
- break;
- }
-}
+
+(function LoadConfig () {
+ includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
+ includeIfNotIncluded("SoloPlay/Functions/Globals.js");
+
+ const LADDER_ENABLED = (me.ladder || Developer.addLadderRW);
+
+ SetUp.include();
+ SetUp.config();
+
+ /* Pickit configuration. */
+ Config.PickRange = 40;
+ // Config.PickitFiles.push("kolton.nip");
+ // Config.PickitFiles.push("LLD.nip");
+
+ /* Gambling configuration. */
+ Config.GambleItems.push("Amulet");
+ Config.GambleItems.push("Ring");
+ Config.GambleItems.push("Circlet");
+ Config.GambleItems.push("Coronet");
+
+ let weapons = [
+ sdk.items.type.Scepter,
+ sdk.items.type.Mace,
+ sdk.items.type.Sword,
+ sdk.items.type.Knife,
+ sdk.items.type.Axe,
+ sdk.items.type.Wand,
+ sdk.items.type.Hammer,
+ sdk.items.type.Club
+ ].map(el => "[type] == " + el).join(" || ");
+
+ // AutoEquip setup
+ const levelingTiers = [
+ // Weapon
+ "(" + weapons + ") && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Shield
+ "([type] == shield || [type] == auricshields) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "me.classic && [type] == shield && [quality] >= normal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // non runeword white items
+ "(" + weapons + ") && [quality] >= normal && [flag] != ethereal && [flag] != runeword && [2handed] == 0 # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 3) # [tier] == tierscore(item)",
+ "([type] == shield || [type] == auricshields) && [quality] >= normal && [flag] != ethereal && [flag] != runeword # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 2) # [tier] == tierscore(item)",
+ ];
+
+ let miscCharmQuantity = me.charlvl < 40 ? 6 : 3;
+ const expansionTiers = [
+ // Switch
+ "[type] == wand && [quality] >= normal # [itemchargedskill] == 72 # [secondarytier] == 25000", // Weaken charged wand
+ "[name] == beardedaxe && [quality] == unique # [itemchargedskill] == 87 # [secondarytier] == 50000", // Spellsteel Decrepify charged axe
+ // Charms
+ "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # # [invoquantity] == " + miscCharmQuantity + " && [charmtier] == charmscore(item)",
+ "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ ];
+
+ /* Gear */
+ NTIP.buildList(levelingTiers);
+ me.expansion && NTIP.buildList(expansionTiers);
+
+ /* Attack configuration. */
+ Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
+ Config.LowManaSkill = [0, 0];
+ Config.MaxAttackCount = 1000;
+ Config.BossPriority = me.normal;
+ Config.ClearType = 0;
+ Config.ClearPath = { Range: (Pather.canTeleport() ? 30 : 10), Spectype: 0 };
+
+ /* Class specific configuration. */
+ Config.AvoidDolls = true;
+ Config.Vigor = true;
+ Config.Charge = true;
+ Config.Redemption = [45, 25];
+
+ // Maybe add auric shield?
+ Config.imbueables = (function () {
+ /**
+ * @param {number} name
+ * @param {function(): boolean} condition
+ */
+ const _imbueObj = (name, condition) => ({ name: name, condition: condition });
+
+ return [
+ _imbueObj(
+ sdk.items.WarScepter,
+ function () {
+ return me.normal;
+ }
+ ),
+ _imbueObj(
+ sdk.items.DivineScepter,
+ function () {
+ return !me.normal && (me.trueStr < 125 || me.trueDex < 60);
+ }
+ ),
+ _imbueObj(
+ sdk.items.MightyScepter,
+ function () {
+ return me.equipped.get(sdk.body.RightArm).tier < 777 && (me.trueStr >= 125 || me.trueDex >= 60);
+ }
+ ),
+ _imbueObj(
+ sdk.items.Belt,
+ function () {
+ return me.normal && (me.equipped.get(sdk.body.RightArm).tier > 777 || me.classic);
+ }
+ ),
+ _imbueObj(
+ sdk.items.MeshBelt,
+ function () {
+ return !me.normal && me.charlvl < 46 && me.trueStr > 58
+ && (me.equipped.get(sdk.body.RightArm).tier > 777 || me.classic);
+ }
+ ),
+ _imbueObj(
+ sdk.items.SpiderwebSash,
+ function () {
+ return !me.normal && me.trueStr > 50 && (me.equipped.get(sdk.body.RightArm).tier > 777 || me.classic);
+ }
+ ),
+ ].filter((item) => item.condition());
+ })();
+
+ let imbueArr = SetUp.imbueItems();
+
+ !me.smith && NTIP.buildList(imbueArr);
+
+ switch (me.gametype) {
+ case sdk.game.gametype.Classic:
+ // Res shield
+ if (me.equipped.get(sdk.body.LeftArm).tier < 487) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PDiamondShield.js");
+ }
+
+ break;
+ case sdk.game.gametype.Expansion:
+ NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
+ const { basicSocketables, addSocketableObj } = require("../Utils/General");
+
+ Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
+ Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+
+ /* Crafting */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Check.currentBuild().caster
+ ? Config.Recipes.push([Recipe.Caster.Amulet])
+ : Config.Recipes.push([Recipe.Blood.Amulet]);
+ }
+
+ if (me.equipped.get(sdk.body.RingLeft).tier < 100000) {
+ Check.currentBuild().caster
+ ? Config.Recipes.push([Recipe.Caster.Ring])
+ : Config.Recipes.push([Recipe.Blood.Ring]);
+ }
+
+ if (me.equipped.get(sdk.body.Gloves).tier < 110000) {
+ Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]);
+ }
+
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ // Spirit on swap
+ NTIP.addLine("[type] == auricshields && [flag] == runeword # [fcr] >= 25 && [maxmana] >= 89 # [secondarytier] == 110000");
+ }
+
+ // FinalBuild specific setup
+ let dreamerCheck;
+
+ switch (SetUp.finalBuild) {
+ case "Smiter":
+ case "Zealer":
+ // Grief
+ if (LADDER_ENABLED && !me.checkItem({ name: sdk.locale.items.Grief }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Grief.js");
+ }
+
+ if (SetUp.finalBuild === "Zealer") {
+ Config.socketables.push(addSocketableObj(sdk.items.GrimHelm,
+ [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && item.getStat(sdk.stats.DamageResist) === 20 && !item.ethereal
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.BoneVisage,
+ [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && item.getStat(sdk.stats.DamageResist) === 20 && !item.ethereal && item.fname.toLowerCase().includes("vampire gaze")
+ ));
+
+ Check.itemSockables(sdk.items.GrimHelm, "unique", "Vampire Gaze");
+ Check.itemSockables(sdk.items.BoneVisage, "unique", "Vampire Gaze");
+
+ if (!me.checkItem({ name: sdk.locale.items.VampireGaze, classid: sdk.items.BoneVisage }).have) {
+ // Upgrade Vamp Gaze to Elite
+ Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Grim Helm", Roll.NonEth]);
+ }
+
+ // Exile
+ if (!me.checkItem({ name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Exile.js");
+ }
+
+ // Fortitude
+ if (LADDER_ENABLED
+ && !me.checkItem({ name: sdk.locale.items.Fortitude, itemtype: sdk.items.type.Armor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Fortitude.js");
+ }
+ }
+
+ break;
+ case "Hammerdin":
+ // Heart of the Oak
+ if (!me.checkItem({ name: sdk.locale.items.HeartoftheOak }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
+ }
+
+ // Spirit Shield
+ if (LADDER_ENABLED && me.equipped.get(sdk.body.LeftArm).tier < 110000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
+ }
+
+ break;
+ case "Auradin":
+ dreamerCheck = me.haveAll([
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm }
+ ]);
+
+ // Dream Shield
+ if (LADDER_ENABLED
+ && !me.checkItem({ name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DreamShield.js");
+ }
+
+ // Dream Helm
+ if (LADDER_ENABLED
+ && !me.checkItem({ name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DreamHelm.js");
+ }
+
+ if (LADDER_ENABLED && !dreamerCheck) {
+ // Cube to Jah rune
+ if (!me.getItem(sdk.items.runes.Jah)) {
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ }
+
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ // Cube to Mal rune
+ if (!me.getItem(sdk.items.runes.Mal) && me.equipped.get(sdk.body.RightArm).tier >= 110000) {
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ }
+
+ // Cube to Ohm rune
+ if (!me.getItem(sdk.items.runes.Ohm)) {
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ }
+ }
+
+ // Dragon Armor
+ if (LADDER_ENABLED
+ && !me.checkItem({ name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor }).have
+ && dreamerCheck) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DragonArmor.js");
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.HandofJustice }).have && dreamerCheck) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HandOfJustice.js");
+
+ // Azurewrath
+ NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 230 && [sanctuaryaura] >= 10 # [tier] == 115000");
+ }
+
+ if (!me.haveAll([
+ { name: sdk.locale.items.CrescentMoon },
+ { name: sdk.locale.items.HandofJustice }
+ ]) && dreamerCheck) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CrescentMoon.js");
+
+ // Lightsabre
+ NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 150 && [itemabsorblightpercent] == 25 # [tier] == 105000");
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.VoiceofReason }).have
+ && !me.haveSome([
+ { name: sdk.locale.items.CrescentMoon },
+ { name: sdk.locale.items.HandofJustice }
+ ]) && dreamerCheck) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/VoiceOfReason.js");
+ }
+
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude
+ && me.haveAll([
+ { name: sdk.locale.items.HandofJustice },
+ { name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm }
+ ])) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+
+ break;
+ case "Sancdreamer":
+ dreamerCheck = me.haveAll([
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm }
+ ]);
+
+ // Dream Shield
+ if (LADDER_ENABLED
+ && !me.checkItem({ name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DreamShield.js");
+ }
+
+ // Dream Helm
+ if (LADDER_ENABLED && !me.checkItem({ name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DreamHelm.js");
+ }
+
+ if (LADDER_ENABLED && !dreamerCheck) {
+ // Cube to Jah rune
+ if (!me.getItem(sdk.items.runes.Jah)) {
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ }
+
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ }
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ // Cube to Mal rune
+ if (!me.getItem(sdk.items.runes.Mal) && me.equipped.get(sdk.body.RightArm).tier >= 110000) {
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ }
+
+ // Cube to Ohm rune
+ if (!me.getItem(sdk.items.runes.Ohm)) {
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ }
+ }
+
+ // Chains of Honor
+ if (!me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.LastWish }).have && dreamerCheck) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/LastWish.js");
+ }
+
+ if (!me.haveAll([{ name: sdk.locale.items.CrescentMoon }, { name: sdk.locale.items.LastWish }]) && dreamerCheck) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CrescentMoon.js");
+
+ // Lightsabre
+ NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 150 && [itemabsorblightpercent] == 25 # [tier] == 105000");
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.VoiceofReason }).have
+ && !me.haveSome([
+ { name: sdk.locale.items.CrescentMoon },
+ { name: sdk.locale.items.LastWish }
+ ]) && dreamerCheck) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/VoiceOfReason.js");
+ }
+
+ if (LADDER_ENABLED && me.haveAll([
+ { name: sdk.locale.items.LastWish },
+ { name: sdk.locale.items.ChainsofHonor },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.AuricShields },
+ { name: sdk.locale.items.Dream, itemtype: sdk.items.type.Helm }
+ ])) {
+ // Infinity
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
+ if (!isIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js")) {
+ include("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
+ }
+ }
+ // Merc Fortitude
+ if (Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
+ if (!isIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js")) {
+ include("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+ }
+ }
+
+ break;
+ case "Torchadin":
+ // Dragon Armor
+ if (LADDER_ENABLED && !me.checkItem({ name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/DragonArmor.js");
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.HandofJustice }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HandOfJustice.js");
+
+ // Azurewrath
+ NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 230 && [sanctuaryaura] >= 10 # [tier] == 115000");
+ }
+
+ // Exile
+ if (LADDER_ENABLED
+ && !me.checkItem({ name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Exile.js");
+ }
+
+ if (LADDER_ENABLED && !me.haveAll([
+ { name: sdk.locale.items.HandofJustice },
+ { name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields },
+ { name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor }
+ ])) {
+ // Cube to Cham rune
+ if (!me.getItem(sdk.items.runes.Cham) || !me.getItem(sdk.items.runes.Sur) || !me.getItem(sdk.items.runes.Lo)) {
+ if (me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ Config.Recipes.push([Recipe.Rune, "Mal Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ist Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+
+ if (me.checkItem({ name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields }).have) {
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Ohm Rune"]);
+ } else if (!me.checkItem({ name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields }).have
+ && !me.getItem(sdk.items.runes.Ohm)) {
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ }
+ }
+
+ if (me.checkItem({ name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor }).have) {
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Sur Rune"]);
+ } else if ((!me.haveAll([
+ { name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor },
+ { name: sdk.locale.items.HandofJustice }
+ ]) && !me.getItem(sdk.items.runes.Sur))) {
+ Config.Recipes.push([Recipe.Rune, "Lo Rune"]);
+ }
+
+ Config.Recipes.push([Recipe.Rune, "Ber Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Jah Rune"]);
+ }
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ // Cube to Mal rune
+ if (!me.getItem(sdk.items.runes.Mal) && me.equipped.get(sdk.body.RightArm).tier >= 110000) {
+ Config.Recipes.push([Recipe.Rune, "Um Rune"]);
+ }
+
+ // Cube to Ohm rune
+ if (!me.getItem(sdk.items.runes.Ohm)) {
+ Config.Recipes.push([Recipe.Rune, "Gul Rune"]);
+ Config.Recipes.push([Recipe.Rune, "Vex Rune"]);
+ }
+ }
+
+ if (!me.haveAll([{ name: sdk.locale.items.CrescentMoon }, { name: sdk.locale.items.HandofJustice }])) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CrescentMoon.js");
+
+ // Lightsabre
+ NTIP.addLine("[name] == phaseblade && [flag] != ethereal && [quality] == unique # [enhanceddamage] >= 150 && [itemabsorblightpercent] == 25 # [tier] == 105000");
+ }
+
+ if (!me.checkItem({ name: sdk.locale.items.VoiceofReason }).have
+ && !me.haveSome([
+ { name: sdk.locale.items.CrescentMoon },
+ { name: sdk.locale.items.HandofJustice }
+ ])) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/VoiceOfReason.js");
+ }
+
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude
+ && me.haveAll([
+ { name: sdk.locale.items.HandofJustice },
+ { name: sdk.locale.items.Dragon, itemtype: sdk.items.type.Armor },
+ { name: sdk.locale.items.Exile, itemtype: sdk.items.type.AuricShields }
+ ])) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+ break;
+ default:
+ break;
+ }
+
+ Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
+ Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
+
+ // Call to Arms
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
+ }
+
+ // Enigma - Don't make if not Smiter or Hammerdin
+ if (!me.checkItem({ name: sdk.locale.items.Enigma }).have && ["Hammerdin", "Smiter"].includes(SetUp.finalBuild)) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Enigma.js");
+ }
+
+ // Spirit Sword
+ if (LADDER_ENABLED && me.equipped.get(sdk.body.RightArm).tier < 777) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
+ }
+
+ // Spirit Shield
+ if (LADDER_ENABLED
+ && (
+ me.equipped.get(sdk.body.LeftArm).tier < 1000
+ || me.equipped.get(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit
+ )) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
+ }
+
+ // Merc Insight
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).tier < 3600) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
+ }
+
+ // Lore
+ if (me.equipped.get(sdk.body.Head).tier < 315) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
+ }
+
+ /**
+ * @todo Rhyme and Splendor
+ */
+
+ // Ancients' Pledge
+ if (me.equipped.get(sdk.body.LeftArm).tier < 500) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
+ }
+
+ // Merc Fortitude
+ if (Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude && ["Hammerdin", "Smiter"].includes(SetUp.finalBuild)) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+
+ // Merc Treachery
+ if (Item.getMercEquipped(sdk.body.Armor).tier < 15000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
+ }
+
+ // Smoke
+ if (me.equipped.get(sdk.body.Armor).tier < 450) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
+ }
+
+ // Stealth
+ if (me.equipped.get(sdk.body.Armor).tier < 233) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
+ }
+
+ /* if (SetUp.currentBuild === "Start") {
+ let { maxStr, maxDex } = Check.currentBuild();
+ let basic = "[flag] != ethereal && [quality] >= normal && [quality] <= superior";
+ let myStatsReq = "[strreq] <= " + maxStr + " && [dexreq] <= " + maxDex;
+ let statsReq = "[sockets] == 2";
+ let quantityReq = "[maxquantity] == 1";
+ // add Steel RW
+ NTIP.buildList([
+ "[name] == TirRune # # [maxquantity] == 2",
+ "[name] == ElRune # # [maxquantity] == 2",
+ "([type] == sword || [type] == mace || [type] == axe) && " + basic + " && " + myStatsReq + " # " + statsReq + " # " + quantityReq,
+ ]);
+
+ // need to make runewords able to process types, maybe a hacky method of just taking a nip line?
+ // or could create a base item data module and loop through whatever meets the reqs but that feels ugly too
+ Config.KeepRunewords.push("([type] == sword || [type] == mace || [type] == axe) # [enhanceddamage] >= 20 && [ias] >= 25");
+ } */
+
+ SoloWants.buildList();
+
+ break;
+ }
+
+ return true;
+})();
diff --git a/libs/SoloPlay/Config/Sorceress.js b/libs/SoloPlay/Config/Sorceress.js
index 4096f55b..b3b652fe 100644
--- a/libs/SoloPlay/Config/Sorceress.js
+++ b/libs/SoloPlay/Config/Sorceress.js
@@ -17,289 +17,332 @@
* 4. Save the profile and start
*/
-function LoadConfig () {
- includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
- includeIfNotIncluded("SoloPlay/Functions/Globals.js");
-
- SetUp.include();
-
- /* Script */
- Scripts.SoloPlay = true;
- SetUp.config();
-
- /* Chicken configuration. */
- Config.LifeChicken = me.hardcore ? 45 : 10;
- Config.ManaChicken = 0;
- Config.MercChicken = 0;
- Config.TownHP = me.hardcore ? 0 : 35;
- Config.TownMP = 0;
-
- /* Potions configuration. */
- Config.UseHP = me.hardcore ? 90 : 80;
- Config.UseRejuvHP = me.hardcore ? 65 : 50;
- Config.UseMP = me.hardcore ? 75 : 65;
- Config.UseMercHP = 75;
-
- /* Belt configuration. */
- Config.BeltColumn = ["hp", "mp", "mp", "rv"];
- SetUp.belt();
-
- /* Pickit configuration. */
- Config.PickRange = 40;
- // Config.PickitFiles.push("kolton.nip");
- // Config.PickitFiles.push("LLD.nip");
-
- /* Gambling configuration. */
- Config.Gamble = true;
- Config.GambleGoldStart = 1250000;
- Config.GambleGoldStop = 750000;
- // TODO: should gambling be re-written to try and gamble for our current lowest tier'd item
- // for example if our gloves are the lowest tier then only gamble gloves or maybe just make the others conditional like why include
- // gambling for rings/ammys if we have our end game one
- Config.GambleItems.push("Amulet");
- Config.GambleItems.push("Ring");
- Config.GambleItems.push("Circlet") && Config.GambleItems.push("Coronet");
-
- /* AutoMule configuration. */
- Config.AutoMule.Trigger = [];
- Config.AutoMule.Force = [];
- Config.AutoMule.Exclude = [
- "[name] >= Elrune && [name] <= Lemrune",
- ];
-
- /* AutoEquip configuration. */
- Config.AutoEquip = true;
-
- // AutoEquip setup
- const levelingTiers = [
- // Weapon
- "me.normal && [type] == orb && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.charlvl > 1 && ([type] == orb || [type] == wand || [type] == sword || [type] == knife) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.classic && [type] == staff && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Helmet
- "([type] == helm || [type] == circlet) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Belt
- "[type] == belt && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.normal && [type] == belt && [quality] >= lowquality && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Boots
- "[type] == boots && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Armor
- "[type] == armor && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Shield
- "[type] == shield && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- "me.classic && [type] == shield && [quality] >= normal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Gloves
- "[type] == gloves && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Amulet
- "[type] == amulet && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- // Rings
- "[type] == ring && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
- ];
-
- const expansionTiers = [
- // Switch
- "[type] == wand && [quality] >= Normal # [itemchargedskill] == 72 # [secondarytier] == 25000", // Weaken charged wand
- "[type] == wand && [quality] >= Normal # [itemchargedskill] == 91 # [secondarytier] == 50000 + chargeditemscore(item, 91)", // Lower Resist charged wand
- // Charms
- "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "[name] == smallcharm && [quality] == magic # [fireresist]+[lightresist]+[coldresist]+[poisonresist] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- "me.charlvl < 40 && [name] == smallcharm && [quality] == magic ## [invoquantity] == 4 && [charmtier] == charmscore(item)",
- "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
- ];
-
- if (SetUp.currentBuild !== "Start") {
- NTIP.addLine("([type] == orb || [type] == wand || [type] == sword || [type] == knife) && ([quality] >= magic || [flag] == runeword) && [flag] == ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)");
- }
-
- NTIP.arrayLooping(levelingTiers);
- me.expansion && NTIP.arrayLooping(expansionTiers);
-
- /* Attack configuration. */
- Skill.usePvpRange = true;
- Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
- Config.LowManaSkill = [-1, -1];
- Config.MaxAttackCount = 1000;
- Config.BossPriority = false;
- Config.ClearType = 0;
- Config.ClearPath = {Range: 30, Spectype: (me.hell && Pather.canTeleport() ? 0xF : 0)};
-
- /* Monster skip configuration. */
- Pather.canTeleport() && me.lightRes < 75 && Config.SkipEnchant.push("lightning enchanted");
-
- /* Class specific configuration. */
- Config.UseTelekinesis = true; // use telekinesis if have skill
- Config.UseColdArmor = true;
- Config.Dodge = !!(me.charlvl >= CharInfo.respecOne); // 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 = me.hardcore ? 90 : 75; // 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
- Config.CastStatic = me.classic ? 15 : [25, 33, 50][me.diff];
-
- /* Gear */
- let finalGear = Check.finalBuild().finalGear;
- !!finalGear && NTIP.arrayLooping(finalGear);
-
- Config.imbueables = [
- {name: sdk.items.JaredsStone, condition: () => (me.normal && me.expansion)},
- {name: sdk.items.SwirlingCrystal, condition: () => (!me.normal && me.charlvl < 66 && me.expansion)},
- {name: sdk.items.DimensionalShard, condition: () => (Item.getEquippedItem(sdk.body.RightArm).tier < 777 && me.expansion)},
- {name: sdk.items.Belt, condition: () => (me.normal && (Item.getEquippedItem(sdk.body.RightArm).tier > 777 || me.classic))},
- {name: sdk.items.MeshBelt, condition: () => (!me.normal && me.charlvl < 46 && me.trueStr > 58 && (Item.getEquippedItem(sdk.body.RightArm).tier > 777 || me.classic))},
- {name: sdk.items.SpiderwebSash, condition: () => (!me.normal && me.trueStr > 50 && (Item.getEquippedItem(sdk.body.RightArm).tier > 777 || me.classic))},
- ].filter((item) => item.condition());
-
- let imbueArr = SetUp.imbueItems();
-
- !me.smith && NTIP.arrayLooping(imbueArr);
-
- switch (me.gametype) {
- case sdk.game.gametype.Classic:
- // Res shield
- if ((Item.getEquippedItem(sdk.body.LeftArm).tier < 487 && !Item.getEquippedItem(sdk.body.RightArm).twoHanded) || (Item.getEquippedItem(sdk.body.RightArm).tier < 487 && Item.getEquippedItem(sdk.body.RightArm).twoHanded)) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PDiamondShield.js");
- }
-
- break;
- case sdk.game.gametype.Expansion:
- NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
-
- /* Crafting */
- if (Item.getEquippedItem(sdk.body.Neck).tier < 100000) {
- Config.Recipes.push([Recipe.Caster.Amulet]);
- }
-
- if (Item.getEquippedItem(sdk.body.Gloves).tier < 110000) {
- Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]);
- ["Blova", "Lightning"].includes(SetUp.finalBuild) && Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth, "magefist"]);
- }
-
- // basicSocketables located in Globals
- Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
-
- // FinalBuild specific setup
- switch (SetUp.finalBuild) {
- case "Blova":
- case "Lightning":
- // Infinity
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
- }
-
- // Spirit Shield
- if ((me.ladder || Developer.addLadderRW) && SetUp.currentBuild === SetUp.finalBuild && (Item.getEquippedItem(sdk.body.LeftArm).tier < 1000 || Item.getEquippedItem(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
- }
-
- // Chains of Honor
- if (!me.checkItem({name: sdk.locale.items.ChainsofHonor}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
- }
-
- // Heart of the Oak
- if (!me.checkItem({name: sdk.locale.items.HeartoftheOak}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
- }
-
- Config.socketables.push(addSocketableObj(sdk.items.Monarch, [], [],
- !me.hell, (item) => !Check.haveBase("monarch", 4) && item.ilvl >= 41 && item.isBaseType && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.Shako, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.unique && !item.ethereal
- ));
-
- Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
-
- break;
- case "Meteorb":
- case "Cold":
- case "Blizzballer":
- Config.socketables.push(addSocketableObj(sdk.items.DeathMask, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.set && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.LacqueredPlate, [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
- true, (item) => item.set && !item.ethereal
- ));
- Config.socketables.push(addSocketableObj(sdk.items.SwirlingCrystal, [sdk.items.runes.Ist], [],
- false, (item) => item.set && !item.ethereal
- ));
-
- Check.itemSockables(sdk.items.LacqueredPlate, "set", "Tal Rasha's Guardianship");
- Check.itemSockables(sdk.items.DeathMask, "set", "Tal Rasha's Horadric Crest");
-
- break;
- default:
- break;
- }
-
- // Go ahead and keep two P-diamonds prior to finding a moser's unless already using a better shield
- if (!Check.haveItem("shield", "unique", "Moser's Blessed Circle") && !me.haveSome([{name: sdk.locale.items.Sanctuary}, {name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Shield}])) {
- NTIP.addLine("[name] == perfectdiamond # # [maxquantity] == 2");
-
- if (Item.getQuantityOwned(me.getItem(sdk.items.gems.Perfect.Diamond) < 2)) {
- Config.Recipes.push([Recipe.Gem, "flawlessdiamond"]);
- }
- }
-
- Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
-
- // Sanctuary
- if (!me.haveSome([{name: sdk.locale.items.Sanctuary}, {name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Shield}]) && ["Blova", "Lightning"].indexOf(SetUp.currentBuild) === -1) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Sanctuary.js");
- }
-
- // Call to Arms
- if (!me.checkItem({name: sdk.locale.items.CalltoArms}).have) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
- }
-
- // Spirit Sword
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItem(sdk.body.RightArm).tier < 777) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
- }
-
- // Merc Insight
- if ((me.ladder || Developer.addLadderRW) && Item.getEquippedItemMerc(sdk.body.RightArm).tier < 3600) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
- }
-
- // Lore
- if (Item.getEquippedItem(sdk.body.Head).tier < 315) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
- }
-
- // Ancients' Pledge
- if (Item.getEquippedItem(sdk.body.LeftArm).tier < 500) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
- }
-
- // Merc Fortitude
- if (Item.getEquippedItemMerc(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
- }
-
- // Bone
- if (Item.getEquippedItem(sdk.body.Armor).tier < 450) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Bone.js");
- }
-
- // Merc Treachery
- if (Item.getEquippedItemMerc(sdk.body.Armor).tier < 15000) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
- }
-
- // Smoke
- if (Item.getEquippedItem(sdk.body.Armor).tier < 300) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
- }
-
- // Stealth
- if (Item.getEquippedItem(sdk.body.Armor).tier < 233) {
- includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
- }
-
- SoloWants.buildList();
-
- break;
- }
-}
+(function LoadConfig () {
+ includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
+ includeIfNotIncluded("SoloPlay/Functions/Globals.js");
+
+ const LADDER_ENABLED = (me.ladder || Developer.addLadderRW);
+
+ SetUp.include();
+ SetUp.config();
+
+ /* Pickit configuration. */
+ Config.PickRange = 40;
+ // Config.PickitFiles.push("kolton.nip");
+ // Config.PickitFiles.push("test.nip");
+
+ /* Gambling configuration. */
+ // TODO: should gambling be re-written to try and gamble for our current lowest tier'd item
+ // for example if our gloves are the lowest tier then only gamble gloves or maybe just make the others conditional like why include
+ // gambling for rings/ammys if we have our end game one
+ Config.GambleItems.push("Amulet");
+ Config.GambleItems.push("Ring");
+ Config.GambleItems.push("Circlet");
+ Config.GambleItems.push("Coronet");
+
+ // AutoEquip setup
+ const _wepTypes = [
+ "[type] == axe",
+ "[type] == club",
+ "[type] == knife",
+ "[type] == hammer",
+ "[type] == javelin",
+ "[type] == mace",
+ "[type] == orb",
+ "[type] == scepter",
+ "[type] == sword",
+ "[type] == wand",
+ ].join(" || ");
+ const levelingTiers = [
+ // Weapon
+ "me.normal && [type] == orb && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "me.charlvl > 1 && (" + _wepTypes + ") && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "me.classic && [type] == staff && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Shield
+ "[type] == shield && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "me.classic && [type] == shield && [quality] >= normal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // non runeword white items
+ "([type] == shield) && [quality] >= normal && [flag] != ethereal && [flag] != runeword # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 2) # [tier] == tierscore(item)",
+ ];
+
+ const expansionTiers = [
+ // Switch
+ "[type] == wand && [quality] >= Normal # [itemchargedskill] == 72 # [secondarytier] == 25000", // Weaken charged wand
+ "[type] == wand && [quality] >= Normal # [itemchargedskill] == 91 # [secondarytier] == 50000 + chargeditemscore(item, 91)", // Lower Resist charged wand
+ // Charms
+ "[name] == smallcharm && [quality] == magic # [maxhp] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # [itemmagicbonus] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "[name] == smallcharm && [quality] == magic # [fireresist]+[lightresist]+[coldresist]+[poisonresist] >= 1 # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ "me.charlvl < 40 && [name] == smallcharm && [quality] == magic ## [invoquantity] == 4 && [charmtier] == charmscore(item)",
+ "[name] == grandcharm && [quality] == magic # # [invoquantity] == 2 && [charmtier] == charmscore(item)",
+ ];
+
+ if (SetUp.currentBuild !== "Start") {
+ NTIP.addLine("([type] == orb || [type] == wand || [type] == sword || [type] == knife) && ([quality] >= magic || [flag] == runeword) && [flag] == ethereal && [2handed] == 0 # [itemchargedskill] >= 0 # [tier] == tierscore(item)");
+ }
+
+ /* Attack configuration. */
+ Skill.usePvpRange = true;
+ Config.AttackSkill = [0, 0, 0, 0, 0, 0, 0];
+ Config.LowManaSkill = [-1, -1];
+ Config.MaxAttackCount = 1000;
+ Config.BossPriority = false;
+ Config.ClearType = 0;
+ Config.ClearPath = { Range: 30, Spectype: (me.hell && Pather.canTeleport() ? 0xF : 0) };
+
+ /* Monster skip configuration. */
+ Pather.canTeleport() && me.lightRes < 75 && Config.SkipEnchant.push("lightning enchanted");
+
+ /* Class specific configuration. */
+ Config.UseTelekinesis = true; // use telekinesis if have skill
+ Config.UseColdArmor = true;
+ Config.Dodge = !!(me.charlvl >= CharInfo.respecOne); // 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 = me.hardcore ? 90 : 75; // 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
+ Config.CastStatic = me.classic ? 15 : [25, 33, 50][me.diff];
+
+ /* Gear */
+ NTIP.buildList(levelingTiers);
+ me.expansion && NTIP.buildList(expansionTiers);
+
+ Config.imbueables = (function () {
+ /**
+ * @param {number} name
+ * @param {function(): boolean} condition
+ */
+ const _imbueObj = (name, condition) => ({ name: name, condition: condition });
+
+ return [
+ _imbueObj(
+ sdk.items.JaredsStone,
+ () => (me.normal && me.expansion)
+ ),
+ _imbueObj(
+ sdk.items.SwirlingCrystal,
+ () => (!me.normal && me.charlvl < 66 && me.expansion)
+ ),
+ _imbueObj(
+ sdk.items.DimensionalShard,
+ () => (me.equipped.get(sdk.body.RightArm).tier < 777 && me.expansion)
+ ),
+ _imbueObj(
+ sdk.items.Belt,
+ () => (me.normal && (me.equipped.get(sdk.body.RightArm).tier > 777 || me.classic))
+ ),
+ _imbueObj(
+ sdk.items.MeshBelt,
+ () => (!me.normal
+ && me.charlvl < 46
+ && me.trueStr > 58
+ && (me.equipped.get(sdk.body.RightArm).tier > 777 || me.classic)
+ )
+ ),
+ _imbueObj(
+ sdk.items.SpiderwebSash,
+ () => (!me.normal && me.trueStr > 50 && (me.equipped.get(sdk.body.RightArm).tier > 777 || me.classic))
+ ),
+ ].filter((item) => item.condition());
+ })();
+
+ let imbueArr = SetUp.imbueItems();
+
+ !me.smith && NTIP.buildList(imbueArr);
+
+ switch (me.gametype) {
+ case sdk.game.gametype.Classic:
+ // Res shield
+ if ((me.equipped.get(sdk.body.LeftArm).tier < 487
+ && !me.equipped.get(sdk.body.RightArm).twoHanded)
+ || (me.equipped.get(sdk.body.RightArm).tier < 487 && me.equipped.get(sdk.body.RightArm).twoHanded)) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/PDiamondShield.js");
+ }
+
+ break;
+ case sdk.game.gametype.Expansion:
+ NTIP.addLine("[name] >= VexRune && [name] <= ZodRune");
+ const { basicSocketables, addSocketableObj } = require("../Utils/General");
+
+ /* Crafting */
+ if (me.equipped.get(sdk.body.Neck).tier < 100000) {
+ Config.Recipes.push([Recipe.Caster.Amulet]);
+ }
+
+ {
+ let maxedMage = me.getItemsEx()
+ .filter(i => i.itemType === sdk.items.type.Gloves)
+ .find(i => NTIP.GetTier(i) >= 110000);
+
+ if (!maxedMage) {
+ Config.Recipes.push([Recipe.Unique.Armor.ToExceptional, "Light Gauntlets", Roll.NonEth]);
+ if (["Blova", "Lightning"].includes(SetUp.finalBuild)) {
+ Config.Recipes.push([Recipe.Unique.Armor.ToElite, "Battle Gauntlets", Roll.NonEth, "magefist"]);
+ }
+ }
+ }
+
+ Config.socketables = Config.socketables.concat(basicSocketables.caster, basicSocketables.all);
+
+ // FinalBuild specific setup
+ switch (SetUp.finalBuild) {
+ case "Blova":
+ case "Lightning":
+ // Infinity
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).prefixnum !== sdk.locale.items.Infinity) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInfinity.js");
+ }
+
+ // Spirit Shield
+ if (LADDER_ENABLED && SetUp.currentBuild === SetUp.finalBuild
+ && (me.equipped.get(sdk.body.LeftArm).tier < 1000
+ || me.equipped.get(sdk.body.LeftArmSecondary).prefixnum !== sdk.locale.items.Spirit)) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritShield.js");
+ }
+
+ // Chains of Honor
+ if (!me.checkItem({ name: sdk.locale.items.ChainsofHonor }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/ChainsOfHonor.js");
+ }
+
+ // Heart of the Oak
+ if (!me.checkItem({ name: sdk.locale.items.HeartoftheOak }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/HeartOfTheOak.js");
+ }
+
+ Config.socketables.push(addSocketableObj(sdk.items.Monarch, [], [],
+ !me.hell,
+ /** @param {ItemUnit} item */
+ function (item) {
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ classid: sdk.items.Monarch,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+ return !me.getOwned(wanted).length && item.ilvl >= 41 && item.isBaseType && !item.ethereal;
+ }
+ ));
+
+ if (SetUp.finalBuild === "Lightning") {
+ Config.socketables.push(addSocketableObj(sdk.items.Shako,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal
+ ));
+ } else {
+ Config.socketables.push(addSocketableObj(sdk.items.Diadem,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.unique && !item.ethereal && item.getStat(sdk.stats.PierceLtng) === 20
+ ));
+ }
+ Check.itemSockables(sdk.items.Shako, "unique", "Harlequin Crest");
+ Check.itemSockables(sdk.items.Diadem, "unique", "Griffon's Eye");
+
+ break;
+ case "Meteorb":
+ case "Cold":
+ case "Blizzballer":
+ Config.socketables.push(addSocketableObj(sdk.items.DeathMask,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.set && !item.ethereal
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.LacqueredPlate,
+ [sdk.items.runes.Ber], [sdk.items.gems.Perfect.Ruby],
+ true, (item) => item.set && !item.ethereal
+ ));
+ Config.socketables.push(addSocketableObj(sdk.items.SwirlingCrystal, [sdk.items.runes.Ist], [],
+ false, (item) => item.set && !item.ethereal
+ ));
+
+ Check.itemSockables(sdk.items.LacqueredPlate, "set", "Tal Rasha's Guardianship");
+ Check.itemSockables(sdk.items.DeathMask, "set", "Tal Rasha's Horadric Crest");
+
+ break;
+ default:
+ break;
+ }
+
+ // Go ahead and keep two P-diamonds prior to finding a moser's unless already using a better shield
+ if (!me.checkItem({ name: sdk.locale.items.MosersBlessedCircle }).have
+ && !me.haveSome([
+ { name: sdk.locale.items.Sanctuary },
+ { name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Shield }
+ ])) {
+ NTIP.addLine("[name] == perfectdiamond # # [maxquantity] == 2");
+
+ if (me.getOwned({ classid: sdk.items.gems.Perfect.Diamond }).length < 2) {
+ Config.Recipes.push([Recipe.Gem, "flawlessdiamond"]);
+ }
+ }
+
+ Check.itemSockables(sdk.items.RoundShield, "unique", "Moser's Blessed Circle");
+
+ // Sanctuary
+ if (!me.haveSome([
+ { name: sdk.locale.items.Sanctuary },
+ { name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Shield }
+ ])
+ && ["Blova", "Lightning"].indexOf(SetUp.currentBuild) === -1) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Sanctuary.js");
+ }
+
+ // Call to Arms
+ if (!me.checkItem({ name: sdk.locale.items.CalltoArms }).have) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/CallToArms.js");
+ }
+
+ // Spirit Sword
+ if (LADDER_ENABLED && me.equipped.get(sdk.body.RightArm).tier < 777) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/SpiritSword.js");
+ }
+
+ // Merc Insight
+ if (LADDER_ENABLED && Item.getMercEquipped(sdk.body.RightArm).tier < 3600) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercInsight.js");
+ }
+
+ // Lore
+ if (me.equipped.get(sdk.body.Head).tier < 315) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Lore.js");
+ }
+
+ // Ancients' Pledge
+ if (me.equipped.get(sdk.body.LeftArm).tier < 500) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/AncientsPledge.js");
+ }
+
+ // Merc Fortitude
+ if (Item.getMercEquipped(sdk.body.Armor).prefixnum !== sdk.locale.items.Fortitude) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercFortitude.js");
+ }
+
+ // Bone
+ if (me.equipped.get(sdk.body.Armor).tier < 450) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Bone.js");
+ }
+
+ // Merc Treachery
+ if (Item.getMercEquipped(sdk.body.Armor).tier < 15000) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/MercTreachery.js");
+ }
+
+ // Smoke
+ if (me.equipped.get(sdk.body.Armor).tier < 300) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Smoke.js");
+ }
+
+ // Stealth
+ if (me.equipped.get(sdk.body.Armor).tier < 233) {
+ includeIfNotIncluded("SoloPlay/BuildFiles/Runewords/Stealth.js");
+ }
+
+ SoloWants.buildList();
+
+ break;
+ }
+
+ return true;
+})();
diff --git a/libs/SoloPlay/Functions/AttackOverrides.js b/libs/SoloPlay/Functions/AttackOverrides.js
index 8ca7fe94..22bee0d6 100644
--- a/libs/SoloPlay/Functions/AttackOverrides.js
+++ b/libs/SoloPlay/Functions/AttackOverrides.js
@@ -6,1421 +6,1916 @@
*
*/
-includeIfNotIncluded("common/Attack.js");
+includeIfNotIncluded("core/Attack.js");
Attack.stopClear = false;
+Attack.Result.NOOP = 4;
+
Attack.init = function () {
- const CLASSNAME = sdk.player.class.nameOf(me.classid);
- 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 {
- if (!include("SoloPlay/Functions/ClassAttackOverrides/" + CLASSNAME + "Attacks.js")) {
- console.log(sdk.colors.Red + "Failed to include: " + "SoloPlay/Functions/ClassAttackOverrides/" + CLASSNAME + "Attacks.js");
- console.log(sdk.colors.Blue + "Loading default attacks instead");
- include("common/Attacks/" + CLASSNAME + ".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();
- this.getCurrentChargedSkillIds(true);
- this.checkBowOnSwitch(true);
- }
+ const CLASSNAME = sdk.player.class.nameOf(me.classid);
+ if (Config.Wereform) {
+ include("core/Attacks/wereform.js");
+ } else if (Config.CustomClassAttack && FileTools.exists("libs/core/Attacks/" + Config.CustomClassAttack + ".js")) {
+ console.log("Loading custom attack file");
+ include("core/Attacks/" + Config.CustomClassAttack + ".js");
+ } else {
+ if (!include("SoloPlay/Functions/ClassAttackOverrides/" + CLASSNAME + "Attacks.js")) {
+ console.log(sdk.colors.Red + "Failed to include: " + "SoloPlay/Functions/ClassAttackOverrides/" + CLASSNAME + "Attacks.js");
+ console.log(sdk.colors.Blue + "Loading default attacks instead");
+ include("core/Attacks/" + CLASSNAME + ".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();
+ this.getCurrentChargedSkillIds(true);
+ this.checkBowOnSwitch(true);
+ }
};
+/**
+ * @param {Monster} unit
+ * @returns { { timed: number, untimed: number }}
+ */
Attack.decideSkill = function (unit) {
- let skills = { timed: -1, untimed: -1 };
- if (!unit) return skills;
-
- const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
- const 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;
+ let skills = { timed: -1, untimed: -1 };
+ if (!unit) return skills;
+
+ const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
+ const 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;
};
Attack.getLowerResistPercent = function () {
- const calc = (level) => Math.floor(Math.min(25 + (45 * ((110 * level) / (level + 6)) / 100), 70));
- if (me.expansion && CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)) {
- return calc(CharData.skillData.chargedSkillsOnSwitch.find(chargeSkill => chargeSkill.skill === sdk.skills.LowerResist).level);
- }
- if (Skill.canUse(sdk.skills.LowerResist)) {
- return calc(me.getSkill(sdk.skills.LowerResist, sdk.skills.subindex.SoftPoints));
- }
- return 0;
+ const calc = function (level) {
+ return Math.floor(Math.min(25 + (45 * ((110 * level) / (level + 6)) / 100), 70));
+ };
+ if (me.expansion && CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)) {
+ return calc(
+ CharData.skillData.chargedSkillsOnSwitch
+ .find(chargeSkill => chargeSkill.skill === sdk.skills.LowerResist).level
+ );
+ }
+ if (Skill.canUse(sdk.skills.LowerResist)) {
+ return calc(me.getSkill(sdk.skills.LowerResist, sdk.skills.subindex.SoftPoints));
+ }
+ return 0;
};
-Attack.checkResist = function (unit = undefined, val = -1, maxres = 100) {
- if (!unit || !unit.type || unit.type === sdk.unittype.Player) return true;
-
- const damageType = typeof val === "number" ? this.getSkillElement(val) : val;
- const addLowerRes = !!(Skill.canUse(sdk.skills.LowerResist) || CharData.skillData.haveChargedSkillOnSwitch(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) && ((unit.isSpecial) || me.necromancer)) {
- 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) {
- // 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);
-
- let valid = false;
-
- // check unit's fire resistance
- (me.getState(sdk.states.HolyFire)) && (valid = this.getResist(unit, "fire") < maxres);
- // check unit's light resistance but only if the above check failed
- (me.getState(sdk.states.HolyShock) && !valid) && (valid = this.getResist(unit, "lightning") < 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) && ((unit.isSpecial && me.gold > 500000) || me.necromancer)) {
- return (this.getResist(unit, damageType) - (Math.floor(lowerResPercent / 5)) < 100);
- }
- }
-
- return this.getResist(unit, damageType) < maxres;
+/**
+ * Check if monster is immmune to a damagetype
+ * @param {Monster} unit
+ * @param {number} val
+ * @param {number} maxres
+ * @returns {boolean} true if they are not immune
+ */
+Attack.checkResist = function (unit, val = -1, maxres = 100) {
+ if (!unit || !unit.type || unit.type === sdk.unittype.Player) return true;
+
+ const damageType = typeof val === "number" ? this.getSkillElement(val) : val;
+ const addLowerRes = !!(
+ (Skill.canUse(sdk.skills.LowerResist)
+ || CharData.skillData.haveChargedSkillOnSwitch(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) && ((unit.isSpecial) || me.necromancer)) {
+ 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) {
+ // 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);
+ }
+
+ let valid = false;
+
+ // check unit's fire resistance
+ (me.getState(sdk.states.HolyFire)) && (valid = this.getResist(unit, "fire") < maxres);
+ // check unit's light resistance but only if the above check failed
+ (me.getState(sdk.states.HolyShock) && !valid) && (valid = this.getResist(unit, "lightning") < 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) && ((unit.isSpecial && me.gold > 500000) || me.necromancer)) {
+ return (this.getResist(unit, damageType) - (Math.floor(lowerResPercent / 5)) < 100);
+ }
+ }
+
+ return this.getResist(unit, damageType) < maxres;
};
-// Maybe make this a prototype and use game data to also check if should attack not just can based on effort?
-Attack.canAttack = function (unit = undefined) {
- if (!unit) return false;
- 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;
+/**
+ * @param {Monster} unit
+ * @returns {boolean} If we have a valid skill to use on this monster
+ * @todo Maybe make this a prototype and use game data to also check if should attack not just can based on effort?
+ */
+Attack.canAttack = function (unit) {
+ if (!unit) return false;
+ if (!unit.isMonster) return false;
+ if (me.sorceress) {
+ return Config.AttackSkill.some(function (skill) {
+ return skill > -1 && Attack.checkResist(unit, Attack.getSkillElement(skill));
+ });
+ }
+ // 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;
};
-Attack.openChests = function (range = 10, x = undefined, y = undefined) {
- if (!Config.OpenChests.Enabled || !Misc.openChestsEnabled) return false;
- x === undefined && (x = me.x);
- y === undefined && (y = me.y);
-
- if (me.getMobCount(range) > 1) return false;
-
- let list = [];
- const 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())
- && unit.getMobCount(10) === 0) {
- list.push(copyUnit(unit));
- }
- } while (unit.getNext());
- }
-
- while (list.length) {
- list.sort(Sort.units);
- Misc.openChest(list.shift()) && Pickit.pickItems();
- }
-
- return true;
+/**
+ * @param {number} range
+ * @param {number} x
+ * @param {number} y
+ * @returns {boolean}
+ */
+Attack.openChests = function (range, x, y) {
+ if (!Config.OpenChests.Enabled || !Misc.openChestsEnabled) return false;
+ range === undefined && (range = 5);
+ x === undefined && (x = me.x);
+ y === undefined && (y = me.y);
+
+ /** @param {ObjectUnit} chest */
+ const openChest = function (chest) {
+ // Skip invalid/open and Countess chests
+ if (!chest || chest.x === 12526 || chest.x === 12565 || chest.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;
+ }
+ if (Pather.getWalkDistance((chest.x + 1), (chest.y + 2)) > 10) {
+ // chest is too far away
+ console.debug("Chest too far away");
+ return false;
+ }
+ if ([(chest.x + 1), (chest.y + 2)].distance > 5) {
+ Pather.walkTo(chest.x + 1, chest.y + 2, 3);
+ }
+ Packet.entityInteract(chest);
+
+ return Misc.poll(function () {
+ return !chest || chest.mode !== sdk.objects.mode.Inactive;
+ }, 300, 10);
+ };
+ const containers = [
+ "chest", "loose rock", "hidden stash", "loose boulder",
+ "corpseonstick", "casket", "armorstand", "weaponrack",
+ "holeanim", "roguecorpse", "corpse", "tomb2", "tomb3", "chest3",
+ "skeleton", "guardcorpse", "sarcophagus", "object2",
+ "cocoon", "hollow log", "hungskeleton",
+ "bonechest", "woodchestl", "woodchestr",
+ "burialchestr", "burialchestl", "chestl",
+ "chestr", "groundtomb", "tomb3l", "tomb1l",
+ "deadperson", "deadperson2", "groundtombl", "casket", "barrel", "ratnest",
+ "goo pile", "largeurn", "urn", "jug", "basket", "stash",
+ "pillar", "skullpile", "skull pile",
+ "jar3", "jar2", "jar1", "barrel wilderness",
+ "explodingchest", "icecavejar1", "icecavejar2", "icecavejar3",
+ "icecavejar4", "evilurn"
+ ];
+ const list = [];
+ let unit = Game.getObject();
+
+ if (unit) {
+ do {
+ if (unit.name
+ && unit.mode === sdk.objects.mode.Inactive
+ && containers.includes(unit.name.toLowerCase())
+ && getDistance(unit, x, y) <= range
+ && !checkCollision(me, unit, sdk.collision.BlockWalk)
+ /* && unit.getMobCount(10) === 0 */) {
+ list.push(copyUnit(unit));
+ }
+ } while (unit.getNext());
+ }
+
+ while (list.length) {
+ list.sort(Sort.units);
+ openChest(list.shift()) && Pickit.pickItems(5);
+ }
+
+ return true;
};
-// this might be depreciated now
-Attack.killTarget = function (name = undefined) {
- if (!name || Config.AttackSkill[1] < 0) return false;
- typeof name === "string" && (name = name.toLowerCase());
- let target = (typeof name === "object" ? name : Misc.poll(() => Game.getMonster(name), 2000, 100));
-
- if (!target) {
- console.warn("ÿc8KillTargetÿc0 :: " + name + " not found. Performing Attack.Clear(25)");
- return (Attack.clear(25) && Pickit.pickItems());
- }
-
- // exit if target is immune
- if (target && !Attack.canAttack(target) && !Attack.canTeleStomp(target)) {
- console.warn("ÿc8KillTargetÿc0 :: Attack failed. " + target.name + " is immune.");
- return false;
- }
-
- 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;
- }
- };
-
- // think doing this might be safer for non-teleporters, alot of the time they end up either stuck in recursive node action <-> clear loop
- // or try to bull their way through mobs to the boss and instead should try to clear to them but without the loop
- if (!Pather.canTeleport() && Attack.clear(15, 0, target)) return true;
-
- const who = (!!target.name ? target.name : name);
- const gid = target.gid;
- let errorInfo = "";
- let [retry, attackCount] = [0, 0];
- let lastLoc = {x: me.x, y: me.y};
- let tick = getTickCount();
-
- try {
- console.log("ÿc7Kill ÿc0:: " + who);
- // disable opening chests while killing unit
- Misc.openChestsEnabled = false;
-
- 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;
- }
- }
-
- Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9);
- attackCount > 0 && attackCount % 15 === 0 && Skill.getRange(Config.AttackSkill[1]) < 4 && Packet.flash(me.gid);
- me.overhead("KillTarget: " + target.name + " health " + target.hpPercent + " % left");
- 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++;
-
- if (target.dead || Config.FastPick || (attackCount > 0 && attackCount % 5 === 0)) {
- Config.FastPick ? Pickit.fastPick() : Pickit.essessntialsPick(false, true);
- }
- }
-
- attackCount === Config.MaxAttackCount && (errorInfo = " (attackCount exceeded: " + attackCount + ")");
- 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));
- }
- } finally {
- // re-enable
- Misc.openChestsEnabled = true;
- }
-
- return (!target || !copyUnit(target).x || target.dead || !target.attackable);
+/**
+ * @param {Monster | string | number} name
+ * @returns {boolean}
+ */
+Attack.killTarget = function (name) {
+ if (!name || Config.AttackSkill[1] < 0) return false;
+ typeof name === "string" && (name = name.toLowerCase());
+ let target = (typeof name === "object"
+ ? name
+ : Misc.poll(function () {
+ return Game.getMonster(name);
+ }, 2000, 100));
+
+ if (!target) {
+ console.warn("ÿc8KillTargetÿc0 :: " + name + " not found. Performing Attack.Clear(25)");
+ return (Attack.clear(25) && Pickit.pickItems());
+ }
+
+ // exit if target is immune
+ if (target && !Attack.canAttack(target) && !Attack.canTeleStomp(target)) {
+ console.warn("ÿc8KillTargetÿc0 :: Attack failed. " + target.name + " is immune.");
+ return false;
+ }
+
+ 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;
+ }
+ };
+
+ // think doing this might be safer for non-teleporters, alot of the time they end up either stuck in recursive node action <-> clear loop
+ // or try to bull their way through mobs to the boss and instead should try to clear to them but without the loop
+ if (!Pather.canTeleport() && Attack.clear(15, 0, target)) return true;
+
+ const who = (!!target.name ? target.name : name);
+ const gid = target.gid;
+ let errorInfo = "";
+ let [retry, attackCount] = [0, 0];
+ let lastLoc = { x: me.x, y: me.y };
+ let tick = getTickCount();
+
+ try {
+ console.log("ÿc7Kill ÿc0:: " + who);
+ // disable opening chests while killing unit
+ Misc.openChestsEnabled = false;
+
+ while (attackCount < Config.MaxAttackCount && target.attackable && !this.skipCheck(target)) {
+ // Check if unit got invalidated, happens if necro raises a skeleton from the boss's corpse.
+ if (!target || !copyUnit(target).x) {
+ target = Game.getMonster(-1, -1, gid);
+ !target && (target = findTarget(gid, lastLoc));
+
+ if (!target) {
+ console.warn("ÿc1Failed to kill " + who + " (couldn't relocate unit)");
+ break;
+ }
+ }
+
+ if (Config.Dodge && me.hpPercent <= Config.DodgeHP) {
+ this.deploy(target, Config.DodgeRange, 5, 9);
+ }
+ if (attackCount > 0 && attackCount % 15 === 0) {
+ Skill.getRange(Config.AttackSkill[1]) < 4 && Packet.flash(me.gid);
+ }
+ me.overhead("KillTarget: " + target.name + " health " + target.hpPercent + " % left");
+ 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++;
+
+ if (target.dead || Config.FastPick || attackCount % 5 === 0) {
+ Config.FastPick ? Pickit.fastPick() : Pickit.essessntialsPick(false);
+ }
+ }
+
+ attackCount === Config.MaxAttackCount && (errorInfo = " (attackCount exceeded: " + attackCount + ")");
+ 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));
+ }
+ } finally {
+ // re-enable
+ Misc.openChestsEnabled = true;
+ }
+
+ return (!target || !copyUnit(target).x || target.dead || !target.attackable);
};
Attack.clearLocations = function (list = []) {
- for (let x = 0; x < list.length; x++) {
- Attack.clear(20);
- Pather.moveTo(list[x][0], list[x][1]);
- Attack.clear(20);
- Pickit.pickItems();
- }
-
- return true;
+ for (let x = 0; x < list.length; x++) {
+ Attack.clear(20);
+ Pather.moveTo(list[x][0], list[x][1]);
+ Attack.clear(20);
+ Pickit.pickItems();
+ }
+
+ return true;
};
-Attack.clearPos = function (x = undefined, y = undefined, range = 15, pickit = true) {
- while (!me.gameReady) {
- delay(40);
- }
-
- if (typeof (range) !== "number") throw new Error("Attack.clear: range must be a number.");
- if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0 || Attack.stopClear || !x || !y) return false;
-
- let i, start, skillCheck;
- let [monsterList, gidAttack] = [[], []];
- let [retry, attackCount] = [0, 0];
- let target = Game.getMonster();
-
- if (target) {
- do {
- if (target.attackable && !this.skipCheck(target) && this.canAttack(target)) {
- // Speed optimization - don't go through monster list until there's at least one within clear range
- if (!start && getDistance(target, x, y) <= range && (Pather.useTeleport() || !checkCollision(me, target, sdk.collision.WallOrRanged))) {
- start = true;
- }
-
- monsterList.push(copyUnit(target));
- }
- } while (target.getNext());
- }
-
- while (start && monsterList.length > 0 && attackCount < 300) {
- if (me.dead || Attack.stopClear) return false;
-
- monsterList.sort(this.sortMonsters);
- target = copyUnit(monsterList[0]);
-
- if (target.x !== undefined
- && (getDistance(target, x, y) <= range || (this.getScarinessLevel(target) > 7 && getDistance(me, target) <= range)) && target.attackable) {
- Config.Dodge && me.hpPercent <= Config.DodgeHP && this.deploy(target, Config.DodgeRange, 5, 9);
-
- Misc.townCheck(true);
- let checkMobAttackCount = gidAttack.find(g => g.gid === target.gid);
- let checkAttackSkill = (!!checkMobAttackCount && checkMobAttackCount.attacks > 0 && checkMobAttackCount.attacks % 3 === 0);
- let result = ClassAttack.doAttack(target, checkAttackSkill);
-
- 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;
- }
- }
-
- (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) {
- console.warn("ÿc1Skipping " + target.name + " " + target.gid + " " + gidAttack[i].attacks);
- monsterList.shift();
- }
-
- if (target.dead || Config.FastPick || attackCount % 5 === 0) {
- Config.FastPick ? Pickit.fastPick() : Pickit.essessntialsPick(false, true);
- }
- } else {
- if (retry++ > 3) {
- monsterList.shift();
- retry = 0;
- }
-
- Packet.flash(me.gid);
- }
- } else {
- monsterList.shift();
- }
- }
-
- ClassAttack.afterAttack(pickit);
- pickit && Attack.openChests(range, x, y);
- attackCount > 0 && pickit && Pickit.pickItems();
- Pather.moveTo(x, y);
-
- return true;
+/**
+ * Clear around a certain node
+ * @param {number} x
+ * @param {number} y
+ * @param {number} range
+ * @param {boolean} pickit
+ * @param {function(): boolean} [cb]
+ * @returns {boolean}
+ */
+Attack.clearPos = function (x, y, range = 15, pickit = true, cb = null) {
+ /** @type {PathNode} */
+ const _startPos = { x: x, y: y };
+ const _mobCount = _startPos.mobCount({ range: range });
+ console.debug("ÿc7ClearPosÿc0 :: " + _mobCount + " monsters around " + _startPos.x + ", " + _startPos.y);
+ try {
+ if (typeof (range) !== "number") throw new Error("Attack.clear: range must be a number.");
+ if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0 || Attack.stopClear || !x || !y) return false;
+ NodeAction.enabled = false;
+
+ while (!me.gameReady) {
+ delay(40);
+ }
+
+ /** @type {Map} */
+ const monsterList = [];
+ let start = false;
+ let [retry, attackCount] = [0, 0];
+ let target = Game.getMonster();
+
+ if (target) {
+ do {
+ if (target.attackable && !this.skipCheck(target) && this.canAttack(target)) {
+ // Speed optimization - don't go through monster list until there's at least one within clear range
+ if (!start && getDistance(target, _startPos) <= range
+ && (Pather.useTeleport() || !checkCollision(me, target, sdk.collision.BlockWalk))) {
+ start = true;
+ }
+
+ monsterList.push(copyUnit(target));
+ }
+ } while (target.getNext());
+ }
+
+ while (start && monsterList.length > 0 && attackCount < 300) {
+ if (me.dead || Attack.stopClear) return false;
+
+ monsterList.sort(this.sortMonsters);
+ target = copyUnit(monsterList[0]);
+
+ if (target.x !== undefined
+ && (getDistance(target, _startPos) <= range
+ || (this.getScarinessLevel(target) > 7 && getDistance(me, target) <= range))
+ && target.attackable) {
+ if (Config.Dodge && me.hpPercent <= Config.DodgeHP) {
+ this.deploy(target, Config.DodgeRange, 5, 9);
+ }
+
+ let _currMon = attacks.get(target.gid);
+ const checkAttackSkill = (!!_currMon && _currMon.attacks > 0 && _currMon.attacks % 3 === 0);
+ const result = ClassAttack.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;
+ let skillCheck;
+ const isSpecial = target.isSpecial;
+ const secAttack = me.barbarian ? (isSpecial ? 2 : 4) : 5;
+ let checkSkill = Config.AttackSkill[isSpecial ? 1 : 3];
+ let hammerCheck = me.paladin && checkSkill === sdk.skills.BlessedHammer;
+
+ if (Config.AttackSkill[secAttack] > -1 && (!Attack.checkResist(target, checkSkill)
+ || (hammerCheck && !ClassAttack.getHammerPosition(target)))) {
+ skillCheck = Config.AttackSkill[secAttack];
+ } else {
+ skillCheck = checkSkill;
+ }
+
+ // Desync/bad position handler
+ switch (skillCheck) {
+ case sdk.skills.BlessedHammer:
+ // Tele in random direction with Blessed Hammer
+ if (_currMon.attacks > 0 && _currMon.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 (_currMon.attacks > 0 && _currMon.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 && _currMon.attacks > 15) {
+ console.warn("ÿc1Skipping " + target.name + " " + target.gid + " " + _currMon.attacks);
+ monsterList.shift();
+ }
+
+ if (Config.FastPick || attackCount % 5 === 0) {
+ Config.FastPick
+ ? Pickit.fastPick()
+ : target.dead
+ ? Pickit.pickItems(6)
+ : Pickit.essessntialsPick(false);
+ } else {
+ Pickit.pickItems(4);
+ }
+ Attack.openChests();
+ // if (_startPos.distance > 15) {
+ // Pather.move(
+ // _startPos,
+ // { allowNodeActions: false, allowClearing: false, minDist: 10 }
+ // );
+ // }
+ if (typeof cb === "function" && cb()) {
+ break;
+ }
+ } else {
+ if (retry++ > 3) {
+ monsterList.shift();
+ retry = 0;
+ }
+
+ Packet.flash(me.gid);
+ }
+ } else {
+ monsterList.shift();
+ }
+ }
+
+ if (attackCount > 0) {
+ ClassAttack.afterAttack(pickit);
+ if (pickit) {
+ Attack.openChests(range, x, y);
+ Pickit.pickItems();
+ }
+ }
+ if (Developer.debugging.pathing) {
+ console.debug("Returning to position " + x + "/" + y + " distance: " + [x, y].distance);
+ }
+ if (_startPos.distance > 15) {
+ Pather.move(
+ _startPos,
+ { allowNodeActions: false, allowClearing: false, minDist: 10 }
+ );
+ }
+ // if ([x, y].distance > 5) {
+ // // todo - generate path and create hooks along it so we don't waste this movement time
+ // Pather.move({ x: x, y: y }, { allowClearing: false });
+ // }
+ console.debug("Start count: " + _mobCount + " End count: " + _startPos.mobCount({ range: range }));
+ return _startPos.mobCount() < _mobCount;
+ } finally {
+ NodeAction.enabled = true;
+ }
};
-Attack.buildMonsterList = function (skipBlocked = false) {
- skipBlocked === true && (skipBlocked = 0x4);
- let monList = [];
- let monster = Game.getMonster();
-
- if (monster) {
- do {
- monster.attackable && monList.push(copyUnit(monster));
- } while (monster.getNext());
- }
-
- return skipBlocked === 0x4 ? monList.filter(mob => !checkCollision(me, mob, skipBlocked)) : monList;
+/**
+ * @description Clear an already formed array of monstas
+ * @param {Function | Array} mainArg
+ * @param {Function} [sortFunc]
+ * @param {boolean} [refresh]
+ * @returns {boolean}
+ */
+Attack.clearList = function (mainArg, sortFunc, refresh) {
+ if (typeof mainArg !== "function" && !Array.isArray(mainArg)) {
+ throw new Error("clearList: Invalid argument");
+ }
+ /** @type {Map 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) {
+ if (Config.Dodge && me.hpPercent <= Config.DodgeHP) {
+ this.deploy(target, Config.DodgeRange, 5, 9);
+ }
+
+ let _currMon = attacks.get(target.gid);
+ const checkAttackSkill = (!!_currMon && _currMon.attacks > 0 && _currMon.attacks % 3 === 0);
+ const result = ClassAttack.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;
+ let skillCheck;
+ const isSpecial = target.isSpecial;
+ const secAttack = me.barbarian ? (isSpecial ? 2 : 4) : 5;
+ let checkSkill = Config.AttackSkill[isSpecial ? 1 : 3];
+ let hammerCheck = me.paladin && checkSkill === sdk.skills.BlessedHammer;
+
+ if (Config.AttackSkill[secAttack] > -1 && (!Attack.checkResist(target, checkSkill)
+ || (hammerCheck && !ClassAttack.getHammerPosition(target)))) {
+ skillCheck = Config.AttackSkill[secAttack];
+ } else {
+ skillCheck = checkSkill;
+ }
+
+ // Desync/bad position handler
+ switch (skillCheck) {
+ case sdk.skills.BlessedHammer:
+ // Tele in random direction with Blessed Hammer
+ if (_currMon.attacks > 0 && _currMon.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 (_currMon.attacks > 0 && _currMon.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 && _currMon.attacks > 15) {
+ console.warn("ÿc1Skipping " + target.name + " " + target.gid + " " + _currMon.attacks);
+ monsterList.shift();
+ }
+
+ attackCount += 1;
+
+ if (Config.FastPick || attackCount % 5 === 0) {
+ Config.FastPick
+ ? Pickit.fastPick()
+ : target.dead
+ ? Pickit.pickItems(6)
+ : Pickit.essessntialsPick(false);
+ } else {
+ Pickit.pickItems(4);
+ }
+ Attack.openChests();
+ } 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;
};
// Clear an entire area based on settings
Attack.clearLevelEx = function (givenSettings = {}) {
- function RoomSort(a, b) {
- return getDistance(myRoom[0], myRoom[1], a[0], a[1]) - getDistance(myRoom[0], myRoom[1], b[0], b[1]);
- }
-
- // credit @jaenstr
- const settings = Object.assign({}, {
- spectype: Config.ClearType,
- quitWhen: () => {}
- }, givenSettings);
-
- let room = getRoom();
- if (!room) return false;
-
- const currentArea = getArea().id;
- let result, myRoom, previousArea, rooms = [];
-
- do {
- rooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]);
- } while (room.getNext());
-
- 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();
- result = Pather.getNearestWalkable(room[0], room[1], 18, 3);
-
- if (result) {
- Pather.moveTo(result[0], result[1], 3, settings.spectype);
- previousArea = result;
-
- if (settings.quitWhen()) return true;
- if (!this.clear(40, settings.spectype)) {
- break;
- }
- } else if (currentArea !== getArea().id) {
- // Make sure bot does not get stuck in different area.
- Pather.moveTo(previousArea[0], previousArea[1], 3, settings.spectype);
- }
- }
-
- return true;
+ function RoomSort (a, b) {
+ return getDistance(myRoom[0], myRoom[1], a[0], a[1]) - getDistance(myRoom[0], myRoom[1], b[0], b[1]);
+ }
+
+ // credit @jaenstr
+ const settings = Object.assign({}, {
+ spectype: Config.ClearType,
+ quitWhen: () => {}
+ }, givenSettings);
+
+ let room = getRoom();
+ if (!room) return false;
+
+ const currentArea = getArea().id;
+ let result, myRoom, previousArea, rooms = [];
+
+ do {
+ rooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]);
+ } while (room.getNext());
+
+ 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();
+ result = Pather.getNearestWalkable(room[0], room[1], 18, 3);
+
+ if (result) {
+ Pather.moveTo(result[0], result[1], 3, settings.spectype);
+ previousArea = result;
+
+ if (settings.quitWhen()) return true;
+ if (!this.clear(40, settings.spectype)) {
+ break;
+ }
+ } else if (currentArea !== getArea().id) {
+ // Make sure bot does not get stuck in different area.
+ Pather.moveTo(previousArea[0], previousArea[1], 3, settings.spectype);
+ }
+ }
+
+ return true;
};
// Clear an entire area until area is done or level is reached
Attack.clearLevelUntilLevel = function (charlvl = undefined, 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;
-
- !charlvl && (charlvl = me.charlvl + 1);
- let result, myRoom, previousArea;
- let rooms = [];
- const currentArea = getArea().id;
-
- do {
- rooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]);
- } while (room.getNext());
-
- myPrint("Starting Clear until level My level: " + me.charlvl + " wanted level: " + charlvl);
-
- while (rooms.length > 0) {
- // get the first room + initialize myRoom var
- !myRoom && (room = getRoom(me.x, me.y));
-
- if (room) {
- if (room instanceof Array) { // use previous room to calculate distance
- 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();
- result = Pather.getNearestWalkable(room[0], room[1], 18, 3);
-
- if (result) {
- Pather.moveTo(result[0], result[1], 3, spectype);
- previousArea = result;
-
- if (Attack.stopClear) {
- Attack.stopClear = false; // Reset value
- return true;
- }
-
- if (me.charlvl >= charlvl) {
- myPrint("Clear until level requirment met. My level: " + me.charlvl + " wanted level: " + charlvl);
- return true;
- }
-
- 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);
- }
- }
-
- return true;
+ 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;
+
+ !charlvl && (charlvl = me.charlvl + 1);
+ let result, myRoom, previousArea;
+ let rooms = [];
+ /** @type {PathNode} */
+ let node = { x: null, y: null };
+ const currentArea = getArea().id;
+
+ do {
+ rooms.push([room.x * 5 + room.xsize / 2, room.y * 5 + room.ysize / 2]);
+ } while (room.getNext());
+
+ myPrint("Starting Clear until level My level: " + me.charlvl + " wanted level: " + charlvl);
+
+ while (rooms.length > 0) {
+ // get the first room + initialize myRoom var
+ !myRoom && (room = getRoom(me.x, me.y));
+
+ if (room) {
+ if (room instanceof Array) { // use previous room to calculate distance
+ 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();
+ result = Pather.getNearestWalkable(room[0], room[1], 18, 3);
+
+ if (result) {
+ node = { x: result[0], y: result[1] };
+ Pather.move(node, { retry: 3, clearSettings: { spectype: spectype } });
+ previousArea = result;
+
+ if (Attack.stopClear) {
+ Attack.stopClear = false; // Reset value
+ return true;
+ }
+
+ if (me.charlvl >= charlvl) {
+ myPrint("Clear until level requirment met. My level: " + me.charlvl + " wanted level: " + charlvl);
+ return true;
+ }
+
+ if (!this.clear(40, spectype)) {
+ break;
+ }
+ } else if (currentArea !== getArea().id) {
+ // Make sure bot does not get stuck in different area.
+ node = { x: previousArea[0], y: previousArea[1] };
+ Pather.move(node, { retry: 3, clearSettings: { spectype: spectype } });
+ }
+ }
+
+ return true;
};
-// 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
-// accepts object for bossId or classid, gid sometimes works depends if the gid is > 999
-// should stop clearing after boss is killed if we are using bossid
-// @todo if we are skipping a certain monster because of enchant or aura we should skip any monsters within the area of that scary monster
-// @todo maybe include refresh call every x amount of attacks in case the we've changed position and the monster we are targetting isn't actually the right one anymore
-Attack.clear = function (range = 25, spectype = 0, bossId = false, sortfunc = undefined, pickit = true) {
- while (!me.gameReady) {
- delay(40);
- }
-
- if (typeof (range) !== "number") throw new Error("Attack.clear: range must be a number.");
- if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0 || Attack.stopClear) return false;
- const canTeleport = Pather.canTeleport();
- !sortfunc && (sortfunc = canTeleport ? this.sortMonsters : Attack.walkingSortMonsters);
-
- 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 && (canTeleport || !checkCollision(me, target, sdk.collision.BlockWall))) {
- start = true;
- }
-
- monsterList.push(copyUnit(target));
- }
- } while (target.getNext());
- }
-
- // if (!canTeleport && range < 10 && !me.inArea(sdk.areas.BloodMoor) && monsterList.some(mon => mon.isFallen)) {
- // // handle shamans if they are just out of the range we were meant to clear but we have fallens in the list
- // }
-
- while (start && monsterList.length > 0 && attackCount < 300) {
- if (me.dead || Attack.stopClear) 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 && getDistance(me, target) <= 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));
- }
- const checkMobAttackCount = gidAttack.find(g => g.gid === target.gid);
- const checkAttackSkill = (!!checkMobAttackCount && checkMobAttackCount.attacks > 0 && checkMobAttackCount.attacks % 3 === 0);
- const result = ClassAttack.doAttack(target, checkAttackSkill);
-
- 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;
- }
- }
-
- (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) {
- console.warn("ÿc1Skipping " + target.name + " " + target.gid + " " + gidAttack[i].attacks);
- monsterList.shift();
- }
-
- // we cleared this monster so subtract amount of attacks from current attack count in order to prevent pre-maturely ending clear
- if (target.dead) {
- if (boss && boss.gid === target.gid) {
- killedBoss = true;
- console.log("ÿc7Cleared ÿc0:: " + (!!target.name ? target.name : bossId) + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick));
- }
- attackCount -= gidAttack[i].attacks;
- }
- (target.dead || Config.FastPick || attackCount % 5 === 0) && Config.FastPick ? Pickit.fastPick() : Pickit.essessntialsPick(false, true);
- } else {
- if (Coords_1.isBlockedBetween(me, target)) {
- let collCheck = Coords_1.getCollisionBetweenCoords(me.x, me.y, target.x, target.y);
- if (collCheck !== sdk.collision.MonsterObject) {
- console.log("ÿc1Skipping " + target.name + " because they are blocked. Collision: " + collCheck.toString(16));
- monsterList.shift();
- retry = 0;
- }
- }
-
- if (retry++ > 3) {
- monsterList.shift();
- retry = 0;
- }
-
- Packet.flash(me.gid);
- }
- } else {
- monsterList.shift();
- }
- }
-
- ClassAttack.afterAttack(pickit);
- this.openChests(range, orgx, orgy);
- attackCount > 0 && pickit && Pickit.pickItems();
-
- 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 false;
- }
- }
-
- return true;
+/**
+ * @description Clear monsters in a section based on range and spectype or clear monsters around a boss monster
+ * @param {number} [range] - area radius to clear around
+ * @param {number} [spectype] - type of monsters to clear
+ * @param {number | Monster} [bossId] - gid, classid, or Monster unit to clear
+ * @param {{ (a: Monster, b: Monster) => number }} [sortfunc] - how to sort the monsters we are clearing, defaults to Attack.sortMonsters
+ * @param {boolean} [pickit] - Are we allowed to pick items when we are done
+ * @returns {AttackResult} - (0) on failure, (1) on success, (4) on no operation performed
+ * @todo
+ * - change to passing an object
+ * - if we are skipping a certain monster because of enchant or aura we should skip any monsters within the area of that scary monster
+ * - maybe include refresh call every x amount of attacks in case the we've changed position and the monster we are targetting isn't actually the right one anymore
+ * - should we stop clearing after boss is killed if we are using bossid?
+ */
+Attack.clear = function (range, spectype, bossId, sortfunc, pickit = true) {
+ if (Config.AttackSkill[1] < 0 || Config.AttackSkill[3] < 0 || Attack.stopClear) return false;
+
+ while (!me.gameReady) {
+ delay(40);
+ }
+
+ range === undefined && (range = 25);
+ if (typeof (range) !== "number") throw new Error("Attack.clear: range must be a number.");
+ spectype === undefined && (spectype = 0);
+ bossId === undefined && (bossId = false);
+ const canTeleport = Pather.canTeleport();
+ !sortfunc && (sortfunc = canTeleport ? this.sortMonsters : Attack.walkingSortMonsters);
+
+ /** @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 });
+ } else {
+ ({ orgx, orgy } = { orgx: me.x, orgy: me.y });
+ }
+
+ const 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
+ && (canTeleport || !checkCollision(me, target, sdk.collision.BlockWalk))) {
+ start = true;
+ }
+
+ monsterList.push(copyUnit(target));
+ }
+ } while (target.getNext());
+ }
+
+ // if (!canTeleport && range < 10 && !me.inArea(sdk.areas.BloodMoor) && monsterList.some(mon => mon.isFallen)) {
+ // // handle shamans if they are just out of the range we were meant to clear but we have fallens in the list
+ // }
+
+ while (start && monsterList.length > 0 && attackCount < 300) {
+ if (me.dead || Attack.stopClear) return false;
+
+ boss && (({ orgx, orgy } = { orgx: boss.x, orgy: boss.y }));
+ monsterList.sort(sortfunc);
+ target = copyUnit(monsterList[0]);
+
+ if ((target.x === undefined || !target.attackable) && monsterList.shift()) {
+ // console.debug("Attack.clear: Invalid target, skipping | ", target);
+ continue;
+ }
+
+ if ((getDistance(target, orgx, orgy) <= range
+ || (this.getScarinessLevel(target) > 7 && target.distance <= range))) {
+ if (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 > 0 && _currMon.attacks % 3 === 0);
+ const result = ClassAttack.doAttack(target, checkAttackSkill);
+
+ if (result) {
+ // console.debug("Attack.clear: " + 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;
+ let isSpecial = target.isSpecial;
+ let secAttack = me.barbarian ? (isSpecial ? 2 : 4) : 5;
+ let checkSkill = Config.AttackSkill[isSpecial ? 1 : 3];
+ let hammerCheck = me.paladin && checkSkill === sdk.skills.BlessedHammer;
+
+ if (Config.AttackSkill[secAttack] > -1 && (!Attack.checkResist(target, checkSkill)
+ || (hammerCheck && !ClassAttack.getHammerPosition(target)))) {
+ skillCheck = Config.AttackSkill[secAttack];
+ } else {
+ skillCheck = checkSkill;
+ }
+
+ // Desync/bad position handler
+ switch (skillCheck) {
+ case sdk.skills.BlessedHammer:
+ // Tele in random direction with Blessed Hammer
+ if (_currMon.attacks > 0 && _currMon.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 (_currMon.attacks > 0 && _currMon.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 && _currMon.attacks > 15) {
+ console.warn("ÿc1Skipping " + target.name + " " + target.gid + " " + _currMon.attacks);
+ monsterList.shift();
+ }
+
+ // we cleared this monster so subtract amount of attacks from current attack count in order to prevent pre-maturely ending clear
+ if (Config.FastPick || attackCount % 5 === 0) {
+ Config.FastPick
+ ? Pickit.fastPick()
+ : target.dead
+ ? Pickit.pickItems(6)
+ : Pickit.essessntialsPick(false);
+ } else {
+ Pickit.pickItems(4);
+ }
+ Attack.openChests(4);
+ if (target.dead) {
+ clearResult = Attack.Result.SUCCESS;
+ if (boss && boss.gid === target.gid) {
+ killedBoss = true;
+ console.log(
+ "ÿc7Cleared ÿc0:: " + (!!target.name ? target.name : bossId)
+ + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick)
+ );
+ }
+ attackCount -= _currMon.attacks;
+ }
+ } else {
+ if (Coords_1.isBlockedBetween(me, target)) {
+ let collCheck = Coords_1.getCollisionBetweenCoords(me.x, me.y, target.x, target.y);
+ if (collCheck !== sdk.collision.MonsterObject) {
+ console.log("ÿc1Skipping " + target.name + " because they are blocked. Collision: " + collCheck.toString(16));
+ monsterList.shift();
+ retry = 0;
+
+ continue;
+ }
+ }
+
+ 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();
+ }
+
+ if (boss && !killedBoss) {
+ // check if boss corpse is around
+ // sometimes this fails, need better check for it
+ 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 clearResult;
};
// Take a array of coords - path and clear
// pick parameter is range of items to pick
// From legacy sonic
Attack.clearCoordList = function (list, pick) {
- for (let node of list) {
- Attack.clear(node.radius);
- Pather.moveTo(node.x, node.y);
- Attack.clear(node.radius);
- pick && Pickit.pickItems(pick);
- }
+ for (let node of list) {
+ Attack.clear(node.radius);
+ Pather.moveTo(node.x, node.y);
+ Attack.clear(node.radius);
+ pick && Pickit.pickItems(pick);
+ }
};
Attack.checkBowOnSwitch = function (firstInit = false) {
- const preBow = CharData.skillData.bowData.bowOnSwitch;
- const checkTypes = [sdk.items.type.AmazonBow, sdk.items.type.Bow, sdk.items.type.Crossbow, sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver];
-
- me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
- const items = me.getItemsEx().filter(item => item && item.isEquipped && item.isOnSwap && checkTypes.includes(item.itemType));
- if (preBow && !items.some(item => [sdk.items.type.AmazonBow, sdk.items.type.Bow, sdk.items.type.Crossbow].includes(item.itemType))) {
- CharData.skillData.bowData.resetBowData();
- return;
- }
- items.forEach(item => {
- if ([sdk.items.type.AmazonBow, sdk.items.type.Bow, sdk.items.type.Crossbow].includes(item.itemType)) {
- CharData.skillData.bowData.bowOnSwitch = true;
- if (CharData.skillData.bowData.bowGid !== item.gid) {
- CharData.skillData.bowData.setBowInfo(item, firstInit);
- }
- }
- if ([sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver].includes(item.itemType)) {
- if (CharData.skillData.bowData.quiverType !== item.itemType) {
- CharData.skillData.bowData.setArrowInfo(item, firstInit);
- }
- }
- });
+ const preBow = CharData.skillData.bow.onSwitch;
+ const _bows = [sdk.items.type.AmazonBow, sdk.items.type.Bow, sdk.items.type.Crossbow];
+ const _quivers = [sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver];
+ const checkTypes = [].concat(_bows, _quivers);
+
+ me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
+ const items = me.getItemsEx()
+ .filter(function (item) {
+ return item && item.isEquipped && item.isOnSwap && checkTypes.includes(item.itemType);
+ });
+ if (preBow && !items.some(item => _bows.includes(item.itemType))) {
+ CharData.skillData.bow.resetBowData();
+ return;
+ }
+ items.forEach(function (item) {
+ if (_bows.includes(item.itemType)) {
+ CharData.skillData.bow.onSwitch = true;
+ if (CharData.skillData.bow.bowGid !== item.gid) {
+ CharData.skillData.bow.setBowInfo(item, firstInit);
+ }
+ } else if (_quivers.includes(item.itemType)) {
+ if (CharData.skillData.bow.quiverType !== item.itemType) {
+ CharData.skillData.bow.setArrowInfo(item, firstInit);
+ }
+ }
+ });
};
Attack.haveDependancy = function (itemType) {
- return [sdk.items.type.AmazonBow, sdk.items.type.Bow, sdk.items.type.Crossbow].includes(itemType) ? me.getItem("aqv", sdk.items.mode.Equipped) : me.getItem("cqv", sdk.items.mode.Equipped);
+ return [sdk.items.type.AmazonBow, sdk.items.type.Bow, sdk.items.type.Crossbow].includes(itemType)
+ ? me.getItem("aqv", sdk.items.mode.Equipped)
+ : me.getItem("cqv", sdk.items.mode.Equipped);
};
Attack.useBowOnSwitch = function (unit, skillId = 0, switchBack = true) {
- if (!CharData.skillData.bowData.bowOnSwitch) return false;
- if (!this.haveDependancy(CharData.skillData.bowData.bowType)) return false;
- return Skill.switchCast(skillId, { hand: sdk.skills.hand.Left, x: unit, switchBack: switchBack });
+ if (!CharData.skillData.bow.onSwitch) return false;
+ if (!this.haveDependancy(CharData.skillData.bow.bowType)) return false;
+ return Skill.switchCast(skillId, { hand: sdk.skills.hand.Left, x: unit, switchBack: switchBack });
};
// maybe store the copyUnit of the item or at least gid so we don't need to iterate through all our items to find the one with the charged skill when we need it
Attack.getCurrentChargedSkillIds = function (init = false) {
- const chargeSkillObj = (skill, level, gid) => ({ skill: skill, level: level, gid: gid });
- let [currentChargedSkills, chargedSkills, chargedSkillsOnSwitch] = [[], [], []];
-
- // Item must be equipped - removed charms as I don't think at any point using hydra from torch has ever been worth it
- me.getItemsEx(-1)
- .filter(item => item && ((item.isEquipped && !item.rare)))
- .forEach(function (item) {
- 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) {
- // add to total list of skillIds
- if (stats[sdk.stats.ChargedSkill][i].charges > 0 && !currentChargedSkills.includes(stats[sdk.stats.ChargedSkill][i].skill)) {
- currentChargedSkills.push(stats[sdk.stats.ChargedSkill][i].skill);
- chargedSkills.push(chargeSkillObj(stats[sdk.stats.ChargedSkill][i].skill, stats[sdk.stats.ChargedSkill][i].level, item.gid));
- }
-
- // add to switch only list for use with swtich casting
- if (stats[sdk.stats.ChargedSkill][i].charges > 0 && !chargedSkillsOnSwitch.some(chargeSkill => chargeSkill.skill === stats[sdk.stats.ChargedSkill][i].skill) && item.isOnSwap) {
- chargedSkillsOnSwitch.push(chargeSkillObj(stats[sdk.stats.ChargedSkill][i].skill, stats[sdk.stats.ChargedSkill][i].level, item.gid));
- }
- }
- }
- } else {
- // add to total list
- if (stats[sdk.stats.ChargedSkill].charges > 0 && !currentChargedSkills.includes(stats[sdk.stats.ChargedSkill].skill)) {
- currentChargedSkills.push(stats[sdk.stats.ChargedSkill].skill);
- chargedSkills.push(chargeSkillObj(stats[sdk.stats.ChargedSkill].skill, stats[sdk.stats.ChargedSkill].level, item.gid));
- }
-
- // add to switch only list for use with swtich casting
- if (stats[sdk.stats.ChargedSkill].charges > 0 && !chargedSkillsOnSwitch.some(chargeSkill => chargeSkill.skill === stats[sdk.stats.ChargedSkill].skill) && item.isOnSwap) {
- chargedSkillsOnSwitch.push(chargeSkillObj(stats[sdk.stats.ChargedSkill].skill, stats[sdk.stats.ChargedSkill].level, item.gid));
- }
- }
- }
- });
-
- // only update other threads if this isn't being called from Attack.init
- if (CharData.skillData.currentChargedSkills.length > 0 || init) {
- switch (true) {
- case !currentChargedSkills.equals(CharData.skillData.currentChargedSkills):
- case Object.keys(Misc.recursiveSearch(chargedSkillsOnSwitch, CharData.skillData.chargedSkillsOnSwitch)).length > 0:
- case Object.keys(Misc.recursiveSearch(chargedSkills, CharData.skillData.chargedSkills)).length > 0:
- CharData.skillData.init(currentChargedSkills, chargedSkills, chargedSkillsOnSwitch);
- !init && CharData.skillData.update();
- break;
- }
- }
-
- return true;
+ /**
+ * @typedef {Object} Charge
+ * @property {number} skill
+ * @property {number} level
+ * @property {number} charges
+ * @property {number} maxcharges
+ */
+
+ /**
+ * @constructor
+ * @param {Charge} charge
+ * @param {number} gid
+ */
+ function ChargedSkill (charge, gid) {
+ this.skill = charge.skill;
+ this.level = charge.level;
+ this.charges = charge.charges;
+ this.maxcharges = charge.maxcharges;
+ this.gid = gid;
+ }
+
+ /** @type {Array} */
+ const currentChargedSkills = [];
+ /** @type {Array[]} */
+ const [chargedSkills, chargedSkillsOnSwitch] = [[], []];
+
+ // Item must be equipped - removed charms as I don't think at any point using hydra from torch has ever been worth it
+ me.getItemsEx(-1)
+ .filter(item => item && ((item.isEquipped && !item.rare)))
+ .forEach(function (item) {
+ let stats = item.getStat(-2);
+ if (!stats.hasOwnProperty(sdk.stats.ChargedSkill)) return;
+
+ /** @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) {
+ if (!charge || !charge.hasOwnProperty("skill")) continue;
+ // add to total list of skillIds
+ if (charge.charges > 0 && !currentChargedSkills.includes(charge.skill)) {
+ currentChargedSkills.push(charge.skill);
+ chargedSkills.push(new ChargedSkill(charge, item.gid));
+ }
+
+ // add to switch only list for use with swtich casting
+ if (charge.charges > 0
+ && !chargedSkillsOnSwitch.some(cSk => cSk.skill === charge.skill)
+ && item.isOnSwap) {
+ chargedSkillsOnSwitch.push(new ChargedSkill(charge, item.gid));
+ }
+ }
+ });
+
+ // only update other threads if this isn't being called from Attack.init
+ if (CharData.skillData.currentChargedSkills.length > 0 || init) {
+ switch (true) {
+ case !currentChargedSkills.equals(CharData.skillData.currentChargedSkills):
+ case Object.keys(Misc.recursiveSearch(chargedSkillsOnSwitch, CharData.skillData.chargedSkillsOnSwitch)).length > 0:
+ case Object.keys(Misc.recursiveSearch(chargedSkills, CharData.skillData.chargedSkills)).length > 0:
+ CharData.skillData.init(currentChargedSkills, chargedSkills, chargedSkillsOnSwitch);
+ break;
+ }
+ }
+
+ return true;
};
-Attack.getItemCharges = function (skillId = undefined) {
- if (!skillId) return false;
-
- let chargedItems = [], validCharge = function (itemCharge) {
- return itemCharge.skill === skillId && itemCharge.charges > 1;
- };
-
- // Item must equipped, or a charm in inventory
- me.getItemsEx(-1)
- .filter(item => item && (item.isEquipped && !item.rare || (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
- });
- }
- }
- }
- });
-
- return !!(chargedItems.length > 0);
+/**
+ * @param {number} skillId
+ * @returns {boolean}
+ */
+Attack.getItemCharges = function (skillId) {
+ if (!skillId) return false;
+
+ /** @param {Charge} itemCharge */
+ let validCharge = function (itemCharge) {
+ return itemCharge.skill === skillId && itemCharge.charges > 1;
+ };
+
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item && item.isEquipped && !item.rare;
+ });
+ for (let item of items) {
+ 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) {
+ if (!charge || !charge.hasOwnProperty("skill")) continue;
+ if (validCharge(charge)) return true;
+ }
+ }
+
+ return false;
};
-Attack.castCharges = function (skillId = undefined, unit = undefined) {
- if (!skillId || !unit || !Skill.wereFormCheck(skillId) || (me.inTown && !Skill.townSkill(skillId))) {
- return false;
- }
-
- me.castChargedSkillEx(skillId, unit) && delay(25);
- me.weaponswitch === 1 && me.switchWeapons(0);
-
- return true;
+/**
+ * @param {number} skillId
+ * @param {Monster} unit
+ * @returns {boolean}
+ */
+Attack.castCharges = function (skillId, unit) {
+ if (!skillId || !unit || !Skill.wereFormCheck(skillId)
+ || (me.inTown && !Skill.townSkill(skillId))) {
+ return false;
+ }
+
+ try {
+ me.castChargedSkillEx(skillId, unit) && delay(25);
+ } finally {
+ me.weaponswitch === 1 && me.switchWeapons(0);
+ }
+
+ return true;
};
-Attack.switchCastCharges = function (skillId = undefined, unit = undefined) {
- if (!skillId || !unit || !Skill.wereFormCheck(skillId) || (me.inTown && !Skill.townSkill(skillId))) {
- return false;
- }
-
- me.castSwitchChargedSkill(skillId, unit) && delay(25);
- me.weaponswitch === 1 && me.switchWeapons(0);
-
- return true;
+/**
+ * @param {number} skillId
+ * @param {Monster} unit
+ * @returns {boolean}
+ */
+Attack.switchCastCharges = function (skillId, unit) {
+ if (!skillId || !unit || !Skill.wereFormCheck(skillId)
+ || (me.inTown && !Skill.townSkill(skillId))) {
+ return false;
+ }
+
+ try {
+ me.castSwitchChargedSkill(skillId, unit) && delay(25);
+ } finally {
+ me.switchToPrimary();
+ }
+
+ return true;
};
-Attack.dollAvoid = function (unit = undefined) {
- if (!unit) return false;
- 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)) return Pather.moveTo(unit.x + cx, unit.y + cy);
- }
-
- return false;
+/**
+ * Position ourselves further from a doll to attack
+ * @param {Monster} unit
+ * @returns {boolean}
+ */
+Attack.dollAvoid = function (unit) {
+ if (!unit) return false;
+ 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;
};
// Its the inverse of spotOnDistance, its a spot going in the direction of the spot
Attack.inverseSpotDistance = function (spot, distance, otherSpot) {
- otherSpot === undefined && (otherSpot = me);
- let x = otherSpot.x, y = otherSpot.y, area = otherSpot.area;
- let nodes = getPath(area, x, y, spot.x, spot.y, 2, 5);
- return nodes && nodes.find((node) => node.distance > distance) || { x: x, y: y };
+ otherSpot === undefined && (otherSpot = me);
+ let x = otherSpot.x, y = otherSpot.y, area = otherSpot.area;
+ let nodes = getPath(area, x, y, spot.x, spot.y, 2, 5);
+ return nodes && nodes.find(function (node) {
+ return node.distance > distance;
+ }) || { x: x, y: y };
};
+/**
+ * @param {PathNode} coord
+ * @param {Monster} monster
+ * @returns {boolean}
+ */
Attack.shouldDodge = function (coord, monster) {
- return !!monster && getUnits(sdk.unittype.Missile)
- // for every missle that isnt from our merc
- .filter(missile => missile && monster && monster.gid === missile.owner)
- // if any
- .some(missile => {
- let xoff = Math.abs(coord.x - missile.targetx);
- let yoff = Math.abs(coord.y - missile.targety);
- let xdist = Math.abs(coord.x - missile.x);
- let ydist = Math.abs(coord.y - missile.y);
-
- // If missile wants to hit is and is close to us
- return xoff < 7 && yoff < 7 && xdist < 13 && ydist < 13;
- });
+ return !!monster && getUnits(sdk.unittype.Missile)
+ .filter(function (missile) {
+ // for every missle that isnt from our merc
+ return missile && monster && monster.gid === missile.owner;
+ })
+ .some(function (missile) {
+ // if any
+ let xoff = Math.abs(coord.x - missile.targetx);
+ let yoff = Math.abs(coord.y - missile.targety);
+ let xdist = Math.abs(coord.x - missile.x);
+ let ydist = Math.abs(coord.y - missile.y);
+
+ // If missile wants to hit is and is close to us
+ return xoff < 7 && yoff < 7 && xdist < 13 && ydist < 13;
+ });
};
-new Overrides.Override(Attack, Attack.sortMonsters, function(orignal, unitA, unitB) {
- let stateCheck = (m) => [sdk.states.Fanaticism, sdk.states.Conviction].some(state => m.getState(state));
- if ((unitA.isSpecial && stateCheck(unitA)) && (unitB.isSpecial && stateCheck(unitB))) return getDistance(me, unitA) - getDistance(me, unitB);
- if (unitA.isSpecial && stateCheck(unitA)) return -1;
- if (unitB.isSpecial && stateCheck(unitB)) return 1;
- return orignal(unitA, unitB);
-}).apply();
+new Overrides.Override(Attack,
+ Attack.sortMonsters,
+ /**
+ * @override
+ * @param {(a: Monster, b: Monster) => number} orignal
+ * @param {Monster} unitA
+ * @param {Monster} unitB
+ * @returns {number}
+ */
+ function (orignal, unitA, unitB) {
+ /** @param {Monster} m */
+ const stateCheck = function (m) {
+ return [sdk.states.Fanaticism, sdk.states.Conviction]
+ .some(function (state) {
+ return m.getState(state);
+ });
+ };
+ if ((unitA.isSpecial && stateCheck(unitA)) && (unitB.isSpecial && stateCheck(unitB))) {
+ return getDistance(me, unitA) - getDistance(me, unitB);
+ }
+ if (unitA.isSpecial && stateCheck(unitA)) return -1;
+ if (unitB.isSpecial && stateCheck(unitB)) return 1;
+ return orignal(unitA, unitB);
+ }
+).apply();
+/**
+ * @param {Monster} unitA
+ * @param {Monster} unitB
+ */
Attack.walkingSortMonsters = function (unitA, unitB) {
- // sort main bosses first
- if ((unitA.isPrimeEvil) && (unitB.isPrimeEvil)) return getDistance(me, unitA) - getDistance(me, unitB);
- if (unitA.isPrimeEvil) return -1;
- if (unitB.isPrimeEvil) 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, sdk.monsters.FoulCrowNest, sdk.monsters.BlackVultureNest, sdk.monsters.BloodHawkNest, sdk.monsters.BloodHookNest,
- sdk.monsters.BloodWingNest, sdk.monsters.CloudStalkerNest, sdk.monsters.FeederNest, sdk.monsters.SuckerNest
- ];
-
- 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 ((unitA.isSuperUnique) && (unitB.isSuperUnique)) return getDistance(me, unitA) - getDistance(me, unitB);
- if (unitA.isSuperUnique) return -1;
- if (unitB.isSuperUnique) return 1;
-
- // fallens are annoying, put them later if we have line of sight of another monster
- if (unitA.isFallen && unitB.isFallen) return getDistance(me, unitA) - getDistance(me, unitB);
- if (!unitA.isFallen && !checkCollision(me, unitA, (sdk.collision.BlockWall | sdk.collision.LineOfSight | sdk.collision.Ranged))) return -1;
- if (!unitB.isFallen && !checkCollision(me, unitA, (sdk.collision.BlockWall | sdk.collision.LineOfSight | sdk.collision.Ranged))) return 1;
-
- return getDistance(me, unitA) - getDistance(me, unitB);
+ // sort main bosses first
+ if ((unitA.isPrimeEvil) && (unitB.isPrimeEvil)) {
+ return getDistance(me, unitA) - getDistance(me, unitB);
+ }
+ if (unitA.isPrimeEvil) return -1;
+ if (unitB.isPrimeEvil) return 1;
+
+ // Barb optimization
+ if (me.barbarian) {
+ let skillElm = Attack.getSkillElement(Config.AttackSkill[(unitA.isSpecial) ? 1 : 3]);
+ if (!Attack.checkResist(unitA, skillElm)) {
+ return 1;
+ }
+
+ if (!Attack.checkResist(unitB, skillElm)) {
+ 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, sdk.monsters.FoulCrowNest, sdk.monsters.BlackVultureNest,
+ sdk.monsters.BloodHawkNest, sdk.monsters.BloodHookNest, sdk.monsters.BloodWingNest,
+ sdk.monsters.CloudStalkerNest, sdk.monsters.FeederNest, sdk.monsters.SuckerNest
+ ];
+
+ 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 ((unitA.isSuperUnique) && (unitB.isSuperUnique)) {
+ return getDistance(me, unitA) - getDistance(me, unitB);
+ }
+ if (unitA.isSuperUnique) return -1;
+ if (unitB.isSuperUnique) return 1;
+
+ // fallens are annoying, put them later if we have line of sight of another monster
+ if (unitA.isFallen && unitB.isFallen) {
+ return getDistance(me, unitA) - getDistance(me, unitB);
+ }
+ const _coll = (sdk.collision.BlockWall | sdk.collision.LineOfSight | sdk.collision.Ranged);
+ if (!unitA.isFallen && !checkCollision(me, unitA, _coll)) return -1;
+ if (!unitB.isFallen && !checkCollision(me, unitA, _coll)) return 1;
+
+ return getDistance(me, unitA) - getDistance(me, unitB);
};
Attack.pwnDury = function () {
- let duriel = Misc.poll(() => Game.getMonster(sdk.monsters.Duriel));
-
- if (!duriel) return false;
- const tick = getTickCount();
- const gid = duriel.gid;
- const saveSpots = [
- { x: 22648, y: 15688 },
- { x: 22624, y: 15725 },
- ];
-
- // @todo - keep track of last position to attempt relocating dury if we've lost reference
- try {
- Attack.stopClear = true;
- while (!duriel.dead) {
- if (getTickCount() - tick > Time.minutes(10)) {
- break;
- }
- if (!duriel || !copyUnit(duriel).x) {
- duriel = Misc.poll(() => Game.getMonster(-1, -1, gid), 1000, 80);
- if (!duriel || !duriel.attackable) return true;
- }
- //ToDo; figure out static
- if (duriel.getState(sdk.states.Frozen) && duriel.distance < 7 || duriel.distance < 12) {
- let safeSpot = saveSpots.sort((a, b) => getDistance(duriel, b) - getDistance(duriel, a)).first();
- Pather.teleportTo(safeSpot.x, safeSpot.y);
- }
- ClassAttack.doAttack(duriel, true);
- }
- } finally {
- Attack.stopClear = false;
- }
-
- return true;
+ const getDuriel = function () {
+ return Game.getMonster(sdk.monsters.Duriel);
+ };
+ let duriel = Misc.poll(getDuriel);
+
+ if (!duriel) return false;
+ const tick = getTickCount();
+ const gid = duriel.gid;
+ const saveSpots = [
+ { x: 22648, y: 15688 },
+ { x: 22624, y: 15725 },
+ ];
+
+ // @todo - keep track of last position to attempt relocating dury if we've lost reference
+ try {
+ Attack.stopClear = true;
+ while (!duriel.dead) {
+ if (getTickCount() - tick > Time.minutes(10)) {
+ break;
+ }
+ if (!duriel || !copyUnit(duriel).x) {
+ duriel = Misc.poll(getDuriel, 1000, 80);
+ if (!duriel || !duriel.attackable) return true;
+ }
+ //ToDo; figure out static
+ if (duriel.getState(sdk.states.Frozen) && duriel.distance < 7 || duriel.distance < 12) {
+ let safeSpot = saveSpots.sort(function (a, b) {
+ return getDistance(duriel, b) - getDistance(duriel, a);
+ }).first();
+ Pather.teleportTo(safeSpot.x, safeSpot.y);
+ }
+ ClassAttack.doAttack(duriel, true);
+ }
+ } finally {
+ Attack.stopClear = false;
+ }
+
+ return true;
};
Attack.pwnMeph = function () {
- // TODO: fill out
+ // TODO: fill out
};
// Credit @Jaenster - modified by me(theBGuy) for other classes
Attack.pwnDia = function () {
- // Can't farcast if our skill main attack isn't meant for it
- if ((!me.sorceress && !me.necromancer && !me.assassin)
- || (["Poison", "Summon"].includes(SetUp.currentBuild))
- || (Skill.getRange(Config.AttackSkill[1]) < 10)) {
- return false;
- }
-
- const calculateSpots = function (center, skillRange) {
- let coords = [];
- for (let i = 0; i < 360; i++) {
- coords.push({
- x: Math.floor(center.x + skillRange * Math.cos(i) + 0.5),
- y: Math.floor(center.y + skillRange * Math.sin(i) + 0.5),
- });
- }
- // only unique spots
- return coords.filter((e, i, s) => s.indexOf(e) === i).filter(el => Attack.validSpot(el.x, el.y));
- };
-
- const checkMobs = function () {
- let mobs = getUnits(sdk.unittype.Monster).filter(function(el) {
- return !!el && el.attackable && el.classid !== sdk.monsters.Diablo && el.distance < 20;
- });
- return mobs;
- };
-
- const getDiablo = function () {
- let check = checkMobs();
- !!check && Attack.clearList(check);
- return Game.getMonster(sdk.monsters.Diablo);
- };
- {
- let nearSpot = Pather.spotOnDistance({ x: 7792, y: 5292 }, 35, {returnSpotOnError: false});
- Pather.moveToUnit(nearSpot);
- }
-
- let dia = Misc.poll(getDiablo, 15e3, 30);
-
- if (!dia) {
- // Move to Star
- Pather.moveTo(7788, 5292, 3, 30);
- dia = Misc.poll(getDiablo, 15e3, 30);
- }
-
- if (!dia) {
- console.log("No diablo");
- return false;
- }
-
- let tick = getTickCount();
- let lastPosition = { x: 7791, y: 5293 };
- let manaTP, manaSK, manaStatic, rangeStatic;
- let [minDist, maxDist, minRange, maxRange] = (() => {
- // set values
- switch (me.classid) {
- case sdk.player.class.Sorceress:
- [manaTP, manaSK] = [Skill.getManaCost(sdk.skills.Teleport), Skill.getManaCost(Config.AttackSkill[1])];
- [manaStatic, rangeStatic] = [Skill.getManaCost(sdk.skills.StaticField), Skill.getManaCost(sdk.skills.StaticField)];
-
- switch (Config.AttackSkill[1]) {
- case sdk.skills.FrozenOrb:
- return [15, 20, 10, 20];
- case sdk.skills.Lightning:
- return [20, 25, 18, 25];
- default:
- case sdk.skills.Blizzard:
- case sdk.skills.Meteor:
- return [40, 45, 15, 58];
- }
- case sdk.player.class.Necromancer:
- return [35, 40, 15, 50];
- case sdk.player.class.Assassin:
- return [25, 30, 15, 30];
- default:
- return [15, 20, 10, 20];
- }
- })();
-
- const shouldWalk = function (spot) {
- if (!Pather.canTeleport()) return true;
- return (spot.distance < 10 || me.gold < 10000 || me.mpPercent < 50);
- };
-
- Attack.stopClear = true;
-
- do {
- // give up in 10 minutes
- if (getTickCount() - tick > Time.minutes(10)) {
- break;
- }
-
- while ((dia = getDiablo())) {
- if (dia.dead) {
- me.overhead("Diablo's dead");
- break;
- }
-
- if (getDistance(me, dia) < minDist || getDistance(me, dia) > maxDist || getTickCount() - tick > 25e3) {
- let spot = calculateSpots(dia, ((minRange + maxRange) / 2))
- .filter((loc) => getDistance(me, loc) > minRange && getDistance(me, loc) < maxRange /*todo, in neighbour room*/)
- .filter(function (loc) {
- let collision = getCollision(me.area, loc.x, loc.y);
- // noinspection JSBitwiseOperatorUsage
- let isLava = !!(collision & Coords_1.BlockBits.IsOnFloor);
- // this spot is on lava, fuck this
- if (isLava) return false;
- // noinspection JSBitwiseOperatorUsage
- return !(collision & (Coords_1.BlockBits.BlockWall));
- })
- .sort((a, b) => getDistance(me, a) - getDistance(me, b))
- .first();
- tick = getTickCount();
- if (spot !== undefined) {
- shouldWalk(spot) ? Pather.walkTo(spot.x, spot.y) : Pather.moveTo(spot.x, spot.y, 15, false);
- }
- }
-
- if (me.sorceress && me.mp < manaSK + manaTP) {
- me.overhead("Dont attack, save mana for teleport");
- delay(10);
- continue;
- }
-
- if (me.necromancer || me.assassin) {
- me.overhead("FarCasting: Diablo's health " + dia.hpPercent + " % left");
- ClassAttack.farCast(dia);
- } else {
- // If we got enough mana to teleport close to diablo, static the bitch, and jump back
- let diabloMissiles = getUnits(sdk.unittype.Missile).filter(function (unit) {
- let _a;
- return ((_a = unit.getParent()) === null || _a === void 0 ? void 0 : _a.gid) === dia.gid;
- });
- console.log("Diablo missiles: " + diabloMissiles.length);
- console.log("Diablo mode:" + dia.mode);
- me.overhead("Dia life " + (~~(dia.hp / 128 * 100)).toString() + "%");
- if (me.mp > manaStatic + manaTP + manaTP && diabloMissiles.length < 3 && !dia.attacking && dia.hpPercent > Config.CastStatic) {
- let [x, y] = me;
- ClassAttack.switchCurse(dia, true); // curse him if we can
- // re-check his mode
- if (!dia.attacking) {
- // Find a spot close to Diablo
- let spot = Pather.spotOnDistance(dia, rangeStatic * (2 / 3), { returnSpotOnError: false });
- Pather.moveToEx(spot.x, spot.y, { allowClearing: false });
- Skill.cast(sdk.skills.StaticField);
- // move back to previous spot
- Pather.moveToEx(x, y, { allowClearing: false });
- }
- }
- Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, dia);
-
- if (!!dia && !checkCollision(me, dia, Coords_1.Collision.BLOCK_MISSILE) && Skill.getRange(Config.AttackSkill[2]) > 15) {
- Skill.cast(Config.AttackSkill[2], sdk.skills.hand.Right, dia);
- }
- }
- }
-
- if (dia && dia.dead) {
- break;
- }
-
- if (!dia) {
- let path = getPath(me.area, me.x, me.y, lastPosition.x, lastPosition.y, 1, 5);
-
- // failed to make a path from me to the old spot
- if (!path) {
- break;
- }
-
- // walk close to old node, if we dont find dia continue
- if (!path.some(function (node) {
- Pather.walkTo(node.x, node.y);
- return getDiablo();
- })) {
- break;
- }
- }
- } while (true);
-
- !!dia ? Pather.moveTo(dia) : Pather.moveTo(7774, 5305);
- Pickit.pickItems();
- Pather.moveTo(7792, 5291); // Move back to star
- Pickit.pickItems();
- Attack.stopClear = false;
-
- return dia;
+ // Can't farcast if our skill main attack isn't meant for it
+ if ((!me.sorceress && !me.necromancer && !me.assassin)
+ || (["Poison", "Summon"].includes(SetUp.currentBuild))
+ || (Skill.getRange(Config.AttackSkill[1]) < 10)) {
+ return false;
+ }
+
+ const calculateSpots = function (center, skillRange) {
+ let coords = [];
+ for (let i = 0; i < 360; i++) {
+ coords.push({
+ x: Math.floor(center.x + skillRange * Math.cos(i) + 0.5),
+ y: Math.floor(center.y + skillRange * Math.sin(i) + 0.5),
+ });
+ }
+ // only unique spots
+ return coords.filter(function (e, i, s) {
+ return s.indexOf(e) === i;
+ }).filter(function (el) {
+ return Attack.validSpot(el.x, el.y);
+ });
+ };
+
+ const checkMobs = function () {
+ let mobs = getUnits(sdk.unittype.Monster).filter(function (el) {
+ return !!el && el.attackable && el.classid !== sdk.monsters.Diablo && el.distance < 20;
+ });
+ return mobs;
+ };
+
+ const getDiablo = function () {
+ let check = checkMobs();
+ !!check && Attack.clearList(check);
+ return Game.getMonster(sdk.monsters.Diablo);
+ };
+ {
+ let nearSpot = Pather.spotOnDistance({ x: 7792, y: 5292 }, 35, { returnSpotOnError: false });
+ Pather.moveToUnit(nearSpot);
+ }
+
+ let dia = Misc.poll(getDiablo, 15e3, 30);
+
+ if (!dia) {
+ // Move to Star
+ Pather.moveTo(7788, 5292, 3, 30);
+ dia = Misc.poll(getDiablo, 15e3, 30);
+ }
+
+ if (!dia) {
+ console.log("No diablo");
+ return false;
+ }
+
+ let tick = getTickCount();
+ let lastPosition = { x: 7791, y: 5293 };
+ let manaTP, manaSK, manaStatic, rangeStatic;
+ let [minDist, maxDist, minRange, maxRange] = (() => {
+ // set values
+ switch (me.classid) {
+ case sdk.player.class.Sorceress:
+ [manaTP, manaSK] = [Skill.getManaCost(sdk.skills.Teleport), Skill.getManaCost(Config.AttackSkill[1])];
+ [manaStatic, rangeStatic] = [Skill.getManaCost(sdk.skills.StaticField), Skill.getRange(sdk.skills.StaticField)];
+
+ switch (Config.AttackSkill[1]) {
+ case sdk.skills.FrozenOrb:
+ return [15, 20, 10, 20];
+ case sdk.skills.Lightning:
+ return [20, 25, 18, 25];
+ default:
+ case sdk.skills.Blizzard:
+ case sdk.skills.Meteor:
+ return [40, 45, 15, 58];
+ }
+ case sdk.player.class.Necromancer:
+ return [35, 40, 15, 50];
+ case sdk.player.class.Assassin:
+ return [25, 30, 15, 30];
+ default:
+ return [15, 20, 10, 20];
+ }
+ })();
+
+ const shouldWalk = function (spot) {
+ if (!Pather.canTeleport()) return true;
+ return (spot.distance < 10 || me.gold < 10000 || me.mpPercent < 50);
+ };
+
+ Attack.stopClear = true;
+
+ do {
+ // give up in 10 minutes
+ if (getTickCount() - tick > Time.minutes(10)) {
+ break;
+ }
+
+ while ((dia = getDiablo())) {
+ if (dia.dead) {
+ me.overhead("Diablo's dead");
+ break;
+ }
+
+ if (getDistance(me, dia) < minDist || getDistance(me, dia) > maxDist || getTickCount() - tick > 25e3) {
+ let spot = calculateSpots(dia, ((minRange + maxRange) / 2))
+ .filter(function (loc) {
+ return getDistance(me, loc) > minRange && getDistance(me, loc) < maxRange; /*todo, in neighbour room*/
+ })
+ .filter(function (loc) {
+ let collision = getCollision(me.area, loc.x, loc.y);
+ // noinspection JSBitwiseOperatorUsage
+ let isLava = !!(collision & Coords_1.BlockBits.IsOnFloor);
+ // this spot is on lava, fuck this
+ if (isLava) return false;
+ // noinspection JSBitwiseOperatorUsage
+ return !(collision & (Coords_1.BlockBits.BlockWall));
+ })
+ .sort(function (a, b) {
+ return getDistance(me, a) - getDistance(me, b);
+ })
+ .first();
+ tick = getTickCount();
+ if (spot !== undefined) {
+ shouldWalk(spot)
+ ? Pather.walkTo(spot.x, spot.y)
+ : Pather.move(spot, { allowClearing: false, retry: 15 });
+ }
+ }
+
+ if (me.sorceress && me.mp < manaSK + manaTP) {
+ me.overhead("Dont attack, save mana for teleport");
+ delay(10);
+ continue;
+ }
+
+ if (me.necromancer || me.assassin) {
+ me.overhead("FarCasting: Diablo's health " + dia.hpPercent + " % left");
+ ClassAttack.farCast(dia);
+ } else {
+ // If we got enough mana to teleport close to diablo, static the bitch, and jump back
+ let diabloMissiles = getUnits(sdk.unittype.Missile).filter(function (unit) {
+ let _a;
+ return ((_a = unit.getParent()) === null || _a === void 0 ? void 0 : _a.gid) === dia.gid;
+ });
+ console.log("Diablo missiles: " + diabloMissiles.length);
+ console.log("Diablo mode:" + dia.mode);
+ me.overhead("Dia life " + (~~(dia.hp / 128 * 100)).toString() + "%");
+ if (me.mp > manaStatic + manaTP + manaTP
+ && diabloMissiles.length < 3 && !dia.attacking
+ && dia.hpPercent > Config.CastStatic) {
+ let [x, y] = me;
+ ClassAttack.switchCurse(dia, true); // curse him if we can
+ // re-check his mode
+ if (!dia.attacking) {
+ // Find a spot close to Diablo
+ let spot = Pather.spotOnDistance(dia, rangeStatic * (2 / 3), { returnSpotOnError: false });
+ Pather.moveToEx(spot.x, spot.y, { allowClearing: false });
+ Skill.cast(sdk.skills.StaticField);
+ // move back to previous spot
+ Pather.moveToEx(x, y, { allowClearing: false });
+ }
+ }
+ Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, dia);
+
+ if (!!dia && !checkCollision(me, dia, Coords_1.Collision.BLOCK_MISSILE)
+ && Skill.getRange(Config.AttackSkill[2]) > 15) {
+ Skill.cast(Config.AttackSkill[2], sdk.skills.hand.Right, dia);
+ }
+ }
+ }
+
+ if (dia && dia.dead) {
+ break;
+ }
+
+ if (!dia) {
+ let path = getPath(me.area, me.x, me.y, lastPosition.x, lastPosition.y, 1, 5);
+
+ // failed to make a path from me to the old spot
+ if (!path) {
+ break;
+ }
+
+ // walk close to old node, if we dont find dia continue
+ if (!path.some(function (node) {
+ Pather.walkTo(node.x, node.y);
+ return getDiablo();
+ })) {
+ break;
+ }
+ }
+ } while (true);
+
+ !!dia ? Pather.moveTo(dia) : Pather.moveTo(7774, 5305);
+ Pickit.pickItems();
+ Pather.moveTo(7792, 5291); // Move back to star
+ Pickit.pickItems();
+ Attack.stopClear = false;
+
+ return dia;
};
Attack.pwnAncients = function () {
- // @todo fillout
+ // @todo fillout
};
+/**
+ * @param {Monster} unit
+ * @param {number} distance
+ * @param {number} spread
+ * @param {number} range
+ * @returns {boolean}
+ */
Attack.deploy = function (unit, distance = 10, spread = 5, range = 9) {
- if (arguments.length < 4) throw new Error("deploy: Not enough arguments supplied");
-
- let index, currCount;
- let count = 999;
- let monList = (this.buildMonsterList() || []).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(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)) {
- currCount = this.getMonsterCount(grid[i].x, grid[i].y, range, monList);
-
- if (currCount < count) {
- index = i;
- count = currCount;
- }
-
- if (currCount === 0) {
- break;
- }
- }
- }
-
- return typeof index === "number" ? Pather.moveTo(grid[index].x, grid[index].y, 0) : false;
+ !unit && (unit = me);
+ let index, currCount;
+ let count = 999;
+ let monList = (this.buildMonsterList() || []).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(function (a, b) {
+ return getDistance(b.x, b.y, unit.x, unit.y) - getDistance(a.x, a.y, unit.x, unit.y);
+ });
+
+ let lines = new WeakMap();
+ for (let { x, y } of grid) {
+ lines.set(new Line(x + 1, y + 1, x, y, 0x62, true));
+ }
+
+ 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)) {
+ currCount = this.getMonsterCount(grid[i].x, grid[i].y, range, monList);
+
+ if (currCount < count) {
+ index = i;
+ count = currCount;
+ }
+
+ if (currCount === 0) {
+ break;
+ }
+ }
+ }
+
+ return typeof index === "number"
+ ? Pather.move(grid[index], { allowNodeActions: false })
+ : false;
};
+/**
+ * Attempt to find non blocked position to attack from
+ * @param {Monster} unit
+ * @param {number} distance
+ * @param {number} coll
+ * @param {boolean} walk
+ * @param {boolean} force
+ * @returns {boolean}
+ * @todo maybe recursion check?
+ */
Attack.getIntoPosition = function (unit = false, distance = 0, coll = 0, walk = false, force = false) {
- if (!unit || !unit.x || !unit.y) return false;
- Developer.debugging.pathing && console.time("getIntoPosition");
- const useTele = Pather.useTeleport();
- walk === true && (walk = 1);
-
- if (distance < 4 && (!unit.hasOwnProperty("mode") || !unit.dead)) {
- // we are actually able to walk to where we want to go, hopefully prevent wall hugging
- if (walk && (unit.distance < 8 || !CollMap.checkColl(me, unit, sdk.collision.WallOrRanged | sdk.collision.Objects | sdk.collision.IsOnFloor))) {
- Pather.walkTo(unit.x, unit.y, 3);
- } else if (walk && (unit.distance < 4 && CollMap.checkColl(me, unit, sdk.collision.MonsterIsOnFloorDarkArea))) {
- console.debug("Are we in a doorway?");
- return true;
- } else {
- // don't clear while trying to reposition
- Pather.moveToEx(unit.x, unit.y, { clearSettings: { allowClearing: !useTele, range: useTele ? 10 : 5 } });
- }
-
- return !CollMap.checkColl(me, unit, coll);
- }
-
- let cx, cy, currCount, count = 999, potentialSpot = { x: undefined, y: undefined };
- let coords = [];
- let fullDistance = distance;
- const name = unit.hasOwnProperty("name") ? unit.name : "";
- const angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI);
- const angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 135, -135, 180];
-
- let caster = (force && !me.inTown);
-
- //let t = getTickCount();
-
- for (let n = 0; n < 3; n += 1) {
- const nearMobs = getUnits(sdk.unittype.Monster).filter(m => m.getStat(sdk.stats.Alignment) !== 2);
- (n > 0) && (distance -= Math.floor(fullDistance / 3 - 1));
-
- for (let i = 0; i < angles.length; i += 1) {
- cx = Math.round((Math.cos((angle + angles[i]) * Math.PI / 180)) * distance + unit.x);
- cy = Math.round((Math.sin((angle + angles[i]) * Math.PI / 180)) * distance + unit.y);
- (Pather.checkSpot(cx, cy, sdk.collision.BlockWall, false)) && coords.push({ x: cx, y: cy });
- }
-
- if (coords.length > 0) {
- coords.sort(Sort.units);
-
- // If one of the valid positions is a position I am at already - and we aren't trying to force a new spot
- if (!force) {
- for (let i = 0; i < coords.length; i += 1) {
- if ((getDistance(me, coords[i].x, coords[i].y) < 1
- && !CollMap.checkColl(unit, {x: coords[i].x, y: coords[i].y}, sdk.collision.WallOrRanged | sdk.collision.Objects | sdk.collision.IsOnFloor, 1))
- || (getDistance(me, coords[i].x, coords[i].y) <= 5 && me.getMobCount(6) > 2)) {
- return true;
- }
- }
- }
-
- for (let i = 0; i < coords.length; i += 1) {
- // Valid position found - no collision between the spot and the unit
- if (!CollMap.checkColl({ x: coords[i].x, y: coords[i].y }, unit, coll, 1)) {
- // currCount = coords[i].mobCount({ range: 7 });
- Developer.debugging.pathing && console.time("countMobs");
- currCount = nearMobs.filter(m => getDistance(coords[i].x, coords[i].y, m.x, m.y) < 8).length;
- Developer.debugging.pathing && console.timeEnd("countMobs");
-
- // this might be a valid spot but also check the mob count at that node
- if (caster) {
- potentialSpot.x === undefined && (potentialSpot = { x: coords[i].x, y: coords[i].y });
-
- if (currCount < count) {
- count = currCount;
- potentialSpot = { x: coords[i].x, y: coords[i].y };
- Developer.debugging.pathing && console.log(sdk.colors.Blue + "CheckedSpot" + sdk.colors.Yellow + ": x: " + coords[i].x + " y: " + coords[i].y + " mob amount: " + sdk.colors.NeonGreen + count);
- }
-
- if (currCount !== 0) {
- Developer.debugging.pathing && console.log(sdk.colors.Red + "Not Zero, check next: currCount: " + sdk.colors.NeonGreen + " " + currCount);
- continue;
- }
- }
-
- // I am already in my optimal position
- if (coords[i].distance < 3) return true;
-
- // we are actually able to walk to where we want to go, hopefully prevent wall hugging
- if (walk && (coords[i].distance < 6 || !CollMap.checkColl(me, unit, sdk.collision.WallOrRanged | sdk.collision.Objects | sdk.collision.IsOnFloor))) {
- Pather.walkTo(coords[i].x, coords[i].y, 2);
- } else {
- Pather.moveToEx(coords[i].x, coords[i].y, { clearSettings: { allowClearing: !useTele, range: useTele ? 10 : 5, retry: 3 } });
- }
-
- Developer.debugging.pathing && console.log(sdk.colors.Purple + "SecondCheck :: " + sdk.colors.Yellow + "Moving to: x: " + coords[i].x + " y: " + coords[i].y + " mob amount: " + sdk.colors.NeonGreen + currCount);
- Developer.debugging.pathing && console.timeEnd("getIntoPosition");
- return true;
- }
- }
- }
- }
-
- if (caster && potentialSpot.x !== undefined) {
- if (potentialSpot.distance < 3) return true;
- if ((() => {
- if (Pather.useTeleport() && Pather.teleportTo(potentialSpot.x, potentialSpot.y)) {
- return true;
- }
- switch (walk) {
- case 1:
- return Pather.walkTo(potentialSpot.x, potentialSpot.y, 2);
- case 2:
- default:
- if (potentialSpot.distance < 6 && !CollMap.checkColl(me, potentialSpot, sdk.collision.WallOrRanged)) {
- return Pather.walkTo(potentialSpot.x, potentialSpot.y, 2);
- }
- return Pather.moveTo(potentialSpot.x, potentialSpot.y, 1);
- }
- })()) {
- Developer.debugging.pathing && console.log(sdk.colors.Orange + "DefaultCheck :: " + sdk.colors.Yellow + "Moving to: x: " + potentialSpot.x + " y: " + potentialSpot.y + " mob amount: " + sdk.colors.NeonGreen + count);
- Developer.debugging.pathing && console.timeEnd("getIntoPosition");
- return true;
- }
- }
-
- console.warn("ÿc4Attackÿc0: Failed to get into valid position" + (name ? " for: " + name : ""));
-
- return false;
+ if (!unit || !unit.x || !unit.y) return false;
+ Developer.debugging.pathing && console.time("getIntoPosition");
+ const useTele = Pather.useTeleport();
+ const name = unit.hasOwnProperty("name") ? unit.name : "";
+ const angle = Math.round(Math.atan2(me.y - unit.y, me.x - unit.x) * 180 / Math.PI);
+ const angles = [0, 15, -15, 30, -30, 45, -45, 60, -60, 75, -75, 90, -90, 135, -135, 180];
+ const caster = (force || (distance > 4 && !me.inTown && Skill.getRange(Config.AttackSkill[1]) > 8));
+ const minMonCount = caster && distance < 8 ? 1 : 0;
+ const _coll = (sdk.collision.WallOrRanged | sdk.collision.Objects | sdk.collision.IsOnFloor);
+ const _pathSettings = { clearSettings: { allowClearing: !useTele, range: useTele ? 10 : 5, retry: 5 } };
+
+ walk === true && (walk = 1);
+
+ if (distance < 4 && (!unit.hasOwnProperty("mode") || !unit.dead)) {
+ /**
+ * if we are surrounded by monsters it can be near impossible to get into position
+ * what would be good is if we are surrounded either pick an AoE skill and cast or
+ * just attack whatever monster is the nearest to us, this would also be a good place
+ * for necro's use of terror and barbs use of howl/leap/leapAttack/whirlwind
+ */
+ // we are actually able to walk to where we want to go, hopefully prevent wall hugging
+ if (walk && (unit.distance < 8 || !CollMap.checkColl(me, unit, _coll))) {
+ Pather.walkTo(unit.x, unit.y, 3);
+ } else if (walk && (unit.distance < 4 && CollMap.checkColl(me, unit, sdk.collision.MonsterIsOnFloorDarkArea))) {
+ console.debug("Are we in a doorway?");
+ return true;
+ } else {
+ // don't clear while trying to reposition
+ Pather.move(unit, _pathSettings);
+ }
+
+ return !CollMap.checkColl(me, unit, coll);
+ }
+
+ let count = 999;
+ let potentialSpot = { x: null, y: null };
+ let fullDistance = distance;
+
+ const coords = [];
+ const nearMobs = getUnits(sdk.unittype.Monster)
+ .filter(function (m) {
+ return m.getStat(sdk.stats.Alignment) !== 2;
+ });
+ for (let n = 0; n < 3; n += 1) {
+ const temp = [];
+ (n > 0) && (distance -= Math.floor(fullDistance / 3 - 1));
+
+ for (let currAngle of angles) {
+ const _angle = ((angle + currAngle) * Math.PI / 180);
+ let cx = Math.round((Math.cos(_angle)) * distance + unit.x);
+ let cy = Math.round((Math.sin(_angle)) * distance + unit.y);
+
+ // ignore this spot as it's too close to our current position when we are forcing a new location
+ if (force && [cx, cy].distance < distance) continue;
+ if (Pather.checkSpot(cx, cy, sdk.collision.BlockWall, false)) {
+ coords.push({ x: cx, y: cy });
+ temp.push({ x: cx, y: cy });
+ }
+ }
+ if (!temp.length) continue;
+
+ coords.sort(Sort.units);
+
+ // If one of the valid positions is a position I am at already - and we aren't trying to force a new spot
+ if (!force) {
+ for (let coord of temp) {
+ if ((getDistance(me, coord.x, coord.y) < 1
+ && !CollMap.checkColl(unit, { x: coord.x, y: coord.y }, _coll, 1))
+ || (getDistance(me, coord.x, coord.y) <= 5 && me.getMobCount(6) > 2)) {
+ return true;
+ }
+ }
+ }
+ }
+ for (let coord of coords) {
+ // Valid position found - no collision between the spot and the unit
+ if (!CollMap.checkColl({ x: coord.x, y: coord.y }, unit, coll, 1)) {
+ const currCount = nearMobs
+ .filter(function (m) {
+ return getDistance(coord.x, coord.y, m.x, m.y) < 8;
+ }).length;
+
+ // this might be a valid spot but also check the mob count at that node
+ if (caster) {
+ potentialSpot.x === null && (potentialSpot = { x: coord.x, y: coord.y });
+
+ if (currCount < count) {
+ count = currCount;
+ potentialSpot = { x: coord.x, y: coord.y };
+ }
+
+ if (currCount > minMonCount) {
+ continue;
+ }
+ }
+
+ // I am already in my optimal position
+ if (coord.distance < 3) return true;
+
+ // we are actually able to walk to where we want to go, hopefully prevent wall hugging
+ if (walk && (coord.distance < 6 || !CollMap.checkColl(me, unit, _coll))) {
+ Pather.walkTo(coord.x, coord.y, 2);
+ } else {
+ Pather.move(coord, _pathSettings);
+ }
+ if (Developer.debugging.pathing) {
+ console.log(
+ sdk.colors.Purple + "SecondCheck :: " + sdk.colors.Yellow
+ + "Moving to: x: " + coord.x + " y: " + coord.y
+ + " mob amount: " + sdk.colors.NeonGreen + currCount
+ );
+ console.timeEnd("getIntoPosition");
+ }
+ return true;
+ }
+ }
+
+ if (caster && potentialSpot.x !== null) {
+ if (potentialSpot.distance < 3) return true;
+ if ((function () {
+ if (Pather.useTeleport() && Pather.teleportTo(potentialSpot.x, potentialSpot.y)) {
+ return true;
+ }
+ switch (walk) {
+ case 1:
+ return Pather.walkTo(potentialSpot.x, potentialSpot.y, 2);
+ case 2:
+ default:
+ if (potentialSpot.distance < 6 && !CollMap.checkColl(me, potentialSpot, sdk.collision.WallOrRanged)) {
+ return Pather.walkTo(potentialSpot.x, potentialSpot.y, 2);
+ }
+ // return Pather.moveTo(potentialSpot.x, potentialSpot.y, 1);
+ return Pather.move(potentialSpot, _pathSettings);
+ }
+ })()) {
+ if (Developer.debugging.pathing) {
+ console.log(
+ sdk.colors.Orange + "DefaultCheck :: " + sdk.colors.Yellow
+ + "Moving to: x: " + potentialSpot.x + " y: " + potentialSpot.y
+ + " mob amount: " + sdk.colors.NeonGreen + count
+ );
+ console.timeEnd("getIntoPosition");
+ }
+ return true;
+ }
+ }
+
+ console.warn("ÿc4Attackÿc0: Failed to get into valid position" + (name ? " for: " + name : ""));
+
+ return false;
};
Attack.castableSpot = function (x = undefined, y = undefined) {
- // Just in case
- if (!me.area || !x || !y) return false;
-
- let result;
-
- try { // Treat thrown errors as invalid spot
- result = getCollision(me.area, x, y);
- } catch (e) {
- return false;
- }
-
- return !(result === undefined || !!(result & Coords_1.BlockBits.Casting) || !!(result & Coords_1.Collision.BLOCK_MISSILE) || (result & sdk.collision.Objects) || (result & sdk.collision.BlockWall));
+ // Just in case
+ if (!me.area || !x || !y) return false;
+
+ let result;
+
+ try { // Treat thrown errors as invalid spot
+ result = getCollision(me.area, x, y);
+ } catch (e) {
+ return false;
+ }
+
+ return !(result === undefined
+ || !!(result & Coords_1.BlockBits.Casting)
+ || !!(result & Coords_1.Collision.BLOCK_MISSILE)
+ || (result & sdk.collision.Objects)
+ || (result & sdk.collision.BlockWall));
};
diff --git a/libs/SoloPlay/Functions/AutoBuild.js b/libs/SoloPlay/Functions/AutoBuild.js
new file mode 100644
index 00000000..1168942d
--- /dev/null
+++ b/libs/SoloPlay/Functions/AutoBuild.js
@@ -0,0 +1,92 @@
+/**
+* @filename AutoBuild.js
+* @author theBGuy
+* @credit alogwe - orignal author
+* @desc modified AutoBuild for easier use with Kolbot-SoloPlay
+*
+*/
+js_strict(true);
+
+const AutoBuild = new function AutoBuild () {
+ Config.AutoBuild.DebugMode && (Config.AutoBuild.Verbose = true);
+ const debug = !!Config.AutoBuild.DebugMode;
+ const verbose = !!Config.AutoBuild.Verbose;
+
+ const log = function (message) {
+ FileTools.appendText(getLogFilename(), message + "\n");
+ };
+ const getCurrentScript = function () {
+ return getScript(true).name.toLowerCase();
+ };
+
+ const buildTemplate = me.currentBuild.AutoBuildTemplate;
+ let configUpdateLevel = 0;
+ let lastSuccessfulUpdateLevel = 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 () {
+ let cLvl = me.charlvl;
+ debug && this.print("Updating Config from level " + configUpdateLevel + " to " + cLvl);
+ let reapply = true;
+
+ while (configUpdateLevel < cLvl) {
+ configUpdateLevel += 1;
+ Skill.init();
+ if (buildTemplate[configUpdateLevel] !== undefined) {
+ buildTemplate[configUpdateLevel].Update.apply(Config);
+ lastSuccessfulUpdateLevel = configUpdateLevel;
+ } else if (reapply) {
+ // re-apply from the last successful update - this is helpful if inside the build file there are conditional statements
+ buildTemplate[lastSuccessfulUpdateLevel].Update.apply(Config);
+ reapply = false;
+ }
+ }
+ }
+
+ function getLogFilename () {
+ let d = new Date();
+ let dateString = d.getMonth() + "_" + d.getDate() + "_" + d.getFullYear();
+ return "logs/AutoBuild." + me.realm + "." + me.charname + "." + dateString + ".log";
+ }
+
+ function initialize () {
+ let currentScript = getCurrentScript();
+ this.print("Including build template " + SetUp._buildTemplate + " into " + currentScript);
+
+ if (!buildTemplate) throw new Error("Failed to include template: " + SetUp._buildTemplate);
+
+ // All threads except soloplay.js use this event listener
+ // to update their thread-local Config object
+ if (currentScript !== "libs\\soloplay\\soloplay.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();
+ }
+ }
+
+ // Only print to console from autobuildthread.js,
+ // but log from all scripts
+ function myPrint () {
+ if (!debug && !verbose) return;
+ 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.levelUpHandler = levelUpHandler;
+ this.print = myPrint;
+ this.initialize = initialize;
+ this.applyConfigUpdates = applyConfigUpdates;
+};
diff --git a/libs/SoloPlay/Functions/AutoBuildOverrides.js b/libs/SoloPlay/Functions/AutoBuildOverrides.js
deleted file mode 100644
index 7aa3d0b4..00000000
--- a/libs/SoloPlay/Functions/AutoBuildOverrides.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
-* @filename AutoBuildOverrides.js
-* @author theBGuy
-* @credit alogwe - orignal author
-* @desc modified AutoBuild for easier use with Kolbot-SoloPlay
-*
-*/
-js_strict(true);
-
-includeIfNotIncluded("SoloPlay/Functions/Globals.js");
-includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
-includeIfNotIncluded("SoloPlay/Functions/CubingOverrides.js");
-includeIfNotIncluded("SoloPlay/Functions/PrototypeOverrides.js");
-includeIfNotIncluded("SoloPlay/Functions/RunewordsOverrides.js");
-
-const AutoBuild = new function AutoBuild () {
- Config.AutoBuild.DebugMode && (Config.AutoBuild.Verbose = true);
- const debug = !!Config.AutoBuild.DebugMode;
- const verbose = !!Config.AutoBuild.Verbose;
- let currAutoBuild;
- let configUpdateLevel = 0, lastSuccessfulUpdateLevel = 0;
-
- const log = (message) => FileTools.appendText(getLogFilename(), message + "\n");
- const getCurrentScript = () => getScript(true).name.toLowerCase();
-
- // 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 () {
- let cLvl = me.charlvl;
- debug && this.print("Updating Config from level " + configUpdateLevel + " to " + cLvl);
- let reapply = true;
-
- while (configUpdateLevel < cLvl) {
- configUpdateLevel += 1;
- Skill.init();
- if (currAutoBuild[configUpdateLevel] !== undefined) {
- currAutoBuild[configUpdateLevel].Update.apply(Config);
- lastSuccessfulUpdateLevel = configUpdateLevel;
- } else if (reapply) {
- // re-apply from the last successful update - this is helpful if inside the build file there are conditional statements
- currAutoBuild[lastSuccessfulUpdateLevel].Update.apply(Config);
- reapply = false;
- }
- }
- }
-
- function getBuildType () {
- let build = CharInfo.getActiveBuild();
- 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 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 className = sdk.player.class.nameOf(me.classid);
- let build = getBuildType();
- let template = "SoloPlay/BuildFiles/" + className + "/" + className + "." + build + "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);
- if (["Start", "Stepping", "Leveling"].includes(CharInfo.getActiveBuild())) {
- currAutoBuild = build.AutoBuildTemplate;
- } else {
- currAutoBuild = finalBuild.AutoBuildTemplate;
- }
-
- // Only load() helper thread from default.dbj if it isn't loaded
- if (currentScript === "libs\\soloplay\\soloplay.js" && !getScript("libs\\SoloPlay\\Threads\\AutoBuildThread.js")) {
- load("libs/SoloPlay/Threads/AutoBuildThread.js");
- delay(500);
- }
-
- // All threads except autobuildthread.js use this event listener
- // to update their thread-local Config object
- if (currentScript !== "libs\\SoloPlay\\Threads\\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();
- }
- }
-
- // 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/libs/SoloPlay/Functions/AutoMuleOverrides.js b/libs/SoloPlay/Functions/AutoMuleOverrides.js
index a8218bdc..6f1a2333 100644
--- a/libs/SoloPlay/Functions/AutoMuleOverrides.js
+++ b/libs/SoloPlay/Functions/AutoMuleOverrides.js
@@ -10,62 +10,117 @@
* essentially a basic form of item sharing
*/
-includeIfNotIncluded("Automule.js");
+includeIfNotIncluded("systems/automule/Automule.js");
+
+/**
+* @param {ItemUnit} item
+* @param {string[] | number[]} list
+* @returns {boolean}
+*/
+AutoMule.matchItem = function (item, list) {
+ let parsedPickit = new NTIPList();
+ let 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);
+ if (parsedLine) {
+ parsedPickit.add(parsedLine, info);
+ }
+ }
+ }
+
+ return (classIDs.includes(item.classid) || NTIP.CheckItem(item, parsedPickit));
+};
AutoMule.getMuleItems = function () {
- let info = this.getInfo();
+ // can't mule on single player
+ if (!me.gameserverip) return [];
+ let info = this.getInfo();
+
+ if (!info || !info.hasOwnProperty("muleInfo")) {
+ return false;
+ }
+
+ const muleOrphans = !!(info.muleInfo.hasOwnProperty("muleOrphans") && info.muleInfo.muleOrphans);
+
+ /**
+ * @param {ItemUnit} item
+ */
+ const questItem = function (item) {
+ return [
+ sdk.items.quest.KeytotheCairnStones, sdk.items.quest.ScrollofInifuss,
+ sdk.items.quest.HoradricMalus, sdk.items.quest.WirtsLeg,
+ sdk.items.quest.HoradricStaff, sdk.items.quest.ShaftoftheHoradricStaff,
+ sdk.items.quest.ViperAmulet, sdk.items.quest.Cube,
+ sdk.items.quest.KhalimsBrain, sdk.items.quest.KhalimsEye,
+ sdk.items.quest.KhalimsHeart, sdk.items.quest.KhalimsFlail,
+ sdk.items.quest.DecoyGidbinn, sdk.items.quest.TheGidbinn,
+ sdk.items.quest.KhalimsWill, sdk.items.quest.PotofLife,
+ sdk.items.quest.MephistosSoulstone, sdk.items.quest.HellForgeHammer,
+ sdk.items.quest.MalahsPotion, sdk.items.quest.ScrollofResistance,
+ ].includes(item.classid);
+ };
- if (!info || !info.hasOwnProperty("muleInfo")) {
- return false;
- }
-
- const muleOrphans = !!(info.muleInfo.hasOwnProperty("muleOrphans") && info.muleInfo.muleOrphans);
-
- /**
- * @param {ItemUnit} item
- */
- const questItem = (item) => [
- sdk.items.quest.KeytotheCairnStones, sdk.items.quest.ScrollofInifuss, sdk.items.quest.HoradricMalus, sdk.items.quest.WirtsLeg,
- sdk.items.quest.HoradricStaff, sdk.items.quest.ShaftoftheHoradricStaff, sdk.items.quest.ViperAmulet, sdk.items.quest.Cube,
- sdk.items.quest.KhalimsBrain, sdk.items.quest.KhalimsEye, sdk.items.quest.KhalimsHeart, sdk.items.quest.KhalimsFlail,
- sdk.items.quest.DecoyGidbinn, sdk.items.quest.TheGidbinn, sdk.items.quest.KhalimsWill, sdk.items.quest.PotofLife,
- sdk.items.quest.MephistosSoulstone, sdk.items.quest.HellForgeHammer,
- sdk.items.quest.MalahsPotion, sdk.items.quest.ScrollofResistance,
- ].includes(item.classid);
+ /**
+ * @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)
+ || SoloWants.keepItem(item));
+ };
- /**
- * @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) || SoloWants.keepItem(item));
+ const checkTorchSystem = TorchSystem.getFarmers() && TorchSystem.isFarmer();
- // lets be more explicit about what we want to mule
- let items = me.getItemsEx()
- .filter(function (item) {
- // we don't mule items that are equipped or are junk
- if (!item.isInStorage || Town.ignoredItemTypes.includes(item.itemType)) return false;
- // don't mule quest items
- if (questItem(item)) return false;
- // don't mule wanted autoequip items
- if (AutoEquip.wanted(item)) return false;
- // don't mule items in locked spots - not exactly applicable for soloplay but including it
- 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
- // might be worth it to ignore force for soloplay in this case, muleing an item we need would slow down progression
- if (isWanted(item) && !AutoMule.matchItem(item, Config.AutoMule.Force.concat(Config.AutoMule.Trigger))) return false;
- // don't mule keys if part of torchsystem, again shouldn't really be used with soloplay but still including it
- 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
- return (Pickit.checkItem(item).result > 0 && NTIP.CheckItem(item, NTIP_CheckListNoTier, true).result === 1) || (item.isInStash && muleOrphans);
- });
+ // lets be more explicit about what we want to mule
+ 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 quest items
+ if (questItem(item)) return false;
+ // don't mule wanted autoequip items
+ if (AutoEquip.wanted(item)) return false;
+ // don't mule items in locked spots - not exactly applicable for soloplay but including it
+ 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, again shouldn't really be used with soloplay but still including it
+ if (isAKey(item) && checkTorchSystem) 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
+ return (Pickit.checkItem(item).result > 0
+ && NTIP.CheckItem(item, NTIP.CheckList) === 1) || (item.isInStash && muleOrphans);
+ });
- return items;
+ return items;
};
diff --git a/libs/SoloPlay/Functions/AutoStatOverrides.js b/libs/SoloPlay/Functions/AutoStatOverrides.js
index 6b21ac43..bb33c41b 100644
--- a/libs/SoloPlay/Functions/AutoStatOverrides.js
+++ b/libs/SoloPlay/Functions/AutoStatOverrides.js
@@ -6,45 +6,44 @@
*
*/
-includeIfNotIncluded("common/AutoStat.js");
+includeIfNotIncluded("core/Auto/AutoStat.js");
AutoStat.init = function (statBuildOrder, save = 0, block = 0, bulkStat = true) {
- AutoStat.statBuildOrder = statBuildOrder;
- AutoStat.save = save;
- AutoStat.block = block;
- AutoStat.bulkStat = bulkStat;
+ AutoStat.statBuildOrder = statBuildOrder;
+ AutoStat.save = save;
+ AutoStat.block = block;
+ AutoStat.bulkStat = bulkStat;
- let usedStatPoints = false;
+ let usedStatPoints = false;
- if (!AutoStat.statBuildOrder || !AutoStat.statBuildOrder.length) {
- console.log("AutoStat: No build array specified");
+ if (!AutoStat.statBuildOrder || !AutoStat.statBuildOrder.length) {
+ console.log("AutoStat: No build array specified");
- return false;
- }
+ return false;
+ }
- while (me.getStat(sdk.stats.StatPts) > AutoStat.save) {
- AutoStat.addStatPoint() && (usedStatPoints = true);
- delay(150 + me.ping); // spending multiple single stat at a time with short delay may cause r/d
+ while (me.getStat(sdk.stats.StatPts) > AutoStat.save) {
+ AutoStat.addStatPoint() && (usedStatPoints = true);
+ 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) === AutoStat.remaining) {
- AutoStat.count += 1;
- }
+ // break out of loop if we have stat points available but finished allocating as configured
+ if (me.getStat(sdk.stats.StatPts) === AutoStat.remaining) {
+ AutoStat.count += 1;
+ }
- if (AutoStat.count > 2) {
- break;
- }
- }
+ if (AutoStat.count > 2) {
+ break;
+ }
+ }
- if (usedStatPoints) {
- myData = CharData.getStats();
- myData.me.level = me.charlvl;
- myData.me.strength = me.rawStrength;
- myData.me.dexterity = me.rawDexterity;
- CharData.updateData("me", myData) && updateMyData();
- }
+ if (usedStatPoints) {
+ me.data.level = me.charlvl;
+ me.data.strength = me.rawStrength;
+ me.data.dexterity = me.rawDexterity;
+ CharData.updateData("me", me.data) && me.update();
+ }
- console.log("AutoStat: Finished allocating stat points");
+ console.log("AutoStat: Finished allocating stat points");
- return true;
+ return true;
};
diff --git a/libs/SoloPlay/Functions/CharmEquip.js b/libs/SoloPlay/Functions/CharmEquip.js
new file mode 100644
index 00000000..86472a97
--- /dev/null
+++ b/libs/SoloPlay/Functions/CharmEquip.js
@@ -0,0 +1,792 @@
+/**
+* @filename CharmEquip.js
+* @author theBGuy
+* @desc AutoEquip for charms
+*
+*/
+
+const CharmEquip = (function () {
+ /**
+ * Goals:
+ * need to be able to define what types of charms we want while leveling, and upgrade based on that
+ * need to be able to define what types of charms we want for final build, upgrade to that
+ * need to be able to handle different invoquantity values of final charms vs leveling charms
+ * need to be abel to handle final charms and leveling charms being the same type, in situation where we have enough of a final charm so compare it as a noraml leveling charm
+ * need to differentiate bewtween cubing charm or pickit wanted charm vs autoequip charm
+ * example:
+ * Imagine we are an auradin and we have 9 small charms in our inventory, Seven 5allres/20life and Two random life charms. Our build tells us we should keep 6 of the 5/20s
+ * so we should keep those. That leaves us with One 5/20 and Two random life charms, we should then compare the tier values and keep the highest of the two then sell or drop the third.
+ * As it is now, what happens is we don't compare the 7th 5/20 and we add that to the sell list while keeping the 2 lower charms. If we directly add it to the backup then the invoquantity
+ * gets read from the finalBuild file so instead of only keeping two it says we should keep 6.
+ */
+
+ /**
+ * @param {ItemUnit} a
+ * @param {ItemUnit} b
+ */
+ const sortCharms = function (a, b) {
+ return NTIP.GetCharmTier(b) - NTIP.GetCharmTier(a);
+ };
+
+ /**
+ * Iterate over charm checklist, pickit result 0 and 4 get sold
+ * Otherwise if its not in the stash already and not a final charm try and stash it. I don't remember why I checked if it wasn't a final charm
+ * @param {ItemUnit[]} checkList
+ * @param {boolean} verbose
+ */
+ const spliceCharmCheckList = function (checkList = [], verbose = false) {
+ for (let i = 0; i < checkList.length; i++) {
+ const currCharm = checkList[i];
+ if (!currCharm) continue;
+ const pResult = NTIP.CheckItem(currCharm, NTIP.SoloList);
+ if (pResult === Pickit.Result.UNWANTED) {
+ continue;
+ }
+ if (!currCharm.isInStash && !me.data.charmGids.includes(currCharm.gid)) {
+ if (!Storage.Stash.MoveTo(currCharm)) {
+ verbose && Item.logger("Dropped", currCharm);
+ currCharm.drop();
+ } else {
+ if (verbose) {
+ Cubing.checkItem(currCharm)
+ ? Item.logItem("Stashed Cubing Ingredient", currCharm)
+ : Item.logItem("Stashed", currCharm);
+ }
+ }
+ }
+
+ checkList.splice(i, 1);
+ i -= 1;
+ }
+ };
+
+ const spliceCharmKeepList = function (keep = [], sell = [], verbose = false) {
+ if (!keep.length) return;
+ const id = keep[0].classid;
+ const cInfo = (function () {
+ return CharData.charms.get(id).count() || { max: 0 };
+ })();
+
+ // sort through kept charms
+ if (keep.length > cInfo.max) {
+ keep.sort(sortCharms);
+
+ // everything after the cap (need a better method for this in the instances where the max cap is less then leveling wanted cap)
+ for (let i = cInfo.max; i < keep.length; i++) {
+ if (!!keep[i].classid && !CharmEquip.check(keep[i])) {
+ sell.push(keep[i]);
+ if (verbose) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: CharmEquip Add " + keep[i].fname + " to checkList");
+ }
+ keep.splice(i, 1);
+ i -= 1;
+ }
+ }
+ }
+ };
+
+ /**
+ * @constructor
+ * @param {number} classid
+ */
+ function CharmTypeEquip (classid) {
+ this.classid = classid;
+ this.name = (function () {
+ switch (classid) {
+ case sdk.items.SmallCharm:
+ return "Small";
+ case sdk.items.LargeCharm:
+ return "Large";
+ case sdk.items.GrandCharm:
+ return "Grand";
+ default:
+ return "Unknown";
+ }
+ })();
+ /** @type {boolean} */
+ this.debugging = Developer.debugging[this.name.toLowerCase() + "Charm"];
+ }
+
+ /**
+ * Handle charm autoequip
+ * @param {ItemUnit[]} charmList
+ * @returns {{ keep: ItemUnit[], sell: ItemUnit[] }}
+ */
+ CharmTypeEquip.prototype.autoEquip = function (charmList = []) {
+ const _classid = this.classid;
+ let items = (charmList.length ? charmList : me.getItemsEx())
+ .filter(function (charm) {
+ return charm.isInStorage && charm.classid === _classid && charm.magic;
+ });
+
+ if (!items.length) {
+ this.debugging && console.debug("No charms found for " + this.name + "Charm");
+ return { keep: [], sell: [] };
+ }
+
+ let charms = CharmEquip.sort(items, this.debugging);
+ spliceCharmKeepList(charms.keep, charms.checkList, this.debugging);
+
+ this.debugging && console.log(this.name + " charm checklist length: " + charms.checkList.length);
+ spliceCharmCheckList(charms.checkList, this.debugging);
+
+ return { keep: charms.keep, sell: charms.checkList };
+ };
+
+ const _smallCharm = new CharmTypeEquip(sdk.items.SmallCharm);
+ const _largeCharm = new CharmTypeEquip(sdk.items.LargeCharm);
+ const _grandCharm = new CharmTypeEquip(sdk.items.GrandCharm);
+
+ return {
+ /** @type {Set} */
+ keptGids: new Set(),
+
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ hasCharmTier: function (item) {
+ return me.expansion && Config.AutoEquip && NTIP.GetCharmTier(item) > 0;
+ },
+
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ isFinalCharm: function (item) {
+ return me.data.charmGids.includes(item.gid);
+ },
+
+ init: function () {
+ // No charms in classic
+ if (me.classic) return;
+ let myCharms = me.getItemsEx()
+ .filter(function (item) {
+ return item.isInStorage && item.isCharm && item.magic;
+ });
+ let changed = false;
+
+ const finalCharmKeys = Object.keys(me.data.charms);
+ const check = function (list = [], charms = []) {
+ for (let i = 0; i < list.length; i++) {
+ if (!charms.some(c => c.gid === list[i])) {
+ console.log("A charm was removed from our final list - updated it");
+ me.data.charmGids.remove(list[i]);
+ list.splice(i, 1);
+ i--;
+ changed = true;
+ }
+ }
+ };
+
+ for (let key of finalCharmKeys) {
+ switch (me.data.charms[key].classid) {
+ case sdk.items.SmallCharm:
+ check(me.data.charms[key].have, myCharms);
+
+ break;
+ case sdk.items.LargeCharm:
+ check(me.data.charms[key].have, myCharms);
+
+ break;
+ case sdk.items.GrandCharm:
+ check(me.data.charms[key].have, myCharms);
+
+ break;
+ }
+ }
+
+ changed && me.update();
+ },
+
+ /**
+ * @param {ItemUnit} charm
+ * @returns {string | false}
+ */
+ getCharmType: function (charm) {
+ if (!charm || !charm.isCharm) return false;
+ if (charm.unique) return "unique";
+ if (!NTIP.hasStats(charm) && NTIP.GetCharmTier(charm) > 0) return "misc";
+
+ let charmType = "";
+ const skillerStats = me.getSkillTabs();
+
+ if (charm.getStat(sdk.stats.AddSkillTab, skillerStats[0])) {
+ charmType = "skillerTypeA";
+ } else if (charm.getStat(sdk.stats.AddSkillTab, skillerStats[1])) {
+ charmType = "skillerTypeB";
+ } else if (charm.getStat(sdk.stats.AddSkillTab, skillerStats[2])) {
+ charmType = "skillerTypeC";
+ }
+
+ switch (charm.prefix) {
+ case "Shimmering":
+ case "Azure":
+ case "Lapis":
+ case "Cobalt":
+ case "Sapphire":
+ case "Crimson":
+ case "Russet":
+ case "Garnet":
+ case "Ruby":
+ case "Tangerine":
+ case "Ocher":
+ case "Coral":
+ case "Amber":
+ case "Beryl":
+ case "Viridian":
+ case "Jade":
+ case "Emerald":
+ charmType = "resist";
+ break;
+ }
+
+ if (!charmType || charmType === "") {
+ switch (charm.suffix) {
+ case "of Fortune":
+ case "of Good Luck":
+ charmType = "magicfind";
+ break;
+ case "of Life":
+ case "of Substinence": // Odd issue, seems to be misspelled wherever item.suffix pulls info from
+ case "of Vita":
+ charmType = "life";
+ break;
+ }
+ }
+
+ if (!charmType || charmType === "") {
+ switch (charm.prefix) {
+ case "Red":
+ case "Sanguinary":
+ case "Bloody":
+ case "Jagged":
+ case "Forked":
+ case "Serrated":
+ case "Bronze":
+ case "Iron":
+ case "Steel":
+ case "Fine":
+ case "Sharp":
+ charmType = "damage";
+ break;
+ case "Snowy":
+ case "Shivering":
+ case "Boreal":
+ case "Hibernal":
+ case "Ember":
+ case "Smoldering":
+ case "Smoking":
+ case "Flaming":
+ case "Static":
+ case "Glowing":
+ case "Arcing":
+ case "Shocking":
+ case "Septic":
+ case "Foul":
+ case "Toxic":
+ case "Pestilant":
+ charmType = "elemental";
+ break;
+ }
+ }
+
+ if (!charmType || charmType === "") {
+ switch (charm.suffix) {
+ case "of Craftmanship":
+ case "of Quality":
+ case "of Maiming":
+ charmType = "damage";
+ break;
+ case "of Strength":
+ case "of Dexterity":
+ charmType = "stats";
+ break;
+ case "of Blight":
+ case "of Venom":
+ case "of Pestilence":
+ case "of Anthrax":
+ case "of Frost":
+ case "of Icicle":
+ case "of Glacier":
+ case "of Winter":
+ case "of Flame":
+ case "of Burning":
+ case "of Incineration":
+ case "of Shock":
+ case "of Lightning":
+ case "of Thunder":
+ case "of Storms":
+ charmType = "elemental";
+ break;
+ }
+ }
+
+ if (!charmType || charmType === "") {
+ switch (charm.prefix) {
+ case "Stout":
+ case "Burly":
+ case "Stalwart":
+ charmType = "misc";
+ break;
+ case "Rugged":
+ charmType = "misc";
+ break;
+ case "Lizard's":
+ case "Snake's":
+ case "Serpent's":
+ charmType = "mana";
+ break;
+ }
+ }
+
+ if (!charmType || charmType === "") {
+ switch (charm.suffix) {
+ case "of Balance":
+ case "of Greed":
+ case "of Inertia":
+ charmType = "misc";
+ break;
+ }
+ }
+
+ return charmType;
+ },
+
+ /**
+ * Handle small charm autoequip
+ * @param {ItemUnit[]} charmList
+ * @returns {{ keep: ItemUnit[], sell: ItemUnit[] }}
+ */
+ smallCharm: function (charmList = []) {
+ return _smallCharm.autoEquip(charmList);
+ },
+ /**
+ * Handle large charm autoequip
+ * @param {ItemUnit[]} charmList
+ * @returns {{ keep: ItemUnit[], sell: ItemUnit[] }}
+ */
+ largeCharm: function (charmList = []) {
+ return _largeCharm.autoEquip(charmList);
+ },
+ /**
+ * Handle grand charm autoequip
+ * @param {ItemUnit[]} charmList
+ * @returns {{ keep: ItemUnit[], sell: ItemUnit[] }}
+ */
+ grandCharm: function (charmList = []) {
+ return _grandCharm.autoEquip(charmList);
+ },
+
+ /**
+ * @param {ItemUnit[]} items
+ * @param {boolean} verbose
+ * @returns {{ skillerTypeA: ItemUnit[], skillerTypeB: ItemUnit[], skillerTypeC: ItemUnit[], resist: ItemUnit[], life: ItemUnit[], magicfind: ItemUnit[], damage: ItemUnit[], elemental: ItemUnit[], backup: ItemUnit[], keep: ItemUnit[], checkList: ItemUnit[] }}}
+ */
+ sort: function (items = [], verbose = false) {
+ const charms = {
+ skillerTypeA: [],
+ skillerTypeB: [],
+ skillerTypeC: [],
+ resist: [],
+ life: [],
+ magicfind: [],
+ damage: [],
+ elemental: [],
+ backup: [],
+ keep: [],
+ checkList: []
+ };
+
+ if (!items.length) {
+ verbose && console.log("No charms found");
+ return charms;
+ }
+
+ /** @param {ItemUnit} item */
+ const addToCheckList = function (item) {
+ return charms.checkList.indexOf(item) === -1 && charms.checkList.push(item);
+ };
+ /** @param {ItemUnit} item */
+ const addToBackUp = function (item) {
+ return charms.backup.indexOf(item) === -1 && charms.backup.push(item);
+ };
+
+ const iterateList = function (arr = [], verbose = false, backUpCheck = true) {
+ let invoquantity = NTIP.getInvoQuantity(arr[0]);
+ (invoquantity === undefined || invoquantity === -1) && (invoquantity = 2);
+ let charmType = CharmEquip.getCharmType(arr[0]);
+ verbose && console.log("Amount of " + charmType + " Charms: " + arr.length + " invoquantity: " + invoquantity);
+ if (arr.length > 1) {
+ arr.sort(sortCharms);
+ }
+
+ if (arr.length > invoquantity) {
+ if (verbose) {
+ arr.forEach(function (el, index) {
+ console.log(charmType + "[" + index + "] = " + NTIP.GetCharmTier(el));
+ });
+ }
+
+ for (let i = invoquantity; i < arr.length; i++) {
+ backUpCheck ? addToBackUp(arr[i]) : addToCheckList(arr[i]);
+
+ arr.splice(i, 1);
+ i -= 1;
+ }
+ }
+ };
+
+ verbose && console.log("Amount of items: " + items.length);
+ items.length > 1 && items.sort(sortCharms);
+
+ const finalCharmInfo = Check.finalBuild().finalCharms;
+ const finalCharmKeys = Object.keys(finalCharmInfo);
+
+ let found = false;
+
+ while (items.length > 0) {
+ let gid = items[0].gid;
+ let item = items.shift();
+
+ if (!item.identified) {
+ let idTool = me.getIdTool();
+
+ if (idTool) {
+ item.isInStash && Town.openStash();
+ Town.identifyItem(item, idTool);
+
+ } else if (item.isInStash && (getUIFlag(sdk.uiflags.Stash) || Town.openStash())) {
+ Storage.Inventory.MoveTo(item);
+ Town.identify();
+ }
+
+ if (!Game.getItem(-1, -1, gid)) {
+ verbose && console.log("Sold charm during Town.identify()");
+ items.shift();
+
+ continue;
+ }
+ }
+
+ if (me.data.charmGids.includes(item.gid)) {
+ charms.keep.push(item);
+
+ continue;
+ }
+
+ let next = false;
+
+ for (let key of finalCharmKeys) {
+ try {
+ if (!!me.data.charms[key] && me.data.charms[key].have.indexOf(item.gid) === -1
+ && me.data.charms[key].have.length < me.data.charms[key].max) {
+ if (finalCharmInfo[key].stats(item)) {
+ console.debug(item.fname);
+ me.data.charmGids.push(item.gid);
+ me.data.charms[key].have.push(item.gid);
+ charms.keep.push(item);
+ found = true;
+ next = true;
+
+ break;
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ if (next) {
+ continue;
+ }
+
+ if (NTIP.GetCharmTier(item) <= 0) {
+ verbose && console.log("No tier. Adding to checkList: " + item.fname);
+ addToCheckList(item);
+ } else if (!NTIP.hasStats(item) && NTIP.GetCharmTier(item) > 0) {
+ verbose && console.log("Multiple Misc charm: " + item.fname);
+ charms.backup.push(item);
+ } else {
+ let charmType = CharmEquip.getCharmType(item);
+ switch (charmType) {
+ case "skillerTypeA":
+ case "skillerTypeB":
+ case "skillerTypeC":
+ case "resist":
+ case "life":
+ case "magicfind":
+ case "damage":
+ case "elemental":
+ charms[charmType].push(item);
+ verbose && console.log(charmType + ": " + item.fname);
+
+ break;
+ default:
+ addToCheckList(item);
+ verbose && console.log("Failed all checks. Adding to checkList: " + item.fname);
+
+ break;
+ }
+ }
+ }
+
+ if (found) {
+ me.update();
+ }
+
+ if (Object.values(charms).every(c => !c.length)) {
+ verbose && console.log("No Charms");
+ return charms;
+ }
+
+ charms.skillerTypeA.length > 0 && iterateList(charms.skillerTypeA, verbose);
+ charms.skillerTypeB.length > 0 && iterateList(charms.skillerTypeB, verbose);
+ charms.skillerTypeC.length > 0 && iterateList(charms.skillerTypeC, verbose);
+ charms.resist.length > 0 && iterateList(charms.resist, verbose);
+ charms.life.length > 0 && iterateList(charms.life, verbose);
+ charms.magicfind.length > 0 && iterateList(charms.magicfind, verbose);
+ charms.damage.length > 0 && iterateList(charms.damage, verbose);
+ charms.elemental.length > 0 && iterateList(charms.elemental, verbose);
+
+ // If stats are unspecifed, this will filter charms and keep highest based on invoquantity. If no invoquantity defined it will keep two of that type
+ charms.backup.length > 0 && iterateList(charms.backup, verbose, false);
+ charms.keep = charms.keep.concat(
+ charms.skillerTypeA,
+ charms.skillerTypeB,
+ charms.skillerTypeC,
+ charms.resist,
+ charms.life,
+ charms.magicfind,
+ charms.damage,
+ charms.elemental,
+ charms.backup
+ );
+ if (verbose) {
+ charms.checkList
+ .forEach(function (el, index) {
+ console.log("checkList[" + index + "] = " + NTIP.GetCharmTier(el) + " " + el.fname);
+ });
+ }
+
+ return charms;
+ },
+
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ check: function (item) {
+ if (!item || NTIP.GetCharmTier(item) <= 0 || !item.isCharm) return false;
+ // Annhilus, Hellfire Torch, Gheeds - Handled by a different function so return true to keep
+ if (item.isCharm && item.unique) return true;
+ // is one of our final charms
+ if (me.data.charmGids.includes(item.gid)) return true;
+ // is in our checkList
+ if (CharmEquip.keptGids.has(item.gid)) return true;
+
+ let lowestCharm;
+ let items = me.getItemsEx()
+ .filter(function (charm) {
+ return charm.classid === item.classid
+ && charm.isInStorage
+ && charm.magic
+ && NTIP.GetCharmTier(charm) > 0;
+ });
+ if (!items.length) return true;
+
+ let quantityCap = NTIP.getInvoQuantity(item);
+ let have = 0;
+ let charms = CharmEquip.sort(items);
+ let charmType = CharmEquip.getCharmType(item);
+ let cInfo, newList = [];
+
+ switch (item.classid) {
+ case sdk.items.SmallCharm:
+ cInfo = CharData.charms.get("small").count();
+
+ if (cInfo.curr && (cInfo.curr / cInfo.max) * 100 >= 75) {
+ // chop off past our cap
+ newList = charms.keep
+ .sort(sortCharms)
+ .slice(0, cInfo.max);
+ // check if it made the cut
+ if (!newList.find(i => i.gid === item.gid)) return false;
+ lowestCharm = newList.last();
+ return !!(NTIP.GetCharmTier(item) >= NTIP.GetCharmTier(lowestCharm) || item.gid === lowestCharm.gid);
+ }
+
+ break;
+ case sdk.items.LargeCharm:
+ cInfo = CharData.charms.get("large").count();
+
+ if (cInfo.curr && (cInfo.curr / cInfo.max) * 100 >= 75) {
+ // chop off past our cap
+ newList = charms.keep
+ .sort(sortCharms)
+ .slice(0, cInfo.max);
+ // check if it made the cut
+ if (!newList.find(i => i.gid === item.gid)) return false;
+ lowestCharm = newList.last();
+ return !!(NTIP.GetCharmTier(item) >= NTIP.GetCharmTier(lowestCharm) || item.gid === lowestCharm.gid);
+ }
+
+ break;
+ case sdk.items.GrandCharm:
+ cInfo = CharData.charms.get("grand").count();
+
+ if (cInfo.curr && (cInfo.curr / cInfo.max) * 100 >= 50) {
+ // chop off past our cap
+ newList = charms.keep
+ .sort(sortCharms)
+ .slice(0, cInfo.max);
+ // check if it made the cut
+ if (!newList.find(i => i.gid === item.gid)) return false;
+ lowestCharm = newList.last();
+ return !!(NTIP.GetCharmTier(item) >= NTIP.GetCharmTier(lowestCharm) || item.gid === lowestCharm.gid);
+ }
+
+ break;
+ }
+
+ switch (charmType) {
+ case "skillerTypeA":
+ case "skillerTypeB":
+ case "skillerTypeC":
+ case "resist":
+ case "life":
+ case "magicfind":
+ case "damage":
+ case "elemental":
+ have = charms[charmType].length;
+ lowestCharm = charms[charmType].last();
+ if ((charms[charmType].findIndex(c => c.gid === lowestCharm.gid) + 1) > quantityCap) return false;
+
+ break;
+ default:
+ have = charms.backup.length;
+ lowestCharm = charms.backup.last();
+ if ((charms.backup.findIndex(c => c.gid === lowestCharm.gid) + 1) > quantityCap) return false;
+ // console.debug("Lowest Charm index " + (charms.backup.findIndex(c => c.gid === lowestCharm.gid)) + " out of " + charms.backup.length);
+
+ break;
+ }
+
+ if (!lowestCharm) {
+ // console.debug("Didn't find any other charms of this type " + charmType);
+ return true;
+ }
+
+ if (item.gid === lowestCharm.gid) {
+ // console.debug("Same charm");
+ return true;
+ }
+
+ let [tierParamItem, tierLowestItem] = [NTIP.GetCharmTier(item), NTIP.GetCharmTier(lowestCharm)];
+
+ if (tierParamItem === tierLowestItem) {
+ // console.debug("Same tier value");
+ // super hacky - arbritrary comparsion of xpos if the tier value is the same
+ return (have < quantityCap)
+ || (item.isInInventory && lowestCharm.isInInventory && item.x > lowestCharm.y)
+ || (item.isInInventory && !lowestCharm.isInInventory);
+ }
+
+ return (tierParamItem >= tierLowestItem);
+ },
+
+ run: function () {
+ // No charms in classic
+ if (me.classic) return;
+
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Entering charm auto equip");
+ const verbose = (Developer.debugging.smallCharm
+ || Developer.debugging.largeCharm
+ || Developer.debugging.grandCharm
+ );
+ let tick = getTickCount();
+ let charms = me.getItemsEx()
+ .filter(function (item) {
+ return item.isInStorage && item.isCharm && item.magic;
+ });
+ // don't do anything if we don't have any charms
+ if ((!charms.length)
+ // don't do anything if we have the same charms as last time
+ || ((CharmEquip.keptGids.size && charms.every(c => CharmEquip.keptGids.has(c.gid))))) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Exiting charm auto equip. Time elapsed: " + Time.format(getTickCount() - tick));
+ return;
+ }
+ CharmEquip.keptGids.clear();
+ /** @type {Array} */
+ let totalKeep = [];
+ /** @type {Array} */
+ let totalSell = [];
+ let GCs = CharmEquip.grandCharm(charms);
+ let LCs = CharmEquip.largeCharm(charms);
+ let SCs = CharmEquip.smallCharm(charms);
+ let specialCharms = charms.filter((charm) => charm.unique);
+
+ if (verbose) {
+ console.log("Grand Charms Keep: " + GCs.keep.length + ", Sell: " + GCs.sell.length);
+ console.log("Large Charms Keep: " + LCs.keep.length + ", Sell: " + LCs.sell.length);
+ console.log("Small Charms Keep: " + SCs.keep.length + ", Sell: " + SCs.sell.length);
+ }
+
+ totalKeep = totalKeep.concat(SCs.keep, LCs.keep, GCs.keep, specialCharms);
+ for (let i = 0; i < totalKeep.length; i++) {
+ if (!CharmEquip.check(totalKeep[i])) {
+ totalSell.push(totalKeep[i]);
+ totalKeep.splice(i, 1);
+ i--;
+ }
+ }
+ totalSell = totalSell
+ .concat(SCs.sell, LCs.sell, GCs.sell)
+ .filter(function (charm) {
+ return NTIP.CheckItem(charm, NTIP.CheckList) === Pickit.Result.UNWANTED;
+ });
+ totalKeep.length > 0 && console.log("ÿc8Kolbot-SoloPlayÿc0: Total Charms Kept: " + totalKeep.length);
+
+ if (totalSell.length > 0) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Total Charms Sell: " + totalSell.length);
+
+ for (let i = 0; i < totalSell.length; i++) {
+ totalSell[i].isInStash && !getUIFlag(sdk.uiflags.Stash) && Town.openStash();
+ if (totalSell[i].isInStash && (!totalSell[i].sellable || !Storage.Inventory.MoveTo(totalSell[i]))) {
+ totalSell[i].drop();
+ totalSell.splice(i, 1);
+ i -= 1;
+ }
+ }
+
+ Town.initNPC("Shop", "clearInventory");
+
+ if (getUIFlag(sdk.uiflags.Shop)
+ || (Config.PacketShopping && getInteractedNPC() && getInteractedNPC().itemcount > 0)) {
+ for (let item of totalSell) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Sell old charm " + item.name);
+ verbose && Item.logger("Sold", item);
+ verbose && Item.logItem("CharmEquip Sold", item);
+ item.sell();
+ }
+ }
+ }
+
+ if (totalKeep.length > 0) {
+ for (let item of totalKeep) {
+ CharmEquip.keptGids.add(item.gid);
+ if (item.isInStash && !Cubing.checkItem(item)) {
+ !getUIFlag(sdk.uiflags.Stash) && Town.openStash() && delay(300 + me.ping);
+ if (Storage.Inventory.CanFit(item) && Storage.Inventory.MoveTo(item)) {
+ verbose && Item.logItem("CharmEquip Equipped", item);
+ }
+ }
+ }
+ }
+
+ me.cancelUIFlags();
+
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Exiting charm auto equip. Time elapsed: " + Time.format(getTickCount() - tick));
+ },
+ };
+})();
diff --git a/libs/SoloPlay/Functions/ClassAttackOverrides/AmazonAttacks-WIP.js b/libs/SoloPlay/Functions/ClassAttackOverrides/AmazonAttacks-WIP.js
index 31c7978e..5a747850 100644
--- a/libs/SoloPlay/Functions/ClassAttackOverrides/AmazonAttacks-WIP.js
+++ b/libs/SoloPlay/Functions/ClassAttackOverrides/AmazonAttacks-WIP.js
@@ -7,428 +7,428 @@
// TODO: clean up this whole file
-includeIfNotIncluded("common/Attacks/Amazon.js");
+includeIfNotIncluded("core/Attacks/Amazon.js");
ClassAttack.decoyTick = getTickCount();
// todo: take into account auras as well - conviction/fanat/might can all be dangerous
// mayve include dolls too?
let inDanger = function (unit) {
- let nearUnits = getUnits(sdk.unittype.Monster).filter((mon) => mon.attackable && getDistance(unit, mon) < 10);
- let dangerClose = nearUnits.find(mon => mon.getEnchant(sdk.enchant.ManaBurn) || mon.getEnchant(sdk.enchant.LightningEnchanted));
- return {
- check: nearUnits.length > me.maxNearMonsters || dangerClose,
- mobs: nearUnits.length
- };
+ let nearUnits = getUnits(sdk.unittype.Monster).filter((mon) => mon.attackable && getDistance(unit, mon) < 10);
+ let dangerClose = nearUnits.find(mon => mon.getEnchant(sdk.enchant.ManaBurn) || mon.getEnchant(sdk.enchant.LightningEnchanted));
+ return {
+ check: nearUnits.length > me.maxNearMonsters || dangerClose,
+ mobs: nearUnits.length
+ };
};
ClassAttack.decideSkill = function (unit) {
- let skills = {timed: -1, untimed: -1};
- if (!unit) return skills;
+ let skills = { timed: -1, untimed: -1 };
+ if (!unit) return skills;
- let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
+ let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
- // Get timed skill
- let checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index];
+ // 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)) {
- skills.timed = checkSkill;
- } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5])) {
- skills.timed = Config.AttackSkill[5];
- }
+ if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill)) {
+ skills.timed = checkSkill;
+ } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5])) {
+ skills.timed = Config.AttackSkill[5];
+ }
- // Get untimed skill
- checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[1] : Config.AttackSkill[index + 1];
+ // 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])) {
- skills.untimed = Config.AttackSkill[6];
- }
+ 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])) {
+ skills.untimed = Config.AttackSkill[6];
+ }
- // Low mana timed skill
- if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(skills.untimed) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) {
- skills.timed = Config.LowManaSkill[0];
- }
+ // Low mana timed skill
+ if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(skills.untimed) > 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];
- }
+ // 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;
+ return skills;
};
ClassAttack.doAttack = function (unit) {
- if (!unit) return Attack.Result.SUCCESS;
- let gid = unit.gid;
- let needRepair = me.charlvl < 5 ? [] : me.needRepair();
-
- if ((Config.MercWatch && Town.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;
- }
- }
- }
-
- let mercRevive = 0;
- let gold = me.gold;
- let index = ((unit.isSpecial) || unit.isPlayer) ? 1 : 3;
-
- // todo: assign main attack damage, if we have a range skill but we are attacking a mob thats resistant with our close up skill, move away and use far attack
- // figure out better slow-missiles casting
- // need to re-write to include damage calculations - when should we use poison over light or phys over light
- const data = {
- innerSight: {
- skill: sdk.skills.InnerSight,
- level: me.getSkill(sdk.skills.InnerSight, sdk.skills.subindex.SoftPoints),
- range: 15,
- mana: Skill.getManaCost(sdk.skills.InnerSight),
- use: function () {
- return this.level > 0;
- }
- },
- slowMissiles: {
- skill: sdk.skills.SlowMissiles,
- level: me.getSkill(sdk.skills.SlowMissiles, sdk.skills.subindex.SoftPoints),
- range: 15,
- mana: Skill.getManaCost(sdk.skills.SlowMissiles),
- use: function () {
- return this.level > 0;
- }
- },
- decoy: {
- skill: sdk.skills.Dopplezon,
- level: me.getSkill(sdk.skills.Dopplezon, sdk.skills.subindex.SoftPoints),
- range: 20,
- mana: Skill.getManaCost(sdk.skills.Dopplezon),
- duration: Skill.getDuration(sdk.skills.Dopplezon),
- force: false,
- use: function () {
- return ((this.level > 0 && !me.normal) || this.force);
- }
- },
- lightFury: {
- skill: sdk.skills.LightningFury,
- level: me.getSkill(sdk.skills.LightningFury, sdk.skills.subindex.SoftPoints),
- range: Skill.getRange(sdk.skills.LightningFury),
- mana: Skill.getManaCost(sdk.skills.LightningFury),
- force: false,
- use: function () {
- return (this.level >= 10 || this.force);
- }
- },
- plagueJav: {
- skill: sdk.skills.PlagueJavelin,
- level: me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.SoftPoints),
- range: Skill.getRange(sdk.skills.PlagueJavelin),
- mana: Skill.getManaCost(sdk.skills.PlagueJavelin),
- force: false,
- use: function () {
- return ((!me.normal && this.level > 0) || this.level >= 15 || this.force);
- }
- },
- jab: {
- skill: sdk.skills.Jab,
- level: me.getSkill(sdk.skills.Jab, sdk.skills.subindex.SoftPoints),
- range: Skill.getRange(sdk.skills.Jab),
- mana: Skill.getManaCost(sdk.skills.Jab),
- use: function () {
- return (this.level > 0 && Item.getEquippedItem(sdk.body.RightArm).tier >= 1000);
- }
- },
- };
-
- // Pre-attacks Section -----------------------------------------------------------------------------------------------------------------//
- if (data.slowMissiles.use()) {
- 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 ([156, 211, 242, 243, 544, 571, 391, 365, 267, 229].includes(unit.classid)) {
- // Check if already in this state
- if (!unit.getState(sdk.states.InnerSight)) {
- Skill.cast(sdk.skills.InnerSight, sdk.skills.hand.Right, unit);
- }
- } else {
- Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit);
- }
- }
- }
- }
-
- // if inDanger and within melee distance should we try to find a better spot?
- if (inDanger(unit).check) {
- data.lightFury.level && (data.lightFury.force = data.lightFury.level > 10);
- data.plagueJav.level && (data.plagueJav.force = data.plagueJav.level > 10);
- data.decoy.level && (data.decoy.force = true);
- }
-
- if (data.innerSight.use()) {
- 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);
- }
- }
-
- // Handle Switch casting
- let commonCheck = (gold > 500000 || unit.isBoss || [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area));
- if (me.expansion && index === 1 && unit.curseable) {
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
- && !unit.getState(sdk.states.LowerResist) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast lower resist
- Attack.switchCastCharges(sdk.skills.LowerResist, unit);
- }
-
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken) && !unit.getState(sdk.states.Weaken)
- && !unit.getState(sdk.states.LowerResist) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast weaken
- Attack.switchCastCharges(sdk.skills.Weaken, unit);
- }
- }
-
- // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
- if (Precast.haveCTA > -1 && unit.curseable && (index === 1 || unit.isDoll)
- && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
- Skill.switchCast(sdk.skills.BattleCry, {oSkill: true});
- }
-
- if (data.decoy.use()) {
- // Act Bosses or Immune to my main boss skill
- if ((unit.isPrimeEvil) || !Attack.checkResist(unit, Config.AttackSkill[1]) || data.decoy.force) {
- Misc.poll(() => !me.skillDelay, 1000, 40);
-
- // Don't use decoy if within melee distance
- if (unit.distance > 5) {
- // Check to see if decoy has already been cast
- let decoy = me.getMinionCount(8);
-
- if ((!decoy || data.decoy.force) && (getTickCount() - this.decoyTick >= data.decoy.duration)) {
- if (unit.distance > 10 || checkCollision(me, unit, 0x7)) {
- if (!Attack.getIntoPosition(unit, 10, 0x7)) {
- return Attack.Result.FAILED;
- }
- }
-
- let coord = CollMap.getRandCoordinate(unit.x, -2, 2, unit.y, -2, 2);
- !!coord && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, coord.x, coord.y);
-
- // Check if it was a sucess
- !!me.getMinionCount(8) && (this.decoyTick = getTickCount());
- }
- }
- }
- }
-
- // Only try attacking light immunes if I have my end game javelin - preAttack with Plague Javelin
- if (data.plagueJav.use() && !Attack.checkResist(unit, "lightning")) {
- if (unit.distance <= 15 && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast Slow-Missles, then proceed with Plague Jav. Lowers amount of damage from projectiles.
- !unit.getState(sdk.states.SlowMissiles) && data.slowMissiles.use() && Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit);
-
- // Handle Switch casting
- if (!unit.dead) {
- // should we switch cast any mob thats light immune?
- if (!unit.getState(sdk.states.LowerResist) && unit.curseable && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast lower resist
- Attack.switchCastCharges(sdk.skills.LowerResist, unit);
- }
- }
-
- if (Attack.checkResist(unit, "poison") && !me.skillDelay && !unit.dead) {
- Skill.cast(sdk.skills.PlagueJavelin, Skill.getHand(sdk.skills.PlagueJavelin), unit);
- }
-
- if (!data.jab.use() && data.jab.level) {
- // We are within melee distance might as well use jab rather than stand there
- // Make sure monster is not physical immune
- if (unit.distance < 4 && Attack.checkResist(unit, "physical")) {
- if (checkCollision(me, unit, 0x7)) {
- if (!Attack.getIntoPosition(unit, 3, 0x7)) {
- return Attack.Result.FAILED;
- }
- }
-
- !unit.dead && Skill.cast(sdk.skills.Jab, Skill.getHand(sdk.skills.Jab), unit);
-
- return Attack.Result.SUCCESS;
- }
-
- return Attack.Result.SUCCESS;
- }
- }
- }
-
- // Only try attacking immunes if I have my end game javelin and they aren't lightning enchanted - use jab as main attack
- // why? don't remember reason I did this
- // if (data.jab.use() && !Attack.checkResist(unit, Config.AttackSkill[1]) && Attack.checkResist(unit, "physical") && !unit.getEnchant(sdk.enchant.LightningEnchanted)) {
- // if ((unit.distance > 3 || checkCollision(me, unit, sdk.collision.Ranged)) && !Attack.getIntoPosition(unit, 3, sdk.collision.BlockWall)) {
- // return Attack.Result.FAILED;
- // }
-
- // !unit.dead && Skill.cast(sdk.skills.Jab, Skill.getHand(sdk.skills.Jab), unit);
-
- // return Attack.Result.SUCCESS;
- // }
-
- if (data.plagueJav.use() && Attack.checkResist(unit, "poison") && !unit.getState(sdk.states.Poison) && !me.skillDelay) {
- if (((data.plagueJav.force || unit.distance >= 8) && unit.distance <= 25) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- Skill.cast(sdk.skills.PlagueJavelin, Skill.getHand(sdk.skills.PlagueJavelin), unit);
- }
- }
-
- if (data.lightFury.use()) {
- if ((unit.distance >= 8 && unit.distance <= 25) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- if (Skill.cast(sdk.skills.LightningFury, Skill.getHand(sdk.skills.LightningFury), unit) && data.lightFury.force) 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();
-
- 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 && 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) || (data.decoy.level && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, unit));
- }
- }
-
- return Attack.Result.SUCCESS;
- }
-
- return result;
+ if (!unit) return Attack.Result.SUCCESS;
+ let gid = unit.gid;
+ let needRepair = me.charlvl < 5 ? [] : 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;
+ }
+ }
+ }
+
+ let mercRevive = 0;
+ let gold = me.gold;
+ let index = ((unit.isSpecial) || unit.isPlayer) ? 1 : 3;
+
+ // todo: assign main attack damage, if we have a range skill but we are attacking a mob thats resistant with our close up skill, move away and use far attack
+ // figure out better slow-missiles casting
+ // need to re-write to include damage calculations - when should we use poison over light or phys over light
+ const data = {
+ innerSight: {
+ skill: sdk.skills.InnerSight,
+ level: me.getSkill(sdk.skills.InnerSight, sdk.skills.subindex.SoftPoints),
+ range: 15,
+ mana: Skill.getManaCost(sdk.skills.InnerSight),
+ use: function () {
+ return this.level > 0;
+ }
+ },
+ slowMissiles: {
+ skill: sdk.skills.SlowMissiles,
+ level: me.getSkill(sdk.skills.SlowMissiles, sdk.skills.subindex.SoftPoints),
+ range: 15,
+ mana: Skill.getManaCost(sdk.skills.SlowMissiles),
+ use: function () {
+ return this.level > 0;
+ }
+ },
+ decoy: {
+ skill: sdk.skills.Dopplezon,
+ level: me.getSkill(sdk.skills.Dopplezon, sdk.skills.subindex.SoftPoints),
+ range: 20,
+ mana: Skill.getManaCost(sdk.skills.Dopplezon),
+ duration: Skill.getDuration(sdk.skills.Dopplezon),
+ force: false,
+ use: function () {
+ return ((this.level > 0 && !me.normal) || this.force);
+ }
+ },
+ lightFury: {
+ skill: sdk.skills.LightningFury,
+ level: me.getSkill(sdk.skills.LightningFury, sdk.skills.subindex.SoftPoints),
+ range: Skill.getRange(sdk.skills.LightningFury),
+ mana: Skill.getManaCost(sdk.skills.LightningFury),
+ force: false,
+ use: function () {
+ return (this.level >= 10 || this.force);
+ }
+ },
+ plagueJav: {
+ skill: sdk.skills.PlagueJavelin,
+ level: me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.SoftPoints),
+ range: Skill.getRange(sdk.skills.PlagueJavelin),
+ mana: Skill.getManaCost(sdk.skills.PlagueJavelin),
+ force: false,
+ use: function () {
+ return ((!me.normal && this.level > 0) || this.level >= 15 || this.force);
+ }
+ },
+ jab: {
+ skill: sdk.skills.Jab,
+ level: me.getSkill(sdk.skills.Jab, sdk.skills.subindex.SoftPoints),
+ range: Skill.getRange(sdk.skills.Jab),
+ mana: Skill.getManaCost(sdk.skills.Jab),
+ use: function () {
+ return (this.level > 0 && me.equipped.get(sdk.body.RightArm).tier >= 1000);
+ }
+ },
+ };
+
+ // Pre-attacks Section -----------------------------------------------------------------------------------------------------------------//
+ if (data.slowMissiles.use()) {
+ 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 ([156, 211, 242, 243, 544, 571, 391, 365, 267, 229].includes(unit.classid)) {
+ // Check if already in this state
+ if (!unit.getState(sdk.states.InnerSight)) {
+ Skill.cast(sdk.skills.InnerSight, sdk.skills.hand.Right, unit);
+ }
+ } else {
+ Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit);
+ }
+ }
+ }
+ }
+
+ // if inDanger and within melee distance should we try to find a better spot?
+ if (inDanger(unit).check) {
+ data.lightFury.level && (data.lightFury.force = data.lightFury.level > 10);
+ data.plagueJav.level && (data.plagueJav.force = data.plagueJav.level > 10);
+ data.decoy.level && (data.decoy.force = true);
+ }
+
+ if (data.innerSight.use()) {
+ 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);
+ }
+ }
+
+ // Handle Switch casting
+ let commonCheck = (gold > 500000 || unit.isBoss || [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area));
+ if (me.expansion && index === 1 && unit.curseable) {
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
+ && !unit.getState(sdk.states.LowerResist) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast lower resist
+ Attack.switchCastCharges(sdk.skills.LowerResist, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken) && !unit.getState(sdk.states.Weaken)
+ && !unit.getState(sdk.states.LowerResist) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast weaken
+ Attack.switchCastCharges(sdk.skills.Weaken, unit);
+ }
+ }
+
+ // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
+ if (Precast.haveCTA > -1 && unit.curseable && (index === 1 || unit.isDoll)
+ && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
+ Skill.switchCast(sdk.skills.BattleCry, { oSkill: true });
+ }
+
+ if (data.decoy.use()) {
+ // Act Bosses or Immune to my main boss skill
+ if ((unit.isPrimeEvil) || !Attack.checkResist(unit, Config.AttackSkill[1]) || data.decoy.force) {
+ Misc.poll(() => !me.skillDelay, 1000, 40);
+
+ // Don't use decoy if within melee distance
+ if (unit.distance > 5) {
+ // Check to see if decoy has already been cast
+ let decoy = me.getMinionCount(8);
+
+ if ((!decoy || data.decoy.force) && (getTickCount() - this.decoyTick >= data.decoy.duration)) {
+ if (unit.distance > 10 || checkCollision(me, unit, 0x7)) {
+ if (!Attack.getIntoPosition(unit, 10, 0x7)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ let coord = CollMap.getRandCoordinate(unit.x, -2, 2, unit.y, -2, 2);
+ !!coord && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, coord.x, coord.y);
+
+ // Check if it was a sucess
+ !!me.getMinionCount(8) && (this.decoyTick = getTickCount());
+ }
+ }
+ }
+ }
+
+ // Only try attacking light immunes if I have my end game javelin - preAttack with Plague Javelin
+ if (data.plagueJav.use() && !Attack.checkResist(unit, "lightning")) {
+ if (unit.distance <= 15 && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast Slow-Missles, then proceed with Plague Jav. Lowers amount of damage from projectiles.
+ !unit.getState(sdk.states.SlowMissiles) && data.slowMissiles.use() && Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit);
+
+ // Handle Switch casting
+ if (!unit.dead) {
+ // should we switch cast any mob thats light immune?
+ if (!unit.getState(sdk.states.LowerResist) && unit.curseable && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast lower resist
+ Attack.switchCastCharges(sdk.skills.LowerResist, unit);
+ }
+ }
+
+ if (Attack.checkResist(unit, "poison") && !me.skillDelay && !unit.dead) {
+ Skill.cast(sdk.skills.PlagueJavelin, Skill.getHand(sdk.skills.PlagueJavelin), unit);
+ }
+
+ if (!data.jab.use() && data.jab.level) {
+ // We are within melee distance might as well use jab rather than stand there
+ // Make sure monster is not physical immune
+ if (unit.distance < 4 && Attack.checkResist(unit, "physical")) {
+ if (checkCollision(me, unit, 0x7)) {
+ if (!Attack.getIntoPosition(unit, 3, 0x7)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ !unit.dead && Skill.cast(sdk.skills.Jab, Skill.getHand(sdk.skills.Jab), unit);
+
+ return Attack.Result.SUCCESS;
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+ }
+ }
+
+ // Only try attacking immunes if I have my end game javelin and they aren't lightning enchanted - use jab as main attack
+ // why? don't remember reason I did this
+ // if (data.jab.use() && !Attack.checkResist(unit, Config.AttackSkill[1]) && Attack.checkResist(unit, "physical") && !unit.getEnchant(sdk.enchant.LightningEnchanted)) {
+ // if ((unit.distance > 3 || checkCollision(me, unit, sdk.collision.Ranged)) && !Attack.getIntoPosition(unit, 3, sdk.collision.BlockWall)) {
+ // return Attack.Result.FAILED;
+ // }
+
+ // !unit.dead && Skill.cast(sdk.skills.Jab, Skill.getHand(sdk.skills.Jab), unit);
+
+ // return Attack.Result.SUCCESS;
+ // }
+
+ if (data.plagueJav.use() && Attack.checkResist(unit, "poison") && !unit.getState(sdk.states.Poison) && !me.skillDelay) {
+ if (((data.plagueJav.force || unit.distance >= 8) && unit.distance <= 25) && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ Skill.cast(sdk.skills.PlagueJavelin, Skill.getHand(sdk.skills.PlagueJavelin), unit);
+ }
+ }
+
+ if (data.lightFury.use()) {
+ if ((unit.distance >= 8 && unit.distance <= 25) && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (Skill.cast(sdk.skills.LightningFury, Skill.getHand(sdk.skills.LightningFury), unit) && data.lightFury.force) 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();
+
+ while (unit.attackable) {
+ 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 && 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) || (data.decoy.level && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, unit));
+ }
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ return result;
};
ClassAttack.afterAttack = function () {
- Precast.doPrecast(false);
+ Precast.doPrecast(false);
- let needRepair = me.charlvl < 5 ? [] : me.needRepair();
-
- // Repair check, make sure i have a tome
- if (needRepair.length > 0 && me.getItem(sdk.items.TomeofTownPortal)) {
- Town.visitTown(true);
- }
+ let needRepair = me.charlvl < 5 ? [] : me.needRepair();
+
+ // Repair check, make sure i have a tome
+ if (needRepair.length > 0 && me.getItem(sdk.items.TomeofTownPortal)) {
+ Town.visitTown(true);
+ }
- this.lightFuryTick = 0;
+ this.lightFuryTick = 0;
};
// Returns: 0 - fail, 1 - success, 2 - no valid attack skills
ClassAttack.doCast = function (unit, timedSkill, untimedSkill) {
- let walk;
-
- // 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;
- }
- }
-
- if (timedSkill > -1 && (!me.getState(sdk.states.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 main attack skill is lightning strike and charged strike's skill level is at least level 15, check current monster count. If monster count is less than 3, use CS as its more effective with small mobs
- if (timedSkill === sdk.skills.LightningStrike && me.getSkill(sdk.skills.ChargedStrike, sdk.skills.subindex.SoftPoints) >= 15) {
- if (me.getMobCount(15, Coords_1.BlockBits.LineOfSight | Coords_1.BlockBits.Ranged | Coords_1.BlockBits.ClosedDoor | Coords_1.BlockBits.BlockWall) <= 3) {
- timedSkill = sdk.skills.ChargedStrike;
- }
- }
-
- if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y)) 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)) 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 (this.lightFuryTick && getTickCount() - this.lightFuryTick < Config.LightningFuryDelay * 1000) {
- delay(40);
- }
-
- return Attack.Result.SUCCESS;
+ let walk;
+
+ // 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;
+ }
+ }
+
+ if (timedSkill > -1 && (!me.getState(sdk.states.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 main attack skill is lightning strike and charged strike's skill level is at least level 15, check current monster count. If monster count is less than 3, use CS as its more effective with small mobs
+ if (timedSkill === sdk.skills.LightningStrike && me.getSkill(sdk.skills.ChargedStrike, sdk.skills.subindex.SoftPoints) >= 15) {
+ if (me.getMobCount(15, Coords_1.BlockBits.LineOfSight | Coords_1.BlockBits.Ranged | Coords_1.BlockBits.ClosedDoor | Coords_1.BlockBits.BlockWall) <= 3) {
+ timedSkill = sdk.skills.ChargedStrike;
+ }
+ }
+
+ if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y)) 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)) 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 (this.lightFuryTick && getTickCount() - this.lightFuryTick < Config.LightningFuryDelay * 1000) {
+ delay(40);
+ }
+
+ return Attack.Result.SUCCESS;
};
diff --git a/libs/SoloPlay/Functions/ClassAttackOverrides/AmazonAttacks.js b/libs/SoloPlay/Functions/ClassAttackOverrides/AmazonAttacks.js
index 22837a49..8d0747ce 100644
--- a/libs/SoloPlay/Functions/ClassAttackOverrides/AmazonAttacks.js
+++ b/libs/SoloPlay/Functions/ClassAttackOverrides/AmazonAttacks.js
@@ -3,6 +3,8 @@
* @author theBGuy
* @desc Amazon fixes to improve class attack functionality
*
+* @typedef {import('../../../../sdk/globals')}
+* @typedef {import('../../globals')}
*/
/**
@@ -11,409 +13,491 @@
* - test early on using a bow on switch for ranged attacks (might be worth a point in magic arrow)
*/
-includeIfNotIncluded("common/Attacks/Amazon.js");
+includeIfNotIncluded("core/Attacks/Amazon.js");
ClassAttack.decoyTick = getTickCount();
+/**
+ * @param {Monster} unit
+ * @param {boolean} preattack
+ * @param {boolean} once
+ * @returns {AttackResult}
+ */
ClassAttack.doAttack = function (unit, preattack, once) {
- // unit became invalidated
- if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
-
- let gid = unit.gid;
- let needRepair = me.charlvl < 5 ? [] : me.needRepair();
-
- if ((Config.MercWatch && Town.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;
- }
- }
- }
-
- let preattackRange = Skill.getRange(Config.AttackSkill[0]);
- let decoyDuration = Skill.getDuration(sdk.skills.Dopplezon);
- let gold = me.gold;
- const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
-
- let useInnerSight = Skill.canUse(sdk.skills.InnerSight);
- let useSlowMissiles = Skill.canUse(sdk.skills.SlowMissiles);
- let useDecoy = (Skill.canUse(sdk.skills.Dopplezon) && !me.normal);
- let usePlague = (!me.normal && Skill.canUse(sdk.skills.PlagueJavelin));
- let useJab = (Item.getEquippedItem(sdk.body.RightArm).tier >= 1000 && Skill.canUse(sdk.skills.Jab));
- let useLightFury = me.getSkill(sdk.skills.LightningFury, sdk.skills.subindex.SoftPoints) >= 10;
- let forcePlague = (me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.SoftPoints) >= 15); //Extra poison damage then attack
-
- // Precast Section -----------------------------------------------------------------------------------------------------------------//
- if (useSlowMissiles) {
- 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 (useInnerSight && !unit.getState(sdk.states.InnerSight)) {
- Skill.cast(sdk.skills.InnerSight, sdk.skills.hand.Right, unit);
- }
- } else {
- Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit);
- }
- }
- }
- }
-
- if (Skill.canUse(sdk.skills.LightningFury) && unit.getEnchant(sdk.enchant.ManaBurn) && unit.getMobCount(7) > 2) {
- useLightFury = true;
- }
-
- if (Skill.canUse(sdk.skills.PlagueJavelin) && unit.getEnchant(sdk.enchant.ManaBurn) && unit.getMobCount(7) > 2) {
- forcePlague = true;
- }
-
- if (useInnerSight) {
- 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);
- }
- }
-
- // Handle Switch casting
- let commonCheck = (gold > 500000 || unit.isBoss || [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area));
- if (me.expansion && index === 1 && unit.curseable) {
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
- && !unit.getState(sdk.states.LowerResist) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast lower resist
- Attack.switchCastCharges(sdk.skills.LowerResist, unit);
- }
-
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken) && !unit.getState(sdk.states.Weaken)
- && !unit.getState(sdk.states.LowerResist) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast weaken
- Attack.switchCastCharges(sdk.skills.Weaken, unit);
- }
- }
-
- // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
- if (Precast.haveCTA > -1 && unit.curseable && (index === 1 || unit.isDoll)
- && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
- Skill.switchCast(sdk.skills.BattleCry, {oSkill: true});
- }
-
- if (useDecoy) {
- // Act Bosses or Immune to my main boss skill
- if ((unit.isPrimeEvil) || !Attack.checkResist(unit, Config.AttackSkill[1])) {
- Misc.poll(() => !me.skillDelay, 1000, 40);
-
- // Don't use decoy if within melee distance
- if (unit.distance > 4) {
- // Check to see if decoy has already been cast
- let decoy = Misc.poll(() => Game.getMonster(sdk.summons.Dopplezon), 1000, 10);
-
- if (!decoy && (getTickCount() - this.decoyTick >= decoyDuration) && unit.distance > 4) {
- if (unit.distance > 10 || checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, 10, sdk.collision.Ranged)) {
- return Attack.Result.FAILED;
- }
- }
-
- let coord = CollMap.getRandCoordinate(unit.x, -2, 2, unit.y, -2, 2);
- !!coord && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, coord.x, coord.y);
-
- // Check if it was a sucess
- !!me.getMinionCount(sdk.summons.type.Dopplezon) && (this.decoyTick = getTickCount());
- }
- }
- }
- }
-
- // Only try attacking light immunes if I have my end game javelin - preAttack with Plague Javelin
- if ((usePlague) && !Attack.checkResist(unit, "lightning")) {
- if ((unit.distance <= 15) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast Slow-Missles, then proceed with Plague Jav. Lowers amount of damage from projectiles.
- !unit.getState(sdk.states.SlowMissiles) && useSlowMissiles && Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit);
-
- // Handle Switch casting
- if (!unit.dead) {
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
- && !unit.getState(sdk.states.LowerResist) && unit.curseable && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast lower resist
- Attack.switchCastCharges(sdk.skills.LowerResist, unit);
- }
- }
-
- if (Attack.checkResist(unit, "poison") && !me.skillDelay && !unit.dead) {
- Skill.cast(sdk.skills.PlagueJavelin, Skill.getHand(sdk.skills.PlagueJavelin), unit);
- }
-
- if (!useJab) {
- // We are within melee distance might as well use jab rather than stand there
- // Make sure monster is not physical immune
- if (unit.distance < 4 && Attack.checkResist(unit, "physical")) {
- if (Skill.canUse(sdk.skills.Jab)) {
- if (unit.distance > 3 || checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, 3, sdk.collision.BlockWall)) {
- return Attack.Result.FAILED;
- }
- }
-
- !unit.dead && Skill.cast(sdk.skills.Jab, Skill.getHand(sdk.skills.Jab), unit);
-
- return Attack.Result.SUCCESS;
- }
- }
-
- return Attack.Result.SUCCESS;
- }
- }
- }
-
- // Only try attacking immunes if I have my end game javelin and they aren't lightning enchanted - use jab as main attack
- if (useJab && !Attack.checkResist(unit, Config.AttackSkill[1]) && Attack.checkResist(unit, "physical") && !unit.getEnchant(sdk.enchant.LightningEnchanted)) {
- if ((unit.distance > 3 || checkCollision(me, unit, sdk.collision.Ranged)) && !Attack.getIntoPosition(unit, 3, sdk.collision.BlockWall)) {
- return Attack.Result.FAILED;
- }
-
- !unit.dead && Skill.cast(sdk.skills.Jab, Skill.getHand(sdk.skills.Jab), unit);
-
- return Attack.Result.SUCCESS;
- }
-
- if (forcePlague && Attack.checkResist(unit, "poison") && !unit.getState(sdk.states.Poison) && !me.skillDelay) {
- if ((unit.distance >= 8 && unit.distance <= 15) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- Skill.cast(sdk.skills.PlagueJavelin, Skill.getHand(sdk.skills.PlagueJavelin), unit);
- }
- }
-
- if (useLightFury) {
- if ((unit.distance >= 8 && unit.distance <= 15) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- Skill.cast(sdk.skills.LightningFury, Skill.getHand(sdk.skills.LightningFury), unit);
- }
- }
-
- if (preattack && Config.AttackSkill[0] > 0 && [sdk.skills.InnerSight, sdk.skills.SlowMissiles].indexOf(Config.AttackSkill[0]) === -1
- && Attack.checkResist(unit, Config.AttackSkill[0]) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) {
- if (unit.distance > preattackRange || checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, preattackRange, 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 skills = this.decideSkill(unit);
-
- const switchBowAttack = (unit, attackSkill) => {
- if (Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) {
- try {
- const checkForShamans = unit.isFallen && !me.inArea(sdk.areas.BloodMoor);
- for (let i = 0; i < 5 && unit.attackable; i++) {
- if (checkForShamans && !once) {
- // before we waste time let's see if there is a shaman we should kill
- const shaman = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.distance < 20 && mon.isShaman && mon.attackable)
- .sort((a, b) => a.distance - b.distance).first();
- if (shaman) return ClassAttack.doAttack(shaman, null, true);
- }
- if (!Attack.useBowOnSwitch(unit, attackSkill, i === 5)) return Attack.Result.FAILED;
- if (unit.distance < 8 || me.inDanger()) {
- if (once) return Attack.Result.FAILED;
- let closeMob = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.distance < 10 && mon.attackable && mon.gid !== gid)
- .sort(Attack.walkingSortMonsters).first();
- if (closeMob) return ClassAttack.doAttack(closeMob, null, true);
- }
- }
- } finally {
- me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
- }
- }
- return unit.dead ? Attack.Result.SUCCESS : Attack.Result.FAILED;
- };
-
- // @todo damage/effort comparison vs our normal skill
- if (CharData.skillData.bowData.bowOnSwitch
- && (index !== 1 || !unit.name.includes(getLocaleString(sdk.locale.text.Ghostly)))
- && (unit.distance >= 8 || (unit.isMoving && (unit.targetx !== me.x || unit.targety !== me.y)))
- && ([sdk.skills.Attack, sdk.skills.Jab].includes(skills.timed) || Skill.getManaCost(skills.timed) > me.mp)) {
- let arrowSkill = (() => {
- // todo - better determination of skills
- if (Skill.canUse(sdk.skills.MagicArrow) && Skill.getManaCost(sdk.skills.MagicArrow) < me.mp) return sdk.skills.MagicArrow;
- if (Skill.canUse(sdk.skills.FireArrow) && Skill.getManaCost(sdk.skills.FireArrow) < me.mp) return sdk.skills.FireArrow;
- return 0;
- })();
- if (switchBowAttack(unit, arrowSkill) === Attack.Result.SUCCESS) return Attack.Result.SUCCESS;
- }
-
- if ([sdk.skills.Attack, sdk.skills.Jab].includes(skills.timed)
- && (unit.distance >= 12 || (unit.distance > 4 && unit.isMoving && (unit.targetx !== me.x || unit.targety !== me.y)) || unit.coldEnchanted)) {
- let item = me.getItemsEx().filter(item => item.isEquipped && item.bodylocation === sdk.body.RightArm).first();
- if (item && (item.getStat(sdk.stats.Quantity) * 100 / getBaseStat("items", item.classid, "maxstack")) > 30) {
- skills.timed = sdk.skills.Throw;
- }
- }
-
- 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 && 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.canUse(sdk.skills.Decoy) && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, unit));
- }
- }
-
- return Attack.Result.SUCCESS;
- }
-
- return result;
+ // unit became invalidated
+ if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
+
+ let gid = unit.gid;
+ let needRepair = me.charlvl < 5 ? [] : 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;
+ }
+ }
+ }
+
+ let gold = me.gold;
+ let preattackRange = Skill.getRange(Config.AttackSkill[0]);
+ let decoyDuration = Skill.getDuration(sdk.skills.Dopplezon);
+ const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
+ const useSkills = {
+ InnerSight: false,
+ SlowMissiles: false,
+ Jab: false,
+ Plague: false,
+ LightFury: false,
+ };
+
+ useSkills.InnerSight = Skill.canUse(sdk.skills.InnerSight);
+ useSkills.SlowMissiles = Skill.canUse(sdk.skills.SlowMissiles);
+ useSkills.Decoy = (Skill.canUse(sdk.skills.Dopplezon) && !me.normal);
+
+ // check weapon
+ let [allowThrowing, forcePlague] = [false, false];
+ let equippedWep = me.getEquippedItem(sdk.body.RightArm);
+ if (equippedWep) {
+ allowThrowing = (equippedWep.ethereal && equippedWep.quantityPercent > 25);
+ useSkills.Jab = (NTIP.GetTier(equippedWep) >= 1000 && Skill.canUse(sdk.skills.Jab));
+ if (allowThrowing) {
+ useSkills.Plague = (!me.normal && Skill.canUse(sdk.skills.PlagueJavelin));
+ useSkills.LightFury = (me.getSkill(sdk.skills.LightningFury, sdk.skills.subindex.SoftPoints) >= 10);
+ forcePlague = (me.getSkill(sdk.skills.PlagueJavelin, sdk.skills.subindex.SoftPoints) >= 15); // Extra poison damage then attack
+ }
+ } else {
+ console.warn("We don't have a weapon?");
+ console.debug("Go to town, maybe can get one.");
+ Town.visitTown(true);
+ // we are probably screwed if we can't get a weapon, maybe go back a difficulty?
+ }
+
+ // Precast Section -----------------------------------------------------------------------------------------------------------------//
+ if (useSkills.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 (useSkills.InnerSight && !unit.getState(sdk.states.InnerSight)) {
+ Skill.cast(sdk.skills.InnerSight, sdk.skills.hand.Right, unit);
+ }
+ } else {
+ Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit);
+ }
+ }
+ }
+ }
+
+ if (allowThrowing
+ && Skill.canUse(sdk.skills.LightningFury)
+ && unit.getEnchant(sdk.enchant.ManaBurn)
+ && unit.getMobCount(7) > 2) {
+ useSkills.LightFury = true;
+ }
+
+ if (allowThrowing
+ && Skill.canUse(sdk.skills.PlagueJavelin)
+ && unit.getEnchant(sdk.enchant.ManaBurn)
+ && unit.getMobCount(7) > 2) {
+ forcePlague = true;
+ }
+
+ if (useSkills.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);
+ }
+ }
+
+ // Handle Switch casting
+ const commonCheck = (
+ gold > 500000 || unit.isBoss || [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area)
+ );
+ if (me.expansion && index === 1 && unit.curseable) {
+ if (commonCheck && CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
+ && !unit.getState(sdk.states.LowerResist)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast lower resist
+ Attack.switchCastCharges(sdk.skills.LowerResist, unit);
+ }
+
+ if (commonCheck && CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
+ && !unit.getState(sdk.states.Weaken)
+ && !unit.getState(sdk.states.LowerResist)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast weaken
+ Attack.switchCastCharges(sdk.skills.Weaken, unit);
+ }
+ }
+
+ // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
+ if (Precast.haveCTA > -1 && unit.curseable && (index === 1 || unit.isDoll)
+ && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
+ Skill.switchCast(sdk.skills.BattleCry, { oSkill: true });
+ }
+
+ if (useSkills.Decoy) {
+ // Act Bosses or Immune to my main boss skill
+ if ((unit.isPrimeEvil) || !Attack.checkResist(unit, Config.AttackSkill[1])) {
+ Misc.poll(function () {
+ return !me.skillDelay;
+ }, 1000, 40);
+
+ // Don't use decoy if within melee distance
+ if (unit.distance > 4) {
+ // Check to see if decoy has already been cast
+ let decoy = Misc.poll(function () {
+ return Game.getMonster(sdk.summons.Dopplezon);
+ }, 1000, 10);
+
+ if (!decoy && (getTickCount() - this.decoyTick >= decoyDuration) && unit.distance > 4) {
+ if (unit.distance > 10 || checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, 10, sdk.collision.Ranged)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ let coord = CollMap.getRandCoordinate(unit.x, -2, 2, unit.y, -2, 2);
+ !!coord && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, coord.x, coord.y);
+
+ // Check if it was a sucess
+ !!me.getMinionCount(sdk.summons.type.Dopplezon) && (this.decoyTick = getTickCount());
+ }
+ }
+ }
+ }
+
+ // Only try attacking light immunes if I have my end game javelin - preAttack with Plague Javelin
+ if ((useSkills.Plague) && !Attack.checkResist(unit, "lightning")) {
+ if ((unit.distance <= 15) && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast Slow-Missles, then proceed with Plague Jav. Lowers amount of damage from projectiles.
+ if (!unit.getState(sdk.states.SlowMissiles) && useSkills.SlowMissiles) {
+ Skill.cast(sdk.skills.SlowMissiles, sdk.skills.hand.Right, unit);
+ }
+
+ // Handle Switch casting
+ if (!unit.dead) {
+ if (commonCheck && CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
+ && !unit.getState(sdk.states.LowerResist)
+ && unit.curseable
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast lower resist
+ Attack.switchCastCharges(sdk.skills.LowerResist, unit);
+ }
+ }
+
+ if (Attack.checkResist(unit, "poison") && !me.skillDelay && !unit.dead) {
+ Skill.cast(sdk.skills.PlagueJavelin, Skill.getHand(sdk.skills.PlagueJavelin), unit);
+ }
+
+ if (!useSkills.Jab) {
+ // We are within melee distance might as well use jab rather than stand there
+ // Make sure monster is not physical immune
+ if (unit.distance < 4 && Attack.checkResist(unit, "physical")) {
+ if (Skill.canUse(sdk.skills.Jab)) {
+ if (unit.distance > 3 || checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, 3, sdk.collision.BlockWall)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ !unit.dead && Skill.cast(sdk.skills.Jab, Skill.getHand(sdk.skills.Jab), unit);
+
+ return Attack.Result.SUCCESS;
+ }
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+ }
+ }
+
+ // Only try attacking immunes if I have my end game javelin and they aren't lightning enchanted - use jab as main attack
+ if (useSkills.Jab
+ && !Attack.checkResist(unit, Config.AttackSkill[1])
+ && Attack.checkResist(unit, "physical")
+ && !unit.getEnchant(sdk.enchant.LightningEnchanted)) {
+ if ((unit.distance > 3 || checkCollision(me, unit, sdk.collision.Ranged))
+ && !Attack.getIntoPosition(unit, 3, sdk.collision.BlockWall)) {
+ return Attack.Result.FAILED;
+ }
+
+ !unit.dead && Skill.cast(sdk.skills.Jab, Skill.getHand(sdk.skills.Jab), unit);
+
+ return Attack.Result.SUCCESS;
+ }
+
+ if (forcePlague && Attack.checkResist(unit, "poison") && !unit.getState(sdk.states.Poison) && !me.skillDelay) {
+ if ((unit.distance >= 8 && unit.distance <= 15) && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ Skill.cast(sdk.skills.PlagueJavelin, Skill.getHand(sdk.skills.PlagueJavelin), unit);
+ }
+ }
+
+ if (useSkills.LightFury) {
+ if ((unit.distance >= 8 && unit.distance <= 15) && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ Skill.cast(sdk.skills.LightningFury, Skill.getHand(sdk.skills.LightningFury), unit);
+ }
+ }
+
+ if (preattack && Config.AttackSkill[0] > 0
+ && [sdk.skills.InnerSight, sdk.skills.SlowMissiles].indexOf(Config.AttackSkill[0]) === -1
+ && Attack.checkResist(unit, Config.AttackSkill[0]) && (!me.skillDelay || !Skill.isTimed(Config.AttackSkill[0]))) {
+ if (unit.distance > preattackRange || checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, preattackRange, 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 skills = this.decideSkill(unit);
+
+ const switchBowAttack = function (unit, attackSkill) {
+ if (Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) {
+ try {
+ const checkForShamans = unit.isFallen && !me.inArea(sdk.areas.BloodMoor);
+ for (let i = 0; i < 5 && unit.attackable; i++) {
+ if (checkForShamans && !once) {
+ // before we waste time let's see if there is a shaman we should kill
+ const shaman = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.distance < 20 && mon.isShaman && mon.attackable;
+ })
+ .sort(Sort.units)
+ .first();
+ if (shaman) return ClassAttack.doAttack(shaman, null, true);
+ }
+ if (!Attack.useBowOnSwitch(unit, attackSkill, i === 5)) return Attack.Result.FAILED;
+ if (unit.distance < 8 || me.inDanger()) {
+ if (once) return Attack.Result.FAILED;
+ let closeMob = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.distance < 10 && mon.attackable && mon.gid !== gid;
+ })
+ .sort(Attack.walkingSortMonsters)
+ .first();
+ if (closeMob) return ClassAttack.doAttack(closeMob, null, true);
+ }
+ }
+ } finally {
+ me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
+ }
+ }
+ return unit.dead ? Attack.Result.SUCCESS : Attack.Result.FAILED;
+ };
+
+ // @todo damage/effort comparison vs our normal skill
+ if (CharData.skillData.bow.onSwitch
+ && (index !== 1 || !unit.name.includes(getLocaleString(sdk.locale.text.Ghostly)))
+ && (unit.distance >= 8 || (unit.isMoving && (unit.targetx !== me.x || unit.targety !== me.y)))
+ && ([sdk.skills.Attack, sdk.skills.Jab].includes(skills.timed) || Skill.getManaCost(skills.timed) > me.mp)) {
+ let arrowSkill = (function () {
+ // todo - better determination of skills
+ if (Skill.canUse(sdk.skills.MagicArrow) && Skill.getManaCost(sdk.skills.MagicArrow) < me.mp) {
+ return sdk.skills.MagicArrow;
+ }
+ if (Skill.canUse(sdk.skills.FireArrow) && Skill.getManaCost(sdk.skills.FireArrow) < me.mp) {
+ return sdk.skills.FireArrow;
+ }
+ return 0;
+ })();
+ if (switchBowAttack(unit, arrowSkill) === Attack.Result.SUCCESS) return Attack.Result.SUCCESS;
+ }
+
+ if ([sdk.skills.Attack, sdk.skills.Jab].includes(skills.timed)
+ && (
+ unit.distance >= 12
+ || (unit.distance > 4 && unit.isMoving && (unit.targetx !== me.x || unit.targety !== me.y))
+ || unit.coldEnchanted
+ )) {
+ let item = me.getEquippedItem(sdk.body.RightArm);
+ if (item && (item.getStat(sdk.stats.Quantity) * 100 / getBaseStat("items", item.classid, "maxstack")) > 30) {
+ skills.timed = sdk.skills.Throw;
+ }
+ }
+
+ 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) 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 && 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) !== 1) {
+ (Skill.canUse(sdk.skills.Decoy) && Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, unit));
+ }
+ }
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ return result;
};
ClassAttack.afterAttack = function () {
- Precast.doPrecast(false);
+ Precast.doPrecast(false);
- let needRepair = me.needRepair();
-
- // Repair check, make sure i have a tome
- if (needRepair.length > 0 && me.canTpToTown()) {
- Town.visitTown(true);
- }
+ let needRepair = me.needRepair();
+
+ // Repair check, make sure i have a tome
+ if (needRepair.length > 0 && me.canTpToTown()) {
+ Town.visitTown(true);
+ }
- this.lightFuryTick = 0;
+ ClassAttack.lightFuryTick = 0;
};
-// Returns: 0 - fail, 1 - success, 2 - no valid attack skills
+/**
+ * @param {Monster} unit
+ * @param {number} timedSkill
+ * @param {number} untimedSkill
+ * @returns {AttackResult}
+ */
ClassAttack.doCast = function (unit, timedSkill, untimedSkill) {
- // 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;
- me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
-
- let walk;
-
- // 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;
- }
- }
-
- if (timedSkill > -1 && (!me.getState(sdk.states.SkillDelay) || !Skill.isTimed(timedSkill))) {
- switch (timedSkill) {
- case sdk.skills.Throw:
- case sdk.skills.PlagueJavelin:
- case sdk.skills.LightningFury:
- if (timedSkill === sdk.skills.LightningFury && this.lightFuryTick && getTickCount() - this.lightFuryTick < Time.seconds(Config.LightningFuryDelay)) {
- break;
- }
- let tsRange = timedSkill === sdk.skills.Throw && (unit.isShaman || unit.isUnraveler) ? Skill.getRange(timedSkill) - 5 : Skill.getRange(timedSkill);
- if (unit.distance > Skill.getRange(tsRange) || checkCollision(me, unit, sdk.collision.BlockMissile)) {
- if (!Attack.getIntoPosition(unit, Skill.getRange(tsRange), sdk.collision.BlockMissile)) {
- return Attack.Result.FAILED;
- }
- }
-
- let preHealth = unit.hp;
- let targetPoint = GameData.targetPointForSkill(timedSkill, unit);
-
- if (unit.attackable) {
- if (targetPoint) {
- Skill.cast(timedSkill, Skill.getHand(timedSkill), targetPoint.x, targetPoint.y);
- } else {
- Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
- }
- if (Misc.poll(() => unit.dead || unit.hp < preHealth, 300, 50)) {
- timedSkill === sdk.skills.LightningFury && (this.lightFuryTick = getTickCount());
- }
- }
-
- break;
- default:
- // If main attack skill is lightning strike and charged strike's skill level is at least level 15, check current monster count. If monster count is less than 3, use CS as its more effective with small mobs
- if (timedSkill === sdk.skills.LightningStrike && me.getSkill(sdk.skills.ChargedStrike, sdk.skills.subindex.SoftPoints) >= 15) {
- if (me.getMobCount(15, Coords_1.BlockBits.LineOfSight | Coords_1.BlockBits.Ranged | Coords_1.BlockBits.ClosedDoor | Coords_1.BlockBits.BlockWall) <= 3) {
- timedSkill = sdk.skills.ChargedStrike;
- }
- }
-
- if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y)) 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)) 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 (this.lightFuryTick && getTickCount() - this.lightFuryTick < Config.LightningFuryDelay * 1000) {
- delay(40);
- }
-
- return Attack.Result.SUCCESS;
+ // 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;
+ me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
+
+ let walk;
+
+ // 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;
+ }
+ }
+
+ if (timedSkill > -1 && (!me.getState(sdk.states.SkillDelay) || !Skill.isTimed(timedSkill))) {
+ switch (timedSkill) {
+ case sdk.skills.Throw:
+ case sdk.skills.PlagueJavelin:
+ case sdk.skills.LightningFury:
+ if (timedSkill === sdk.skills.LightningFury
+ && this.lightFuryTick
+ && getTickCount() - this.lightFuryTick < Time.seconds(Config.LightningFuryDelay)) {
+ break;
+ }
+ let tsRange = timedSkill === sdk.skills.Throw && (unit.isShaman || unit.isUnraveler)
+ ? Skill.getRange(timedSkill) - 5
+ : Skill.getRange(timedSkill);
+ if (unit.distance > Skill.getRange(tsRange) || checkCollision(me, unit, sdk.collision.BlockMissile)) {
+ if (!Attack.getIntoPosition(unit, Skill.getRange(tsRange), sdk.collision.BlockMissile)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ let preHealth = unit.hp;
+ let targetPoint = GameData.targetPointForSkill(timedSkill, unit);
+
+ if (unit.attackable) {
+ if (targetPoint) {
+ Skill.cast(timedSkill, Skill.getHand(timedSkill), targetPoint.x, targetPoint.y);
+ } else {
+ Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
+ }
+ if (Misc.poll(() => unit.dead || unit.hp < preHealth, 300, 50)) {
+ timedSkill === sdk.skills.LightningFury && (this.lightFuryTick = getTickCount());
+ }
+ }
+
+ break;
+ default:
+ // If main attack skill is lightning strike and charged strike's skill level is at least level 15, check current monster count. If monster count is less than 3, use CS as its more effective with small mobs
+ if (timedSkill === sdk.skills.LightningStrike
+ && me.getSkill(sdk.skills.ChargedStrike, sdk.skills.subindex.SoftPoints) >= 15) {
+ if (me.getMobCount(15, Coords_1.BlockBits.LineOfSight | Coords_1.BlockBits.Ranged | Coords_1.BlockBits.ClosedDoor | Coords_1.BlockBits.BlockWall) <= 3) {
+ timedSkill = sdk.skills.ChargedStrike;
+ }
+ }
+
+ if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y)) 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)) 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(function () {
+ return !me.skillDelay;
+ }, 1000, 40);
+
+ // Wait for Lightning Fury timeout
+ while (this.lightFuryTick && getTickCount() - this.lightFuryTick < Config.LightningFuryDelay * 1000) {
+ delay(40);
+ }
+
+ return Attack.Result.SUCCESS;
};
diff --git a/libs/SoloPlay/Functions/ClassAttackOverrides/AssassinAttacks.js b/libs/SoloPlay/Functions/ClassAttackOverrides/AssassinAttacks.js
index 48f3d16f..bfb45d7b 100644
--- a/libs/SoloPlay/Functions/ClassAttackOverrides/AssassinAttacks.js
+++ b/libs/SoloPlay/Functions/ClassAttackOverrides/AssassinAttacks.js
@@ -10,275 +10,269 @@
* Test utilizing marital art skills if we have them
*/
-includeIfNotIncluded("common/Attacks/Assassin.js");
+includeIfNotIncluded("core/Attacks/Assassin.js");
ClassAttack.mindBlast = function (unit) {
- if (!unit || !Skill.canUse(sdk.skills.MindBlast)) return;
- // Main bosses
- if (unit.isPrimeEvil) return;
- // Duriel's Lair, Arreat Summit, Worldstone Chamber
- if ([sdk.areas.DurielsLair, sdk.areas.ArreatSummit, sdk.areas.WorldstoneChamber].includes(me.area)) return;
-
- const mindBlastMpCost = Skill.getManaCost(sdk.skills.MindBlast);
- let list = getUnits(sdk.unittype.Monster)
- .filter(function (mob) {
- if (mob.attackable && !mob.isStunned && !mob.isUnderLowerRes && !mob.isUnique) {
- let dist = mob.distance;
- return (dist <= 6 || (dist >= 20 && dist <= 30));
- }
- return false;
- })
- .sort(Sort.units);
-
- if (list.length >= 1) {
- for (let i = 0; i < list.length; i++) {
- if (!list[i].dead && !checkCollision(me, list[i], sdk.collision.BlockWall) && me.mp > mindBlastMpCost * 2) {
- me.overhead("MindBlasting " + list[i].name);
- Skill.cast(sdk.skills.MindBlast, sdk.skills.hand.Right, list[i]);
- }
- }
- }
+ if (!unit || !Skill.canUse(sdk.skills.MindBlast)) return;
+ // Main bosses
+ if (unit.isPrimeEvil) return;
+ // Duriel's Lair, Arreat Summit, Worldstone Chamber
+ if ([sdk.areas.DurielsLair, sdk.areas.ArreatSummit, sdk.areas.WorldstoneChamber].includes(me.area)) return;
+
+ const mindBlastMpCost = Skill.getManaCost(sdk.skills.MindBlast);
+ let list = getUnits(sdk.unittype.Monster)
+ .filter(function (mob) {
+ if (mob.attackable && !mob.isStunned && !mob.isUnderLowerRes && !mob.isUnique) {
+ let dist = mob.distance;
+ return (dist <= 6 || (dist >= 20 && dist <= 30));
+ }
+ return false;
+ })
+ .sort(Sort.units);
+
+ if (list.length >= 1) {
+ for (let i = 0; i < list.length; i++) {
+ if (!list[i].dead && !checkCollision(me, list[i], sdk.collision.BlockWall) && me.mp > mindBlastMpCost * 2) {
+ me.overhead("MindBlasting " + list[i].name);
+ Skill.cast(sdk.skills.MindBlast, sdk.skills.hand.Right, list[i]);
+ }
+ }
+ }
};
ClassAttack.switchCurse = function (unit, force) {
- if (CharData.skillData.haveChargedSkill([sdk.skills.SlowMissiles, sdk.skills.LowerResist, sdk.skills.Weaken]) && unit.curseable) {
- const gold = me.gold;
- const isBoss = unit.isBoss;
- const dangerZone = [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area);
- if (force && checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, 35, sdk.collision.Ranged)) return;
- }
- // If we have slow missles we might as well use it, currently only on Lighting Enchanted mobs as they are dangerous
- // Might be worth it to use on souls too TODO: test this idea
- if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles) && gold > 500000 && !isBoss
- && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
- && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast slow missiles
- Attack.castCharges(sdk.skills.SlowMissiles, unit);
- }
- // Handle Switch casting
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
- && (gold > 500000 || isBoss || dangerZone)
- && !unit.getState(sdk.states.LowerResist)
- && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast lower resist
- Attack.switchCastCharges(sdk.skills.LowerResist, unit);
- }
-
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
- && (gold > 500000 || isBoss || dangerZone)
- && !unit.getState(sdk.states.Weaken) && !unit.getState(sdk.states.LowerResist)
- && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast weaken
- Attack.switchCastCharges(sdk.skills.Weaken, unit);
- }
- }
+ if (CharData.skillData.haveChargedSkill([sdk.skills.SlowMissiles, sdk.skills.LowerResist, sdk.skills.Weaken]) && unit.curseable) {
+ const gold = me.gold;
+ const isBoss = unit.isBoss;
+ const dangerZone = [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area);
+ if (force && checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, 35, sdk.collision.Ranged)) return;
+ }
+ // If we have slow missles we might as well use it, currently only on Lighting Enchanted mobs as they are dangerous
+ // Might be worth it to use on souls too TODO: test this idea
+ if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles) && gold > 500000 && !isBoss
+ && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast slow missiles
+ Attack.castCharges(sdk.skills.SlowMissiles, unit);
+ }
+ // Handle Switch casting
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
+ && (gold > 500000 || isBoss || dangerZone)
+ && !unit.getState(sdk.states.LowerResist)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast lower resist
+ Attack.switchCastCharges(sdk.skills.LowerResist, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
+ && (gold > 500000 || isBoss || dangerZone)
+ && !unit.getState(sdk.states.Weaken) && !unit.getState(sdk.states.LowerResist)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast weaken
+ Attack.switchCastCharges(sdk.skills.Weaken, unit);
+ }
+ }
};
ClassAttack.placeTraps = function (unit, amount) {
- 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
- 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;
-
- switch (Config.Traps[traps]) {
- case sdk.skills.ChargedBoltSentry:
- case sdk.skills.LightningSentry:
- // Immune to lightning but not immune to fire, use fire trap if available
- if (!Attack.checkResist(unit, "lightning") && Attack.checkResist(unit, "fire")) {
- if (Skill.canUse(sdk.skills.WakeofFire)) {
- Skill.cast(sdk.skills.WakeofFire, sdk.skills.hand.Right, unit.x + i, unit.y + j);
- } else if (Skill.canUse(sdk.skills.WakeofInferno)) {
- Skill.cast(sdk.skills.WakeofInferno, sdk.skills.hand.Right, unit.x + i, unit.y + j);
- }
-
- break;
- } else {
- Skill.cast(Config.Traps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j);
- }
-
- break;
- case sdk.skills.WakeofFire:
- case sdk.skills.WakeofInferno:
- // Immune to fire but not immune to lightning, use light trap if available
- if (!Attack.checkResist(unit, "fire") && Attack.checkResist(unit, "lightning")) {
- if (Skill.canUse(sdk.skills.LightningSentry)) {
- Skill.cast(sdk.skills.LightningSentry, sdk.skills.hand.Right, unit.x + i, unit.y + j);
- } else if (Skill.canUse(sdk.skills.ChargedBoltSentry)) {
- Skill.cast(sdk.skills.ChargedBoltSentry, sdk.skills.hand.Right, unit.x + i, unit.y + j);
- }
-
- break;
- } else {
- Skill.cast(Config.Traps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j);
- }
-
- break;
- default:
- Skill.cast(Config.Traps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j);
-
- break;
- }
- }
-
- traps += 1;
- }
- }
- }
-
- return true;
+ 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
+ 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;
+
+ switch (Config.Traps[traps]) {
+ case sdk.skills.ChargedBoltSentry:
+ case sdk.skills.LightningSentry:
+ // Immune to lightning but not immune to fire, use fire trap if available
+ if (!Attack.checkResist(unit, "lightning") && Attack.checkResist(unit, "fire")) {
+ if (Skill.canUse(sdk.skills.WakeofFire)) {
+ Skill.cast(sdk.skills.WakeofFire, sdk.skills.hand.Right, unit.x + i, unit.y + j);
+ } else if (Skill.canUse(sdk.skills.WakeofInferno)) {
+ Skill.cast(sdk.skills.WakeofInferno, sdk.skills.hand.Right, unit.x + i, unit.y + j);
+ }
+
+ break;
+ } else {
+ Skill.cast(Config.Traps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j);
+ }
+
+ break;
+ case sdk.skills.WakeofFire:
+ case sdk.skills.WakeofInferno:
+ // Immune to fire but not immune to lightning, use light trap if available
+ if (!Attack.checkResist(unit, "fire") && Attack.checkResist(unit, "lightning")) {
+ if (Skill.canUse(sdk.skills.LightningSentry)) {
+ Skill.cast(sdk.skills.LightningSentry, sdk.skills.hand.Right, unit.x + i, unit.y + j);
+ } else if (Skill.canUse(sdk.skills.ChargedBoltSentry)) {
+ Skill.cast(sdk.skills.ChargedBoltSentry, sdk.skills.hand.Right, unit.x + i, unit.y + j);
+ }
+
+ break;
+ } else {
+ Skill.cast(Config.Traps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j);
+ }
+
+ break;
+ default:
+ Skill.cast(Config.Traps[traps], sdk.skills.hand.Right, unit.x + i, unit.y + j);
+
+ break;
+ }
+ }
+
+ traps += 1;
+ }
+ }
+ }
+
+ return true;
};
ClassAttack.doAttack = function (unit, preattack) {
- if (!unit) return Attack.Result.SUCCESS;
- 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;
- }
- }
- }
-
- let mercRevive = 0;
- let shouldUseCloak = (Skill.canUse(sdk.skills.CloakofShadows) && !unit.isUnderLowerRes && unit.getMobCount(15, sdk.collision.BlockWall) > 1);
- const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
-
- this.mindBlast(unit);
-
- 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;
- }
-
- // 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 && Config.UseCloakofShadows && shouldUseCloak && !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 && Config.UseCloakofShadows && shouldUseCloak && unit.distance < 20 && !me.skillDelay && !me.getState(sdk.states.CloakofShadows)) {
- Skill.cast(sdk.skills.CloakofShadows, sdk.skills.hand.Right);
- }
-
- // Handle Switch casting
- if (index === 1 && !unit.dead) {
- ClassAttack.switchCurse(unit);
- }
-
- let skills = Attack.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 && Pather.walkTo(spot.x, spot.y);
- }
-
- let closeMob = Attack.getNearestMonster({skipGid: gid});
- !!closeMob && this.doCast(closeMob, skills.timed, skills.untimed);
- }
-
- return Attack.Result.SUCCESS;
- }
-
- return result;
+ if (!unit) return Attack.Result.SUCCESS;
+ 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;
+ }
+ }
+ }
+
+ let mercRevive = 0;
+ let shouldUseCloak = (Skill.canUse(sdk.skills.CloakofShadows) && !unit.isUnderLowerRes && unit.getMobCount(15, sdk.collision.BlockWall) > 1);
+ const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
+
+ this.mindBlast(unit);
+
+ 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;
+ }
+
+ // 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 && Config.UseCloakofShadows && shouldUseCloak && !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 && Config.UseCloakofShadows && shouldUseCloak && unit.distance < 20 && !me.skillDelay && !me.getState(sdk.states.CloakofShadows)) {
+ Skill.cast(sdk.skills.CloakofShadows, sdk.skills.hand.Right);
+ }
+
+ // Handle Switch casting
+ if (index === 1 && !unit.dead) {
+ ClassAttack.switchCurse(unit);
+ }
+
+ let skills = Attack.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) 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 && Pather.walkTo(spot.x, spot.y);
+ }
+
+ let closeMob = Attack.getNearestMonster({ skipGid: gid });
+ !!closeMob && this.doCast(closeMob, skills.timed, skills.untimed);
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ return result;
};
ClassAttack.farCast = function (unit) {
- let timedSkill = Config.AttackSkill[1], untimedSkill = Config.AttackSkill[2];
+ let timedSkill = Config.AttackSkill[1], untimedSkill = Config.AttackSkill[2];
- // No valid skills can be found
- if (timedSkill < 0 && untimedSkill < 0) return false;
+ // No valid skills can be found
+ if (timedSkill < 0 && untimedSkill < 0) return false;
- let checkTraps = this.checkTraps(unit);
+ let checkTraps = this.checkTraps(unit);
- if (checkTraps) {
- if (unit.distance > 30 || checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, 30, sdk.collision.Ranged) || (checkCollision(me, unit, sdk.collision.BlockWall) && (getCollision(me.area, unit.x, unit.y) & sdk.collision.BlockWall))) {
- return false;
- }
- }
+ if (checkTraps) {
+ if (unit.distance > 30 || checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, 30, sdk.collision.Ranged) || (checkCollision(me, unit, sdk.collision.BlockWall) && (getCollision(me.area, unit.x, unit.y) & sdk.collision.BlockWall))) {
+ return false;
+ }
+ }
- this.placeTraps(unit, checkTraps);
- }
+ this.placeTraps(unit, checkTraps);
+ }
- ClassAttack.switchCurse(unit);
+ ClassAttack.switchCurse(unit);
- if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) {
- !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
- }
+ if (timedSkill > -1 && (!me.skillDelay || !Skill.isTimed(timedSkill))) {
+ !unit.dead && Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
+ }
- if (untimedSkill > -1) {
- !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit);
- }
+ if (untimedSkill > -1) {
+ !unit.dead && Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit);
+ }
- return true;
+ return true;
};
diff --git a/libs/SoloPlay/Functions/ClassAttackOverrides/BarbarianAttacks.js b/libs/SoloPlay/Functions/ClassAttackOverrides/BarbarianAttacks.js
index f59f796c..d819b330 100644
--- a/libs/SoloPlay/Functions/ClassAttackOverrides/BarbarianAttacks.js
+++ b/libs/SoloPlay/Functions/ClassAttackOverrides/BarbarianAttacks.js
@@ -5,7 +5,7 @@
*
*/
-includeIfNotIncluded("common/Attacks/Barbarian.js");
+includeIfNotIncluded("core/Attacks/Barbarian.js");
/**
* @todo:
@@ -13,428 +13,515 @@ includeIfNotIncluded("common/Attacks/Barbarian.js");
* - use leap attack with getIntoPosition, long distance or when targetting summoners
* - use leap/leap attack with dodge, useful if we can't tele it provides a similar benefit
*/
-
-ClassAttack.warCryTick = 0;
-
-const howlCheck = function () {
- let levelCheck = (me.getSkill(sdk.skills.Howl, sdk.skills.subindex.SoftPoints) + me.charlvl + 1);
- return getUnits(sdk.unittype.Monster).filter(function (el) {
- return (!!el && el.attackable && el.distance < 6 && el.scareable && GameData.monsterLevel(el.classid, me.area) < levelCheck && !el.isStunned
- && [sdk.states.BattleCry, sdk.states.AmplifyDamage, sdk.states.Decrepify, sdk.states.Terror, sdk.states.Taunt].every(state => !el.getState(state))
- && !checkCollision(me, el, Coords_1.Collision.BLOCK_MISSILE));
- }).length > me.maxNearMonsters;
-};
-
-const battleCryCheck = function () {
- return getUnits(sdk.unittype.Monster).some(function (el) {
- if (el === undefined) return false;
- return (el.attackable && el.distance < 5 && el.curseable
- && [sdk.states.BattleCry, sdk.states.AmplifyDamage, sdk.states.Decrepify, sdk.states.Terror, sdk.states.Taunt].every(state => !el.getState(state))
- && !checkCollision(me, el, Coords_1.Collision.BLOCK_MISSILE));
- });
-};
-
-const warCryCheck = function () {
- return getUnits(sdk.unittype.Monster).some(function (el) {
- if (el === undefined) return false;
- return (el.attackable && el.distance < 5 && !(el.isSpecial) && el.curseable
- && ![sdk.monsters.Andariel, sdk.monsters.Duriel, sdk.monsters.Mephisto, sdk.monsters.Diablo, sdk.monsters.Baal, sdk.monsters.Tentacle1,
- sdk.monsters.BaalClone, sdk.monsters.KorlictheProtector, sdk.monsters.TalictheDefender, sdk.monsters.MadawctheGuardian].includes(el.classid)
- && (!el.isStunned || getTickCount() - ClassAttack.warCryTick >= 1500) && !checkCollision(me, el, Coords_1.Collision.BLOCK_MISSILE));
- });
-};
-
-ClassAttack.tauntMonsters = function (unit, attackSkill, data) {
- // Don't have skill
- // Only mob in these areas are bosses
- // Can't taunt Main bosses or MinionsofDestruction
- if (!Skill.canUse(sdk.skills.Taunt) || !data) return;
- if ([sdk.areas.DurielsLair, sdk.areas.ArreatSummit, sdk.areas.WorldstoneChamber].includes(me.area)) return;
- if (unit.isPrimeEvil || unit.classid === sdk.monsters.ListerTheTormenter) return;
-
- let range = (!me.inArea(sdk.areas.ThroneofDestruction) ? 15 : 30);
- let rangedMobsClassIDs = [
- sdk.monsters.Afflicted, sdk.monsters.Tainted, sdk.monsters.Misshapen1, sdk.monsters.Disfigured, sdk.monsters.Damned1, sdk.monsters.Gloam1, sdk.monsters.SwampGhost,
- sdk.monsters.BurningSoul2, sdk.monsters.BlackSoul1, sdk.monsters.GhoulLord1, sdk.monsters.NightLord, sdk.monsters.DarkLord1, sdk.monsters.BloodLord1,
- sdk.monsters.Banished, sdk.monsters.SkeletonArcher, sdk.monsters.ReturnedArcher1, sdk.monsters.BoneArcher1, sdk.monsters.BurningDeadArcher1, sdk.monsters.HorrorArcher1,
- sdk.monsters.Sexton, sdk.monsters.Cantor, sdk.monsters.Heirophant1, sdk.monsters.DoomKnight, sdk.monsters.VenomLord1, sdk.monsters.Horror1, sdk.monsters.Horror2,
- sdk.monsters.Horror3, sdk.monsters.Horror4, sdk.monsters.Horror5, sdk.monsters.Lord1, sdk.monsters.Lord2, sdk.monsters.Lord3, sdk.monsters.Lord4,
- sdk.monsters.Lord4, sdk.monsters.Afflicted2, sdk.monsters.Tainted, sdk.monsters.Misshapen2, sdk.monsters.Disfigured2, sdk.monsters.Damned2, sdk.monsters.DarkShaman2,
- sdk.monsters.DevilkinShaman, sdk.monsters.DarkShaman2, sdk.monsters.DarkLord2
- ];
- let dangerousAndSummoners = [
- sdk.monsters.Dominus2, sdk.monsters.Witch1, sdk.monsters.VileWitch2, sdk.monsters.Gloam2, sdk.monsters.BlackSoul2, sdk.monsters.BurningSoul1,
- sdk.monsters.FallenShaman, sdk.monsters.CarverShaman2, sdk.monsters.DevilkinShaman2, sdk.monsters.DarkShaman1, sdk.monsters.HollowOne, sdk.monsters.Guardian1,
- sdk.monsters.Unraveler1, sdk.monsters.Ancient1, sdk.monsters.BaalSubjectMummy, sdk.monsters.Council4, sdk.monsters.VenomLord2, sdk.monsters.Ancient2,
- sdk.monsters.Ancient3, sdk.monsters.Succubusexp1, sdk.monsters.VileTemptress, sdk.monsters.StygianHarlot, sdk.monsters.Temptress1, sdk.monsters.Temptress2,
- sdk.monsters.Dominus1, sdk.monsters.VileWitch1, sdk.monsters.StygianFury, sdk.monsters.Witch2, sdk.monsters.Witch3
- ];
-
- [sdk.areas.RiverofFlame, sdk.areas.ChaosSanctuary].includes(me.area) && rangedMobsClassIDs.push(sdk.monsters.Strangler1, sdk.monsters.StormCaster1);
-
- let list = getUnits(sdk.unittype.Monster)
- .filter(function (mob) {
- return ([sdk.monsters.spectype.All, sdk.monsters.spectype.Minion].includes(mob.spectype)
- && [sdk.states.BattleCry, sdk.states.Decrepify, sdk.states.Taunt].every(state => !mob.getState(state))
- && ((rangedMobsClassIDs.includes(mob.classid) && mob.distance <= range) || (dangerousAndSummoners.includes(mob.classid) && mob.distance <= 30)));
- })
- .sort(Sort.units);
-
- if (list.length >= 1) {
- for (let i = 0; i < list.length; i++) {
- let currMob = list[i];
- if (battleCryCheck() && Skill.cast(sdk.skills.BattleCry, sdk.skills.hand.Right)) {
- continue;
- }
-
- if (data.howl.have && !data.warCry.have && data.howl.mana < me.mp && howlCheck()) {
- Skill.cast(sdk.skills.Howl, sdk.skills.hand.Right);
- } else if (data.warCry.have && data.warCry.mana < me.mp && warCryCheck()) {
- Skill.cast(sdk.skills.WarCry, sdk.skills.hand.Right);
- }
-
- if (!!currMob && !currMob.dead && [sdk.states.Terror, sdk.states.BattleCry, sdk.states.Decrepify, sdk.states.Taunt].every(state => !currMob.getState(state))
- && data.taunt.mana < me.mp && !Coords_1.isBlockedBetween(me, currMob)) {
- me.overhead("Taunting: " + currMob.name + " | classid: " + currMob.classid);
- Skill.cast(sdk.skills.Taunt, sdk.skills.hand.Right, currMob);
- }
-
- this.doCast(unit, attackSkill, data);
- }
- }
-};
-
-ClassAttack.doAttack = function (unit = undefined, preattack = false) {
- if (unit === undefined || !unit || unit.dead) return true;
-
- let gid = unit.gid;
- let needRepair = [], gold = me.gold;
- me.charlvl >= 5 && (needRepair = me.needRepair());
-
- if ((Config.MercWatch && Town.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;
- }
- }
- }
-
- const 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 && Skill.canUse(Config.AttackSkill[index + 1]) && Attack.checkResist(unit, Config.AttackSkill[index + 1])) {
- attackSkill = Config.AttackSkill[index + 1];
- }
- }
-
- if (me.expansion && index === 1 && !unit.dead) {
- if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles) && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles) && unit.curseable &&
- (gold > 500000 && !unit.isBoss) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast slow missiles
- Attack.castCharges(sdk.skills.SlowMissiles, unit);
- }
-
- if (CharData.skillData.haveChargedSkill(sdk.skills.InnerSight) && !unit.getState(sdk.states.InnerSight) && unit.curseable &&
- gold > 500000 && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast slow missiles
- Attack.castCharges(sdk.skills.InnerSight, unit);
- }
- }
-
- const buildDataObj = (skillId = -1, reqLvl = 1) => ({
- have: false, skill: skillId, range: Infinity, mana: Infinity, timed: false, reqLvl: reqLvl,
- assignValues: function (range) {
- this.have = Skill.canUse(this.skill);
- if (!this.have) return;
- this.range = range || Skill.getRange(this.skill);
- this.mana = Skill.getManaCost(this.skill);
- this.timed = Skill.isTimed(this.skill);
- }
- });
- const currLvl = me.charlvl;
- const data = {
- switchCast: false,
- howl: buildDataObj(sdk.skills.Howl, 1),
- bash: buildDataObj(sdk.skills.Bash, 1),
- taunt: buildDataObj(sdk.skills.Taunt, 6),
- leap: buildDataObj(sdk.skills.Leap, 6),
- doubleSwing: buildDataObj(sdk.skills.DoubleSwing, 6),
- stun: buildDataObj(sdk.skills.Stun, 12),
- battleCry: buildDataObj(sdk.skills.BattleCry, 18),
- concentrate: buildDataObj(sdk.skills.Concentrate, 18),
- leapAttack: buildDataObj(sdk.skills.LeapAttack, 18),
- grimWard: buildDataObj(sdk.skills.GrimWard, 24),
- warCry: buildDataObj(sdk.skills.WarCry, 30),
- whirlwind: buildDataObj(sdk.skills.Whirlwind, 30),
- main: buildDataObj(Config.AttackSkill[index], 1),
- secondary: buildDataObj(Config.AttackSkill[index + 1], 1),
- };
-
- // TODO: calculate damage values for physcial attacks
- Object.keys(data).forEach(k => typeof data[k] === "object" && currLvl >= data[k].reqLvl && data[k].assignValues());
- // console.debug(data);
-
- // Low mana skill
- if (Skill.getManaCost(attackSkill) > me.mp && Config.LowManaSkill[0] > -1 && Attack.checkResist(unit, Config.LowManaSkill[0])) {
- attackSkill = Config.LowManaSkill[0];
- }
-
- if ([sdk.skills.DoubleSwing, sdk.skills.DoubleThrow, sdk.skills.Frenzy].includes(attackSkill) && !me.dualWielding || !Skill.canUse(attackSkill)) {
- let oneHandSk = [data.bash, data.stun, data.concentrate, data.leapAttack, data.whirlwind]
- .filter((skill) => skill.have && me.mp > skill.mana)
- .sort((a, b) => GameData.physicalAttackDamage(b.skill) - GameData.physicalAttackDamage(a.skill)).first();
- attackSkill = oneHandSk ? oneHandSk.skill : 0;
- }
-
- if (data.howl.have && attackSkill !== sdk.skills.Whirlwind && data.howl.mana < me.mp && howlCheck() && me.hpPercent <= 85) {
- data.grimWard.have ? this.grimWard(6) : Skill.cast(sdk.skills.Howl, sdk.skills.hand.Right);
- }
-
- data.taunt.have && this.tauntMonsters(unit, attackSkill, data);
-
- if (!unit.dead && data.battleCry.have && !me.skillDelay) {
- // Unit not already in Battle Cry, decrepify, terror, or taunt state. Don't want to overwrite helpful cureses
- if ([sdk.states.BattleCry, sdk.states.Decrepify, sdk.states.Terror, sdk.states.Taunt].every(state => !unit.getState(state))) {
- if (unit.distance > data.battleCry.range || checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, data.battleCry.range, sdk.collision.Ranged)) {
- return Attack.Result.FAILED;
- }
- }
-
- if (unit.distance < data.battleCry.range) {
- data.switchCast ? Skill.switchCast(sdk.skills.BattleCry, {hand: 0, switchBack: !data.warCry.have}) : Skill.cast(sdk.skills.BattleCry, sdk.skills.hand.Right, unit);
- }
- }
- }
-
- // TODO: write GameData.killableSummonsByWarCry
- if (data.warCry.have && data.warCry.mana < me.mp && !me.skillDelay && warCryCheck()) {
- data.switchCast ? Skill.switchCast(sdk.skills.WarCry, {hand: 0}) : Skill.cast(sdk.skills.WarCry, sdk.skills.hand.Right, unit);
- this.warCryTick = getTickCount();
- }
-
- // Probably going to get rid of preattack
- if (preattack && Config.AttackSkill[0] > 0 && Config.AttackSkill[0] !== sdk.skills.WarCry && Skill.canUse(Config.AttackSkill[0])
- && Attack.checkResist(unit, Attack.getSkillElement(Config.AttackSkill[0])) && (Skill.getManaCost(Config.AttackSkill[0]) < me.mp)
- && (!me.getState(sdk.states.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 (index === 1) {
- if (data.howl.have && attackSkill !== sdk.skills.Whirlwind && data.howl.mana < me.mp && howlCheck()) {
- data.grimWard.have ? this.grimWard(6) : !data.warCry.have ? Skill.cast(sdk.skills.Howl, Skill.getHand(sdk.skills.Howl)) : null;
- }
- }
-
- if (attackSkill === sdk.skills.DoubleThrow && (me.getWeaponQuantity() <= 3 || me.getWeaponQuantity(sdk.body.LeftArm) <= 3) && data.secondary.have) {
- attackSkill = data.secondary.skill;
- }
-
- // Telestomp with barb is pointless
- return this.doCast(unit, attackSkill, data);
-};
-
-ClassAttack.doCast = function (unit, attackSkill, data) {
- // In case of failing to switch back to main weapon slot
- me.weaponswitch === 1 && me.switchWeapons(0);
- // No attack skill
- if (attackSkill < 0 || !data) return Attack.Result.CANTATTACK;
-
- let walk;
-
- 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)) {
- walk = (Skill.getRange(attackSkill) < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall));
-
- // think this should be re-written in pather with some form of leap pathing similar to teleport
- // leap/leap attack is incredibly useful because we can leap straight to chaos or over mobs/doors/some walls ect
- if (data.leapAttack.have && !checkCollision(me, unit, sdk.collision.BlockWall) && unit.distance > 6) {
- Skill.cast(sdk.skills.LeapAttack, sdk.skills.hand.Right, unit.x, unit.y);
- }
-
- if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.Ranged, walk)) {
- return Attack.Result.FAILED;
- }
- }
-
- if (!unit.dead) {
- Skill.cast(attackSkill, Skill.getHand(attackSkill), unit);
-
- if (!unit.dead && attackSkill === sdk.skills.Berserk && me.dualWielding
- && Skill.canUse(sdk.skills.Frenzy) && unit.distance < 4 && !me.getState(sdk.states.Frenzy)) {
- Skill.cast(sdk.skills.Frenzy, Skill.getHand(sdk.skills.Frenzy), unit);
- }
-
- if (!unit.dead && attackSkill === sdk.skills.Berserk && data.concentrate.have && me.mp > data.concentrate.mana) {
- Skill.cast(sdk.skills.Concentrate, Skill.getHand(sdk.skills.Concentrate), unit);
- }
-
- // Remove this for now, needs more data calculations to decide if its actually worth using (% dmg, %crushing blow, # of mobs filtering phys immunes unless maybe we do ele dmg from something)
- // if (useWhirl && !unit.dead && (me.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE | Coords_1.BlockBits.BlockWall) >= 3 || ([156, 211, 242, 243, 544, 571].indexOf(unit.classid) > -1) && !me.hell)) {
- // this.whirlwind(unit);
- // }
- }
-
- return Attack.Result.SUCCESS;
- }
-};
-
-ClassAttack.afterAttack = function (pickit = false) {
- Precast.doPrecast(false);
-
- let needRepair = me.charlvl < 5 ? [] : me.needRepair();
-
- // Repair check, make sure i have a tome
- if (needRepair.length > 0 && me.getItem(sdk.items.TomeofTownPortal)) {
- Town.visitTown(true);
- }
-
- pickit && this.findItem(10);
-};
-
-ClassAttack.findItemIgnoreGids = [];
-ClassAttack.findItem = function (range = 10) {
- if (!Config.FindItem || !Skill.canUse(sdk.skills.FindItem)) return false;
-
- Config.FindItemSwitch = (me.expansion && Precast.getBetterSlot(sdk.skills.FindItem));
- let retry = false, pick = false, corpseList = [];
- let orgX = me.x, orgY = me.y;
-
- MainLoop:
- for (let i = 0; i < 3; i++) {
- 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());
- }
-
- if (corpseList.length > 0) {
- pick = true;
-
- while (corpseList.length > 0) {
- if (this.checkCloseMonsters(5)) {
- Config.FindItemSwitch && me.switchWeapons(Attack.getPrimarySlot());
- Attack.clearPos(me.x, me.y, 10, false);
- retry = true;
-
- break MainLoop;
- }
-
- corpseList.sort(Sort.units);
- corpse = corpseList.shift();
-
- if (this.checkCorpse(corpse)) {
- if (corpse.distance > 30 || Coords_1.isBlockedBetween(me, corpse)) {
- 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.weaponswitch === 1 && me.switchWeapons(Attack.getPrimarySlot());
- pick && Pickit.pickItems();
-
- return true;
-};
-
-ClassAttack.grimWard = function (range = 10) {
- if (!Skill.canUse(sdk.skills.GrimWard)) return false;
- let corpseList = [], orgX = me.x, orgY = me.y;
-
- 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());
- }
-
- if (corpseList.length > 0) {
- while (corpseList.length > 0) {
- corpseList.sort(Sort.units);
- corpse = corpseList.shift();
-
- if (this.checkCorpse(corpse)) {
- if (corpse.distance > 30 || Coords_1.isBlockedBetween(me, corpse)) {
- Pather.moveToUnit(corpse);
- }
-
- CorpseLoop:
- for (let j = 0; j < 3; j += 1) {
- Skill.cast(sdk.skills.GrimWard, sdk.skills.hand.Right, corpse);
-
- let tick = getTickCount();
-
- while (getTickCount() - tick < 1000) {
- if (corpse.getState(sdk.states.CorpseNoSelect)) {
-
- break CorpseLoop;
- }
-
- delay(10);
- }
- }
- }
- }
- }
- }
-
- return true;
-};
+(function () {
+ ClassAttack.warCryTick = 0;
+
+ const howlCheck = function () {
+ let levelCheck = (me.getSkill(sdk.skills.Howl, sdk.skills.subindex.SoftPoints) + me.charlvl + 1);
+ return getUnits(sdk.unittype.Monster)
+ .filter(function (el) {
+ return (!!el && el.attackable && el.distance < 6
+ && el.scareable && GameData.monsterLevel(el.classid, me.area) < levelCheck && !el.isStunned
+ && [
+ sdk.states.BattleCry, sdk.states.AmplifyDamage,
+ sdk.states.Decrepify, sdk.states.Terror, sdk.states.Taunt
+ ].every(state => !el.getState(state))
+ && !checkCollision(me, el, Coords_1.Collision.BLOCK_MISSILE));
+ }).length > me.maxNearMonsters;
+ };
+
+ const battleCryCheck = function () {
+ return getUnits(sdk.unittype.Monster).some(function (el) {
+ if (el === undefined) return false;
+ return (el.attackable && el.distance < 5 && el.curseable
+ && [
+ sdk.states.BattleCry, sdk.states.AmplifyDamage,
+ sdk.states.Decrepify, sdk.states.Terror, sdk.states.Taunt
+ ].every(state => !el.getState(state))
+ && !checkCollision(me, el, Coords_1.Collision.BLOCK_MISSILE));
+ });
+ };
+
+ const warCryCheck = function () {
+ return getUnits(sdk.unittype.Monster).some(function (el) {
+ if (el === undefined) return false;
+ return (el.attackable && el.distance < 5 && !(el.isSpecial) && el.curseable
+ && ![
+ sdk.monsters.Andariel, sdk.monsters.Duriel,
+ sdk.monsters.Mephisto, sdk.monsters.Diablo,
+ sdk.monsters.Baal, sdk.monsters.Tentacle1,
+ sdk.monsters.BaalClone, sdk.monsters.KorlictheProtector,
+ sdk.monsters.TalictheDefender, sdk.monsters.MadawctheGuardian
+ ].includes(el.classid)
+ && (!el.isStunned || getTickCount() - ClassAttack.warCryTick >= 1500)
+ && !checkCollision(me, el, Coords_1.Collision.BLOCK_MISSILE));
+ });
+ };
+
+ ClassAttack.tauntMonsters = function (unit, attackSkill, data) {
+ // Don't have skill
+ // Only mob in these areas are bosses
+ // Can't taunt Main bosses or MinionsofDestruction
+ if (!Skill.canUse(sdk.skills.Taunt) || !data) return;
+ if ([sdk.areas.DurielsLair, sdk.areas.ArreatSummit, sdk.areas.WorldstoneChamber].includes(me.area)) return;
+ if (unit.isPrimeEvil || unit.classid === sdk.monsters.ListerTheTormenter) return;
+
+ let range = (!me.inArea(sdk.areas.ThroneofDestruction) ? 15 : 30);
+ let rangedMobsClassIDs = [
+ sdk.monsters.Afflicted, sdk.monsters.Tainted,
+ sdk.monsters.Misshapen1, sdk.monsters.Disfigured,
+ sdk.monsters.Damned1, sdk.monsters.Gloam1,
+ sdk.monsters.SwampGhost, sdk.monsters.BurningSoul2,
+ sdk.monsters.BlackSoul1, sdk.monsters.GhoulLord1,
+ sdk.monsters.NightLord, sdk.monsters.DarkLord1, sdk.monsters.BloodLord1,
+ sdk.monsters.Banished, sdk.monsters.SkeletonArcher,
+ sdk.monsters.ReturnedArcher1, sdk.monsters.BoneArcher1,
+ sdk.monsters.BurningDeadArcher1, sdk.monsters.HorrorArcher1,
+ sdk.monsters.Sexton, sdk.monsters.Cantor,
+ sdk.monsters.Heirophant1, sdk.monsters.DoomKnight,
+ sdk.monsters.VenomLord1, sdk.monsters.Horror1, sdk.monsters.Horror2,
+ sdk.monsters.Horror3, sdk.monsters.Horror4,
+ sdk.monsters.Horror5, sdk.monsters.Lord1,
+ sdk.monsters.Lord2, sdk.monsters.Lord3, sdk.monsters.Lord4,
+ sdk.monsters.Lord4, sdk.monsters.Afflicted2,
+ sdk.monsters.Tainted, sdk.monsters.Misshapen2,
+ sdk.monsters.Disfigured2, sdk.monsters.Damned2, sdk.monsters.DarkShaman2,
+ sdk.monsters.DevilkinShaman, sdk.monsters.DarkShaman2, sdk.monsters.DarkLord2
+ ];
+ let dangerousAndSummoners = [
+ sdk.monsters.Dominus2, sdk.monsters.Witch1,
+ sdk.monsters.VileWitch2, sdk.monsters.Gloam2,
+ sdk.monsters.BlackSoul2, sdk.monsters.BurningSoul1,
+ sdk.monsters.FallenShaman, sdk.monsters.CarverShaman2,
+ sdk.monsters.DevilkinShaman2, sdk.monsters.DarkShaman1,
+ sdk.monsters.HollowOne, sdk.monsters.Guardian1,
+ sdk.monsters.Unraveler1, sdk.monsters.Ancient1,
+ sdk.monsters.BaalSubjectMummy, sdk.monsters.Council4,
+ sdk.monsters.VenomLord2, sdk.monsters.Ancient2,
+ sdk.monsters.Ancient3, sdk.monsters.Succubusexp1,
+ sdk.monsters.VileTemptress, sdk.monsters.StygianHarlot,
+ sdk.monsters.Temptress1, sdk.monsters.Temptress2,
+ sdk.monsters.Dominus1, sdk.monsters.VileWitch1,
+ sdk.monsters.StygianFury, sdk.monsters.Witch2, sdk.monsters.Witch3
+ ];
+
+ if ([sdk.areas.RiverofFlame, sdk.areas.ChaosSanctuary].includes(me.area)) {
+ rangedMobsClassIDs.push(sdk.monsters.Strangler1, sdk.monsters.StormCaster1);
+ }
+
+ let list = getUnits(sdk.unittype.Monster)
+ .filter(function (mob) {
+ return ([sdk.monsters.spectype.All, sdk.monsters.spectype.Minion].includes(mob.spectype)
+ && [sdk.states.BattleCry, sdk.states.Decrepify, sdk.states.Taunt].every(state => !mob.getState(state))
+ && ((rangedMobsClassIDs.includes(mob.classid) && mob.distance <= range)
+ || (dangerousAndSummoners.includes(mob.classid) && mob.distance <= 30)));
+ })
+ .sort(Sort.units);
+
+ if (list.length >= 1) {
+ for (let i = 0; i < list.length; i++) {
+ let currMob = list[i];
+ if (battleCryCheck() && Skill.cast(sdk.skills.BattleCry, sdk.skills.hand.Right)) {
+ continue;
+ }
+
+ if (data.howl.have && !data.warCry.have && data.howl.mana < me.mp && howlCheck()) {
+ Skill.cast(sdk.skills.Howl, sdk.skills.hand.Right);
+ } else if (data.warCry.have && data.warCry.mana < me.mp && warCryCheck()) {
+ Skill.cast(sdk.skills.WarCry, sdk.skills.hand.Right);
+ }
+
+ if (!!currMob && !currMob.dead
+ && [
+ sdk.states.Terror, sdk.states.BattleCry,
+ sdk.states.Decrepify, sdk.states.Taunt
+ ].every(state => !currMob.getState(state))
+ && data.taunt.mana < me.mp && !Coords_1.isBlockedBetween(me, currMob)) {
+ me.overhead("Taunting: " + currMob.name + " | classid: " + currMob.classid);
+ Skill.cast(sdk.skills.Taunt, sdk.skills.hand.Right, currMob);
+ }
+
+ this.doCast(unit, attackSkill, data);
+ }
+ }
+ };
+
+ /**
+ * @param {Monster} unit
+ * @param {boolean} preattack
+ * @returns {AttackResult}
+ */
+ ClassAttack.doAttack = function (unit, preattack = false) {
+ if (unit === undefined || !unit || unit.dead) return true;
+
+ let gid = unit.gid;
+ let needRepair = [], gold = me.gold;
+ me.charlvl >= 5 && (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;
+ }
+ }
+ }
+
+ const 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
+ && Skill.canUse(Config.AttackSkill[index + 1])
+ && Attack.checkResist(unit, Config.AttackSkill[index + 1])) {
+ attackSkill = Config.AttackSkill[index + 1];
+ }
+ }
+
+ if (me.expansion && index === 1 && !unit.dead) {
+ if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles)
+ && unit.getEnchant(sdk.enchant.LightningEnchanted)
+ && !unit.getState(sdk.states.SlowMissiles) && unit.curseable &&
+ (gold > 500000 && !unit.isBoss) && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast slow missiles
+ Attack.castCharges(sdk.skills.SlowMissiles, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkill(sdk.skills.InnerSight)
+ && !unit.getState(sdk.states.InnerSight) && unit.curseable &&
+ gold > 500000 && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast slow missiles
+ Attack.castCharges(sdk.skills.InnerSight, unit);
+ }
+ }
+
+ const buildDataObj = (skillId = -1, reqLvl = 1) => ({
+ have: false, skill: skillId, range: Infinity, mana: Infinity, timed: false, reqLvl: reqLvl,
+ assignValues: function (range) {
+ this.have = Skill.canUse(this.skill);
+ if (!this.have) return;
+ this.range = range || Skill.getRange(this.skill);
+ this.mana = Skill.getManaCost(this.skill);
+ this.timed = Skill.isTimed(this.skill);
+ }
+ });
+ const currLvl = me.charlvl;
+ const data = {
+ switchCast: false,
+ howl: buildDataObj(sdk.skills.Howl, 1),
+ bash: buildDataObj(sdk.skills.Bash, 1),
+ taunt: buildDataObj(sdk.skills.Taunt, 6),
+ leap: buildDataObj(sdk.skills.Leap, 6),
+ doubleSwing: buildDataObj(sdk.skills.DoubleSwing, 6),
+ stun: buildDataObj(sdk.skills.Stun, 12),
+ battleCry: buildDataObj(sdk.skills.BattleCry, 18),
+ concentrate: buildDataObj(sdk.skills.Concentrate, 18),
+ leapAttack: buildDataObj(sdk.skills.LeapAttack, 18),
+ grimWard: buildDataObj(sdk.skills.GrimWard, 24),
+ warCry: buildDataObj(sdk.skills.WarCry, 30),
+ whirlwind: buildDataObj(sdk.skills.Whirlwind, 30),
+ main: buildDataObj(Config.AttackSkill[index], 1),
+ secondary: buildDataObj(Config.AttackSkill[index + 1], 1),
+ };
+
+ // TODO: calculate damage values for physcial attacks
+ Object.keys(data).forEach(k => typeof data[k] === "object" && currLvl >= data[k].reqLvl && data[k].assignValues());
+ // console.debug(data);
+
+ // Low mana skill
+ if (Skill.getManaCost(attackSkill) > me.mp
+ && Config.LowManaSkill[0] > -1
+ && Attack.checkResist(unit, Config.LowManaSkill[0])) {
+ attackSkill = Config.LowManaSkill[0];
+ }
+
+ if ([sdk.skills.DoubleSwing, sdk.skills.DoubleThrow, sdk.skills.Frenzy].includes(attackSkill)
+ && !me.dualWielding || !Skill.canUse(attackSkill)) {
+ let oneHandSk = [data.bash, data.stun, data.concentrate, data.leapAttack, data.whirlwind]
+ .filter((skill) => skill.have && me.mp > skill.mana)
+ .sort((a, b) => GameData.physicalAttackDamage(b.skill) - GameData.physicalAttackDamage(a.skill)).first();
+ attackSkill = oneHandSk ? oneHandSk.skill : 0;
+ }
+
+ if (data.howl.have && attackSkill !== sdk.skills.Whirlwind
+ && data.howl.mana < me.mp && howlCheck() && me.hpPercent <= 85) {
+ data.grimWard.have ? this.grimWard(6) : Skill.cast(sdk.skills.Howl, sdk.skills.hand.Right);
+ }
+
+ data.taunt.have && this.tauntMonsters(unit, attackSkill, data);
+
+ if (!unit.dead && data.battleCry.have && !me.skillDelay) {
+ // Unit not already in Battle Cry, decrepify, terror, or taunt state. Don't want to overwrite helpful cureses
+ if ([
+ sdk.states.BattleCry, sdk.states.Decrepify,
+ sdk.states.Terror, sdk.states.Taunt
+ ].every(state => !unit.getState(state))) {
+ if (unit.distance > data.battleCry.range || checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, data.battleCry.range, sdk.collision.Ranged)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ if (unit.distance < data.battleCry.range) {
+ data.switchCast
+ ? Skill.switchCast(sdk.skills.BattleCry, { hand: sdk.skills.hand.Right, switchBack: !data.warCry.have })
+ : Skill.cast(sdk.skills.BattleCry, sdk.skills.hand.Right);
+ }
+ }
+ }
+
+ // TODO: write GameData.killableSummonsByWarCry
+ if (data.warCry.have && data.warCry.mana < me.mp && !me.skillDelay && warCryCheck()) {
+ data.switchCast
+ ? Skill.switchCast(sdk.skills.WarCry, { hand: 0 })
+ : Skill.cast(sdk.skills.WarCry, sdk.skills.hand.Right, unit);
+ this.warCryTick = getTickCount();
+ }
+
+ // Probably going to get rid of preattack
+ if (preattack && Config.AttackSkill[0] > 0
+ && Config.AttackSkill[0] !== sdk.skills.WarCry && Skill.canUse(Config.AttackSkill[0])
+ && Attack.checkResist(unit, Attack.getSkillElement(Config.AttackSkill[0]))
+ && (Skill.getManaCost(Config.AttackSkill[0]) < me.mp)
+ && (!me.getState(sdk.states.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 (index === 1) {
+ if (data.howl.have && attackSkill !== sdk.skills.Whirlwind && data.howl.mana < me.mp && howlCheck()) {
+ data.grimWard.have
+ ? this.grimWard(6)
+ : !data.warCry.have
+ ? Skill.cast(sdk.skills.Howl, Skill.getHand(sdk.skills.Howl))
+ : null;
+ }
+ }
+
+ if (attackSkill === sdk.skills.DoubleThrow
+ && (me.getWeaponQuantity() <= 3 || me.getWeaponQuantity(sdk.body.LeftArm) <= 3)
+ && data.secondary.have) {
+ attackSkill = data.secondary.skill;
+ }
+
+ // Telestomp with barb is pointless
+ return this.doCast(unit, attackSkill, data);
+ };
+
+ ClassAttack.doCast = function (unit, attackSkill, data) {
+ // In case of failing to switch back to main weapon slot
+ me.weaponswitch === 1 && me.switchWeapons(0);
+ // No attack skill
+ if (attackSkill < 0 || !data) return Attack.Result.CANTATTACK;
+
+ 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)
+ );
+
+ // think this should be re-written in pather with some form of leap pathing similar to teleport
+ // leap/leap attack is incredibly useful because we can leap straight to chaos or over mobs/doors/some walls ect
+ if (data.leapAttack.have && !checkCollision(me, unit, sdk.collision.BlockWall) && unit.distance > 6) {
+ Skill.cast(sdk.skills.LeapAttack, sdk.skills.hand.Right, unit.x, unit.y);
+ }
+
+ if (!Attack.getIntoPosition(unit, Skill.getRange(attackSkill), sdk.collision.Ranged, walk)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ if (!unit.dead) {
+ Skill.cast(attackSkill, Skill.getHand(attackSkill), unit);
+
+ if (!unit.dead && attackSkill === sdk.skills.Berserk && me.dualWielding
+ && Skill.canUse(sdk.skills.Frenzy) && unit.distance < 4 && !me.getState(sdk.states.Frenzy)) {
+ Skill.cast(sdk.skills.Frenzy, Skill.getHand(sdk.skills.Frenzy), unit);
+ }
+
+ if (!unit.dead && attackSkill === sdk.skills.Berserk
+ && data.concentrate.have && me.mp > data.concentrate.mana) {
+ Skill.cast(sdk.skills.Concentrate, Skill.getHand(sdk.skills.Concentrate), unit);
+ }
+
+ // Remove this for now, needs more data calculations to decide if its actually worth using (% dmg, %crushing blow, # of mobs filtering phys immunes unless maybe we do ele dmg from something)
+ // if (useWhirl && !unit.dead && (me.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE | Coords_1.BlockBits.BlockWall) >= 3 || ([156, 211, 242, 243, 544, 571].indexOf(unit.classid) > -1) && !me.hell)) {
+ // this.whirlwind(unit);
+ // }
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+ };
+
+ ClassAttack.afterAttack = function (pickit = false) {
+ Precast.doPrecast(false);
+
+ let needRepair = me.charlvl < 5 ? [] : me.needRepair();
+
+ // Repair check, make sure i have a tome
+ if (needRepair.length > 0 && me.getItem(sdk.items.TomeofTownPortal)) {
+ Town.visitTown(true);
+ }
+
+ pickit && this.findItem(10);
+ };
+
+ ClassAttack.findItemIgnoreGids = [];
+ ClassAttack.findItem = function (range = 10) {
+ if (!Config.FindItem || !Skill.canUse(sdk.skills.FindItem)) return false;
+
+ Config.FindItemSwitch = (me.expansion && Precast.getBetterSlot(sdk.skills.FindItem));
+ let pick = false;
+ let corpseList = [];
+ const { x: orgX, y: orgY } = me;
+
+ MainLoop:
+ for (let i = 0; i < 3; i++) {
+ 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());
+ }
+
+ if (corpseList.length > 0) {
+ pick = true;
+
+ while (corpseList.length > 0) {
+ if (this.checkCloseMonsters(10)) {
+ Config.FindItemSwitch && me.switchWeapons(Attack.getPrimarySlot());
+ Attack.clearPos(me.x, me.y, 10, 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 || Coords_1.isBlockedBetween(me, corpse)) {
+ 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)) {
+ !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.weaponswitch === 1 && me.switchWeapons(Attack.getPrimarySlot());
+ pick && Pickit.pickItems();
+
+ return true;
+ };
+
+ /**
+ * @param {Monster} unit
+ * @param {number} [range]
+ * @returns {boolean}
+ */
+ ClassAttack.grimWard = function (unit, range = 10) {
+ if (!Skill.canUse(sdk.skills.GrimWard)) return false;
+ if (!unit || !unit.dead) return false;
+
+ let corpseList = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.dead && mon.distance < 30 && getDistance(mon, unit) <= range && this.checkCorpse(mon);
+ })
+ .sort(function (a, b) {
+ return getDistance(a, unit) - getDistance(b, unit);
+ });
+
+ for (let corpse of corpseList) {
+ // corpseList uses copyUnit, so we need to get the actual corpse
+ let checkCorpse = Game.getMonster(corpse.classid, -1, corpse.gid);
+
+ if (checkCorpse && this.checkCorpse(checkCorpse)) {
+ for (let j = 0; j < 3; j += 1) {
+ if (Skill.cast(sdk.skills.GrimWard, sdk.skills.hand.Right, checkCorpse)) {
+ if (Misc.poll(function () {
+ return checkCorpse.getState(sdk.states.CorpseNoSelect);
+ }, 1000)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ };
+})();
diff --git a/libs/SoloPlay/Functions/ClassAttackOverrides/DruidAttacks.js b/libs/SoloPlay/Functions/ClassAttackOverrides/DruidAttacks.js
index b65d68fc..70b4aa1f 100644
--- a/libs/SoloPlay/Functions/ClassAttackOverrides/DruidAttacks.js
+++ b/libs/SoloPlay/Functions/ClassAttackOverrides/DruidAttacks.js
@@ -10,228 +10,295 @@
* Test traveling in wolf form/ utilizing wereform if we have it and need to perform normal attack
*/
-includeIfNotIncluded("common/Attacks/Druid.js");
-
-ClassAttack.doAttack = function (unit, preattack) {
- if (!unit) return Attack.Result.SUCCESS;
- let gid = unit.gid;
-
- if (Config.MercWatch && Town.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;
- }
- }
- }
-
- let checkSkill;
- let mercRevive = 0;
- let timedSkill = -1;
- let untimedSkill = -1;
- let gold = me.gold;
- let index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
-
- // 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 (index === 1 && !unit.dead && unit.curseable) {
- const commonCheck = (gold > 500000 || unit.isBoss || [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area));
-
- if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles) && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
- && (gold > 500000 && !unit.isBoss) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast slow missiles
- Attack.castCharges(sdk.skills.SlowMissiles, unit);
- }
-
- if (CharData.skillData.haveChargedSkill(sdk.skills.InnerSight) && !unit.getState(sdk.states.InnerSight)
- && gold > 500000 && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast slow missiles
- Attack.castCharges(sdk.skills.InnerSight, unit);
- }
-
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Decrepify)
- && !unit.getState(sdk.states.Decrepify) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast decrepify
- Attack.switchCastCharges(sdk.skills.Decrepify, unit);
- }
-
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
- && !unit.getState(sdk.states.Weaken) && !unit.getState(sdk.states.Decrepify) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast weaken
- Attack.switchCastCharges(sdk.skills.Weaken, unit);
- }
- }
-
- // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
- if (Precast.haveCTA > -1 && !unit.dead && (index === 1 || unit.isDoll)
- && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
- Skill.switchCast(sdk.skills.BattleCry, {oSkill: true});
- }
-
- if (preattack && Config.AttackSkill[0] > 0 && Attack.checkResist(unit, Config.AttackSkill[0])
- && (!me.getState(sdk.states.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;
- }
-
- // Get timed skill
- checkSkill = Attack.getCustomAttack(unit) ? Attack.getCustomAttack(unit)[0] : Config.AttackSkill[index];
-
- if (Attack.checkResist(unit, checkSkill) && Attack.validSpot(unit.x, unit.y, checkSkill)) {
- timedSkill = checkSkill;
- } else if (Config.AttackSkill[5] > -1 && Attack.checkResist(unit, Config.AttackSkill[5]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[5])) {
- 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)) {
- untimedSkill = checkSkill;
- } else if (Config.AttackSkill[6] > -1 && Attack.checkResist(unit, Config.AttackSkill[6]) && Attack.validSpot(unit.x, unit.y, Config.AttackSkill[6])) {
- 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];
- }
-
- if (me.normal && me.charlvl > 12 && gold < 5000 && Skill.getManaCost(timedSkill) > me.mp) {
- switch (SetUp.currentBuild) {
- case "Start":
- if (Skill.canUse(sdk.skills.Firestorm) && Skill.getManaCost(sdk.skills.Firestorm) < me.mp) {
- timedSkill = sdk.skills.Firestorm;
- } else if (me.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE | Coords_1.BlockBits.BlockWall) >= 1) {
- // I have no mana and there are mobs around me, just attack
- timedSkill = sdk.skills.Attack;
- }
-
- break;
- default:
- break;
- }
- }
-
- 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 && Pather.walkTo(spot.x, spot.y);
- }
-
- let closeMob = Attack.getNearestMonster({skipGid: gid});
- !!closeMob && this.doCast(closeMob, timedSkill, untimedSkill);
- }
-
- return Attack.Result.SUCCESS;
- }
-
- return result;
-};
-
-ClassAttack.doCast = function (unit, timedSkill, untimedSkill) {
- let walk;
-
- // No valid skills can be found
- if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK;
-
- // Rebuff Hurricane
- Skill.canUse(sdk.skills.Hurricane) && !me.getState(sdk.states.Hurricane) && Skill.cast(sdk.skills.Hurricane, sdk.skills.hand.Right);
-
- if (timedSkill > -1 && (!me.getState(sdk.states.SkillDelay) || !Skill.isTimed(timedSkill))) {
- switch (timedSkill) {
- case sdk.skills.Tornado:
- if (Math.ceil(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
- if (!unit.dead) {
- Skill.cast(timedSkill, Skill.getHand(timedSkill), unit.x + rand(-1, 1), unit.y);
- }
-
- return Attack.Result.SUCCESS;
- default:
- if (Skill.getRange(timedSkill) < 4 && !Attack.validSpot(unit.x, unit.y)) return Attack.Result.FAILED;
-
- if (Math.ceil(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)) return Attack.Result.FAILED;
-
- if (Math.ceil(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;
-};
-
+includeIfNotIncluded("core/Attacks/Druid.js");
+
+(function () {
+ /**
+ * @constructor
+ * @param {number} skillId
+ * @param {number} reqLvl
+ * @param {number} range
+ */
+ function ClassData (skillId = -1, range = 0) {
+ this.have = false;
+ this.skill = skillId;
+ this.range = range ? range : Skill.getRange(skillId);
+ this.mana = Infinity;
+ this.dmg = 0;
+ this.timed = Skill.isTimed(skillId);
+ this.reqLvl = getBaseStat("skills", skillId, "reqlevel");
+ }
+
+ /**
+ * Initialize data values
+ * @param {number} [range]
+ * @returns {void}
+ */
+ ClassData.prototype.assignValues = function (range) {
+ this.have = Skill.canUse(this.skill);
+ if (!this.have) return;
+ this.range = range || Skill.getRange(this.skill);
+ this.mana = Skill.getManaCost(this.skill);
+ };
+
+ /**
+ * Calculate effective damage for a certain monster unit
+ * @param {Monster} unit
+ * @returns {void}
+ */
+ ClassData.prototype.calcDmg = function (unit) {
+ if (!this.have) return;
+ this.dmg = GameData.avgSkillDamage(this.skill, unit);
+ };
+
+ const AttackData = {
+ "Attack": new ClassData(sdk.skills.Attack, 4),
+ "Firestorm": new ClassData(sdk.skills.Firestorm),
+ "MoltenBoulder": new ClassData(sdk.skills.MoltenBoulder),
+ "ArcticBlast": new ClassData(sdk.skills.ArcticBlast),
+ "Fissure": new ClassData(sdk.skills.Fissure),
+ "Twister": new ClassData(sdk.skills.Twister),
+ "Tornado": new ClassData(sdk.skills.Tornado),
+ "Volcano": new ClassData(sdk.skills.Volcano),
+ // "Hurricane": new ClassData(sdk.skills.Hurricane),
+ // "Armageddon": new ClassData(sdk.skills.Armageddon),
+ };
+
+ /**
+ * hacky for now - fire skills are wonky with the skill delays
+ * better solution might be tracking what was last cast and the actual
+ * delay of each
+ */
+ AttackData.Firestorm.timed = false;
+
+ /**
+ * The keys never change so this makes it easier to iterate without calling Object.keys each time
+ * @type {Array}
+ */
+ const AttackDataKeys = Object.keys(AttackData);
+
+ /**
+ * Helper function to re-init AttackData
+ * @param {number} currLvl
+ * @todo decide when AttackData need to be re-initialized becasue doing it every attack is a waste
+ */
+ const initAttackData = (currLvl = me.charlvl) => {
+ AttackDataKeys.forEach(sk => {
+ if (currLvl >= AttackData[sk].reqLvl) {
+ AttackData[sk].assignValues();
+ }
+ });
+ };
+
+ /**
+ * Helper function to init damage value for unit
+ * @param {Monster} unit
+ */
+ const setDamageValues = (unit) => {
+ AttackDataKeys.forEach(sk => {
+ if (AttackData[sk].have) {
+ AttackData[sk].calcDmg(unit);
+ }
+ });
+ };
+
+ /**
+ * Check if this skill is the most damaging
+ * @param {ClassData} skill
+ * @returns {boolean}
+ */
+ const isHighestDmg = (skill) => {
+ for (let key of AttackDataKeys) {
+ if (AttackData[key].dmg > skill.dmg) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ /**
+ * Used to handle times when there isn't a valid skill we can use, to prevent throwing error
+ */
+ const DummyData = new ClassData(-1, -1);
+
+ /**
+ * @param {Monster} unit
+ * @param {boolean} recheck
+ * @returns {AttackResult}
+ */
+ ClassAttack.doAttack = function (unit, recheck) {
+ if (!unit) return Attack.Result.SUCCESS;
+ 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;
+ }
+ }
+ }
+
+ let mercRevive = 0;
+ let gold = me.gold;
+ const currLvl = me.charlvl;
+ const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
+
+ // maybe every couple attacks or just the first one?
+ Precast.doPrecast();
+
+ if (index === 1 && !unit.dead && unit.curseable) {
+ const commonCheck = (gold > 500000 || unit.isBoss || [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area));
+
+ if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles) && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
+ && (gold > 500000 && !unit.isBoss) && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast slow missiles
+ Attack.castCharges(sdk.skills.SlowMissiles, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkill(sdk.skills.InnerSight) && !unit.getState(sdk.states.InnerSight)
+ && gold > 500000 && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast slow missiles
+ Attack.castCharges(sdk.skills.InnerSight, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Decrepify)
+ && !unit.getState(sdk.states.Decrepify) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast decrepify
+ Attack.switchCastCharges(sdk.skills.Decrepify, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
+ && !unit.getState(sdk.states.Weaken) && !unit.getState(sdk.states.Decrepify) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast weaken
+ Attack.switchCastCharges(sdk.skills.Weaken, unit);
+ }
+ }
+
+ // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
+ if (Precast.haveCTA > -1 && !unit.dead && (index === 1 || unit.isDoll)
+ && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
+ Skill.switchCast(sdk.skills.BattleCry, { oSkill: true });
+ }
+
+ initAttackData(currLvl);
+ setDamageValues(unit);
+
+ let selectedSkillKey = AttackDataKeys
+ .filter(k => AttackData[k].have && me.mp > AttackData[k].mana && (!AttackData[k].timed || !me.skillDelay))
+ .sort((a, b) => AttackData[b].dmg - AttackData[a].dmg)
+ .first();
+ if (!selectedSkillKey) return Attack.Result.FAILED;
+
+ /** @type {ClassData} */
+ let selectedSkill = typeof AttackData[selectedSkillKey] === "object" ? AttackData[selectedSkillKey] : DummyData;
+
+ switch (selectedSkill.skill) {
+ case sdk.skills.Attack:
+ if (!me.normal || (me.charlvl > 6 && !me.checkForMobs({ range: 10, coll: (sdk.collision.BlockWall | sdk.collision.ClosedDoor) }))) {
+ selectedSkill = DummyData;
+ }
+ }
+
+ // console.debug(AttackData);
+ // console.debug("Choose skill :: " + getSkillById(selectedSkill.skill) + " Damage: " + selectedSkill.dmg);
+
+ let result = ClassAttack.doCast(unit, selectedSkill);
+
+ if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) {
+ let merc = me.getMerc();
+
+ while (unit.attackable) {
+ 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 && Pather.walkTo(spot.x, spot.y);
+ }
+
+ let closeMob = Attack.getNearestMonster({ skipGid: gid });
+ !!closeMob && ClassAttack.doCast(closeMob, selectedSkill);
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ return result;
+ };
+
+ /**
+ * @param {Monster} unit
+ * @param {number} timedSkill
+ * @param {number} untimedSkill
+ * @returns {AttackResult}
+ */
+ ClassAttack.doCast = function (unit, choosenSkill) {
+ let { skill, range, mana, timed } = choosenSkill;
+ // unit became invalidated
+ if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
+ if (!!skill && me.mp < mana) {
+ return Attack.Result.NEEDMANA;
+ }
+ // No valid skills can be found
+ if (skill < 0) return Attack.Result.CANTATTACK;
+
+ /**
+ * @todo handling targetting for fissure/molten moulder/volcano
+ */
+
+ if (range > 8 && me.inDanger()) {
+ Attack.getIntoPosition(unit, range + 1, Coords_1.Collision.BLOCK_MISSILE, true);
+ }
+
+ if (!me.skillDelay || !timed) {
+ switch (skill) {
+ case sdk.skills.Tornado:
+ if (Math.ceil(unit.distance) > range || checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, range, sdk.collision.Ranged)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ // Randomized x coord changes tornado path and prevents constant missing
+ if (!unit.dead) {
+ Skill.cast(skill, Skill.getHand(skill), unit.x + rand(-1, 1), unit.y);
+ }
+
+ return Attack.Result.SUCCESS;
+ default:
+ if (range < 4 && !Attack.validSpot(unit.x, unit.y)) return Attack.Result.FAILED;
+
+ if (Math.ceil(unit.distance) > range || checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Allow short-distance walking for melee skills
+ let walk = range < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall);
+
+ if (!Attack.getIntoPosition(unit, range, sdk.collision.Ranged, walk)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ !unit.dead && Skill.cast(skill, Skill.getHand(skill), unit);
+
+ return Attack.Result.SUCCESS;
+ }
+ }
+
+ Misc.poll(() => !me.skillDelay, 1000, 40);
+
+ return Attack.Result.SUCCESS;
+ };
+})();
diff --git a/libs/SoloPlay/Functions/ClassAttackOverrides/NecromancerAttacks.js b/libs/SoloPlay/Functions/ClassAttackOverrides/NecromancerAttacks.js
index 33af666f..40f8c964 100644
--- a/libs/SoloPlay/Functions/ClassAttackOverrides/NecromancerAttacks.js
+++ b/libs/SoloPlay/Functions/ClassAttackOverrides/NecromancerAttacks.js
@@ -5,513 +5,587 @@
*
*/
-includeIfNotIncluded("common/Attacks/Necromancer.js");
-
-ClassAttack.curseIndex = [];
-// @todo refactor this
-ClassAttack.curseIndex[sdk.skills.AmplifyDamage] = {
- skillId: sdk.skills.AmplifyDamage,
- state: sdk.states.AmplifyDamage,
- priority: 2,
- skipIf: () => false,
- useIf: function (unit) {
- return Skill.canUse(this.skillId) && !unit.getState(sdk.states.Decrepify) && !Attack.checkResist(unit, "magic") && !Attack.checkResist(unit, "physical");
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.DimVision] = {
- skillId: sdk.skills.DimVision,
- state: sdk.states.DimVision,
- priority: 1,
- skipIf: function (unit) {
- return unit.isSpecial || [sdk.monsters.OblivionKnight1, sdk.monsters.OblivionKnight2, sdk.monsters.OblivionKnight3].includes(unit.classid);
- },
- useIf: function (unit) {
- return !this.skipIf(unit) && Skill.canUse(this.skillId) && unit.distance > 15;
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.Weaken] = {
- skillId: sdk.skills.Weaken,
- state: sdk.states.Weaken,
- priority: 3,
- skipIf: () => false,
- useIf: function (unit) {
- return Skill.canUse(this.skillId) && !unit.getState(sdk.states.Decrepify) && !unit.getState(sdk.states.AmplifyDamage);
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.IronMaiden] = {
- skillId: sdk.skills.IronMaiden,
- state: sdk.states.IronMaiden,
- priority: 1,
- skipIf: () => false,
- useIf: function (unit) {
- return Skill.canUse(this.skillId) && me.inArea(sdk.areas.DurielsLair) && me.normal && unit;
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.Terror] = {
- skillId: sdk.skills.Terror,
- state: sdk.states.Terror,
- priority: 1,
- skipIf: () => false,
- useIf: function (unit) {
- return unit.scareable && Skill.canUse(this.skillId) && me.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE | Coords_1.BlockBits.BlockWall | Coords_1.BlockBits.Casting, 0, true) >= 3
- && Skill.getManaCost(sdk.skills.Terror) < me.mp && me.hpPercent < 75;
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.Confuse] = {
- skillId: sdk.skills.Confuse,
- state: sdk.states.Confuse,
- priority: 2,
- skipIf: () => false,
- useIf: function (unit) {
- return unit.scareable && unit.distance > 8 && Skill.canUse(this.skillId);
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.LifeTap] = {
- skillId: sdk.skills.LifeTap,
- state: sdk.states.LifeTap,
- priority: 10,
- skipIf: () => false,
- useIf: function () {
- // false for now, maybe based on health check on merc or would this be summonmancer viable?
- return false;
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.Attract] = {
- skillId: sdk.skills.Attract,
- state: sdk.states.Attract,
- priority: 1,
- skipIf: () => false,
- useIf: function (unit) {
- return unit.scareable && me.inArea(sdk.areas.ThroneofDestruction) && unit.distance > 8
- && Skill.canUse(this.skillId);
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.Decrepify] = {
- skillId: sdk.skills.Decrepify,
- state: sdk.states.Decrepify,
- priority: 1,
- skipIf: () => false,
- useIf: function () {
- return Skill.canUse(this.skillId);
- }
-};
-
-ClassAttack.curseIndex[sdk.skills.LowerResist] = {
- skillId: sdk.skills.LowerResist,
- state: sdk.states.LowerResist,
- priority: 1,
- skipIf: () => SetUp.currentBuild !== "Poison",
- useIf: function (unit) {
- return !this.skipIf() && Skill.canUse(this.skillId) && Attack.checkResist(unit, "poison");
- }
-};
-
-// todo - need to re-work this a bit so we don't focus too much on curses when we should be attacking
-ClassAttack.smartCurse = function (unit) {
- if (unit === undefined || unit.dead || !unit.curseable) return false;
-
- let choosenCurse = (this.curseIndex
- .filter((curse) => curse !== undefined && curse.useIf(unit))
- .sort((a, b) => a.priority - b.priority)
- .find((curse) => Skill.getManaCost(curse.skillId) < me.mp) || false);
-
- if (choosenCurse && !unit.getState(choosenCurse.state)) {
- if (!checkCollision(me, unit, sdk.collision.Ranged)) {
- me.overhead("Cursing " + unit.name + " with " + getSkillById(choosenCurse.skillId));
- return Skill.cast(choosenCurse.skillId, sdk.skills.hand.Right, unit);
- } else {
- me.overhead(unit.name + " is blocked, skipping attempt to curse");
- this.doCast(unit, Config.AttackSkill[unit.isSpecial ? 1 : 3], Config.AttackSkill[unit.isSpecial ? 2 : 5]);
- }
- }
-
- return false;
-};
-
-ClassAttack.bpTick = 0;
-
-/**
- *
- * @todo
- * - bonemancer specific check for using bonespear vs bone spirit
- */
-
-// TODO: clean this up
-ClassAttack.doAttack = function (unit, preattack, once) {
- if (!unit) return Attack.Result.SUCCESS;
- let gid = unit.gid;
-
- if (Config.MercWatch && Town.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;
- }
- }
- }
-
- let mercRevive = 0;
- let gold = me.gold;
- const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
- const useTerror = Skill.canUse(sdk.skills.Terror);
- const useBP = Skill.canUse(sdk.skills.BonePrison);
- const bpAllowedAreas = [
- sdk.areas.CatacombsLvl4, sdk.areas.Tristram, sdk.areas.MooMooFarm, sdk.areas.RockyWaste, sdk.areas.DryHills, sdk.areas.FarOasis, sdk.areas.LostCity, sdk.areas.ValleyofSnakes,
- sdk.areas.DurielsLair, sdk.areas.SpiderForest, sdk.areas.GreatMarsh, sdk.areas.FlayerJungle, sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast, sdk.areas.KurastCauseway,
- sdk.areas.DuranceofHateLvl3, sdk.areas.OuterSteppes, sdk.areas.PlainsofDespair, sdk.areas.CityoftheDamned, sdk.areas.ChaosSanctuary, sdk.areas.BloodyFoothills, sdk.areas.FrigidHighlands,
- sdk.areas.ArreatSummit, sdk.areas.NihlathaksTemple, sdk.areas.WorldstoneLvl1, sdk.areas.WorldstoneLvl2, sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction
- ];
-
- // Bone prison
- if (useBP && unit.distance > ([sdk.areas.DurielsLair, sdk.areas.ArreatSummit].includes(me.area) ? 6 : 10)
- && bpAllowedAreas.includes(me.area) && (index === 1 || [sdk.monsters.ListerTheTormenter, sdk.monsters.HellBovine].includes(unit.classid))
- && !checkCollision(me, unit, sdk.collision.Ranged) && Skill.getManaCost(sdk.skills.BonePrison) * 2 < me.mp && getTickCount() - this.bpTick > 2000) {
- if (Skill.cast(sdk.skills.BonePrison, sdk.skills.hand.Right, unit)) {
- this.bpTick = getTickCount();
- }
- }
-
- // write terrorCheck function, need to take into account if monsters are even scareable
- if (useTerror && me.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE | Coords_1.BlockBits.BlockWall | Coords_1.BlockBits.Casting, 0, true) >= 3
- && Skill.getManaCost(sdk.skills.Terror) < me.mp && me.hpPercent < 75) {
- Skill.cast(sdk.skills.Terror, sdk.skills.hand.Right);
- }
-
- this.smartCurse(unit);
-
- if (me.expansion && index === 1 && !unit.dead) {
- if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles)
- && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
- && unit.curseable && (gold > 500000 && !unit.isBoss) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast slow missiles
- Attack.castCharges(sdk.skills.SlowMissiles, unit);
- }
- }
-
- // maybe this should return an object with basic skill info besides the skillId. e.g timed, mana, range, and hand
- const skills = Attack.decideSkill(unit);
-
- const switchBowAttack = (unit) => {
- if (Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) {
- try {
- const checkForShamans = unit.isFallen && !me.inArea(sdk.areas.BloodMoor);
- for (let i = 0; i < 5 && unit.attackable; i++) {
- if (checkForShamans && !once) {
- // before we waste time let's see if there is a shaman we should kill
- const shaman = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.distance < 20 && mon.isShaman && mon.attackable)
- .sort((a, b) => a.distance - b.distance).first();
- if (shaman) return ClassAttack.doAttack(shaman, null, true);
- }
- if (!Attack.useBowOnSwitch(unit, sdk.skills.Attack, i === 5)) return Attack.Result.FAILED;
- if (unit.distance < 8 || me.inDanger()) {
- if (once) return Attack.Result.FAILED;
- let closeMob = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.distance < 10 && mon.attackable && mon.gid !== gid)
- .sort(Attack.walkingSortMonsters).first();
- if (closeMob) return ClassAttack.doAttack(closeMob, null, true);
- }
- }
- } finally {
- me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
- }
- }
- return unit.dead ? Attack.Result.SUCCESS : Attack.Result.FAILED;
- };
-
- if (CharData.skillData.bowData.bowOnSwitch
- && (index !== 1 || !unit.name.includes(getLocaleString(sdk.locale.text.Ghostly)))
- && ([-1, sdk.skills.Attack].includes(skills.timed)
- || Skill.getManaCost(skills.timed) > me.mp
- || (Skill.getManaCost(skills.timed) * 3 > me.mp && [sdk.skills.Teeth].includes(skills.timed)))) {
- if (switchBowAttack(unit) === Attack.Result.SUCCESS) return Attack.Result.SUCCESS;
- }
-
- if (me.normal && gold < 5000 && (skills.timed === -1 || Skill.getManaCost(skills.timed) > me.mp)) {
- if (skills.timed !== sdk.skills.Teeth && Skill.canUse(sdk.skills.Teeth) && Skill.getManaCost(sdk.skills.Teeth) < me.mp) {
- skills.timed = sdk.skills.Teeth;
- } else if (Skill.canUse(sdk.skills.PoisonDagger) && Skill.getManaCost(sdk.skills.PoisonDagger) < me.mp) {
- skills.timed = sdk.skills.PoisonDagger;
- } else if (me.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE | Coords_1.BlockBits.BlockWall) >= 1) {
- // I have no mana and there are mobs around me, just attack
- skills.timed = sdk.skills.Attack;
- }
- }
- const result = this.doCast(unit, skills.timed, skills.untimed);
-
- if (result === Attack.Result.SUCCESS) {
- 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 && Pather.walkTo(spot.x, spot.y);
- }
-
- Config.ActiveSummon && this.raiseArmy();
- this.explodeCorpses(unit);
- this.smartCurse(unit);
- let closeMob = Attack.getNearestMonster({skipGid: gid});
- if (!!closeMob) {
- let findSkill = Attack.decideSkill(closeMob);
- (this.doCast(closeMob, findSkill.timed, findSkill.untimed) === Attack.Result.SUCCESS)
- || (this.canCurse(unit, sdk.skills.Terror) && Skill.cast(sdk.skills.Terror, sdk.skills.hand.Right, unit));
- }
- }
-
- return Attack.Result.SUCCESS;
- }
-
- return result;
-};
-
-// Returns: 0 - fail, 1 - success, 2 - no valid attack skills
-ClassAttack.doCast = function (unit, timedSkill, untimedSkill) {
- // No valid skills can be found
- if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK;
-
- // Check for bodies to exploit for CorpseExplosion before committing to an attack for non-summoner type necros
- if (Config.Skeletons + Config.SkeletonMages + Config.Revives === 0) {
- this.checkCorpseNearMonster(unit) && this.explodeCorpses(unit);
- }
-
- let lowMana = true;
- let walk, timedSkillRange, untimedSkillRange;
-
- if (timedSkill > -1 && (!me.getState(sdk.states.SkillDelay) || !Skill.isTimed(timedSkill)) && me.mp > Skill.getManaCost(timedSkill)) {
- lowMana = false;
- timedSkillRange = Skill.getRange(timedSkill);
-
- switch (timedSkill) {
- case sdk.skills.PoisonNova:
- if (!this.novaTick || getTickCount() - this.novaTick > Config.PoisonNovaDelay * 1000) {
- if (unit.distance > timedSkillRange || checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, timedSkillRange, 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 > timedSkillRange || checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, timedSkillRange, sdk.collision.Ranged)) {
- return Attack.Result.FAILED;
- }
- }
-
- delay(300);
-
- break;
- default:
- if (timedSkillRange < 4 && !Attack.validSpot(unit.x, unit.y)) return Attack.Result.FAILED;
-
- if (timedSkill === sdk.skills.Teeth) {
- timedSkillRange = me.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE | Coords_1.BlockBits.BlockWall | Coords_1.BlockBits.Casting) <= 3 ? 6 : timedSkillRange;
- }
-
- if (unit.distance > timedSkillRange || checkCollision(me, unit, sdk.collision.Ranged)) {
- // Allow short-distance walking for melee skills
- walk = timedSkillRange < 4 && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWall);
-
- if (!Attack.getIntoPosition(unit, timedSkillRange, sdk.collision.Ranged, walk)) return Attack.Result.FAILED;
- }
-
- if (!unit.dead) {
- // Try to find better spot
- if (unit.distance < 4 && timedSkillRange > 6) {
- Attack.deploy(unit, 4, 5, 9);
- }
-
- Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
- }
-
- break;
- }
- }
-
- if (untimedSkill > -1 && me.mp > Skill.getManaCost(untimedSkill)) {
- lowMana = false;
- untimedSkillRange = Skill.getRange(untimedSkill);
-
- if (untimedSkillRange < 4 && !Attack.validSpot(unit.x, unit.y)) return Attack.Result.FAILED;
-
- if (unit.distance > untimedSkillRange || 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, untimedSkillRange, 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 (this.novaTick && getTickCount() - this.novaTick < Config.PoisonNovaDelay * 1000) {
- delay(40);
- }
-
- return lowMana ? Attack.Result.NEEDMANA : Attack.Result.SUCCESS;
-};
-
-ClassAttack.farCast = function (unit) {
- let timedSkill = Config.AttackSkill[1], untimedSkill = Config.AttackSkill[2];
-
- // No valid skills can be found
- if (timedSkill < 0 && untimedSkill < 0) return Attack.Result.CANTATTACK;
-
- // Far to low a range for far casting
- if (Skill.getRange(timedSkill) < 4 && Skill.getRange(untimedSkill) < 4) return Attack.Result.CANTATTACK;
-
- // Bone prison
- if (unit.distance > 10 && !checkCollision(me, unit, sdk.collision.Ranged) && Skill.getManaCost(sdk.skills.BonePrison) * 2 < me.mp && getTickCount() - this.bpTick > 2000) {
- if (Skill.cast(sdk.skills.BonePrison, sdk.skills.hand.Right, unit)) {
- this.bpTick = getTickCount();
- }
- }
-
- this.smartCurse(unit);
-
- // Check for bodies to exploit for CorpseExplosion before committing to an attack for non-summoner type necros
- if (Config.Skeletons + Config.SkeletonMages + Config.Revives === 0) {
- this.checkCorpseNearMonster(unit) && this.explodeCorpses(unit);
- }
-
- if (timedSkill > -1 && (!me.getState(sdk.states.SkillDelay) || !Skill.isTimed(timedSkill))) {
- switch (timedSkill) {
- case sdk.skills.PoisonNova:
- case sdk.skills.Summoner: // Pure Summoner
- break;
- default:
- if (!unit.dead && !checkCollision(me, unit, sdk.collision.Ranged)) {
- Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
- }
-
- break;
- }
- }
-
- if (untimedSkill > -1) {
- if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y)) return Attack.Result.FAILED;
-
- if (!unit.dead && !checkCollision(me, unit, sdk.collision.Ranged)) {
- Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit);
- }
-
- return Attack.Result.SUCCESS;
- }
-
- Misc.poll(() => !me.skillDelay, 1000, 40);
-
- return Attack.Result.SUCCESS;
-};
-
-ClassAttack.explodeCorpses = function (unit) {
- if (Config.ExplodeCorpses === 0 || unit.dead) return false;
-
- let corpseList = [];
- let useAmp = Skill.canUse(sdk.skills.AmplifyDamage);
- let ampManaCost = Skill.getManaCost(sdk.skills.AmplifyDamage);
- let explodeCorpsesManaCost = Skill.getManaCost(Config.ExplodeCorpses);
- 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 (useAmp && !unit.getState(sdk.states.AmplifyDamage) && !unit.getState(sdk.states.Decrepify) && me.mp > (ampManaCost + explodeCorpsesManaCost)) {
- Skill.cast(sdk.skills.AmplifyDamage, sdk.skills.hand.Right, unit);
- }
-
- 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 (useAmp && !unit.getState(sdk.states.AmplifyDamage) && !unit.getState(sdk.states.Decrepify) && me.mp > (ampManaCost + explodeCorpsesManaCost)) {
- Skill.cast(sdk.skills.AmplifyDamage, sdk.skills.hand.Right, unit);
- }
-
- if (Skill.cast(Config.ExplodeCorpses, sdk.skills.hand.Right, corpse)) {
- delay(200);
- }
- }
- } else {
- break;
- }
- }
- }
- }
-
- return true;
-};
+includeIfNotIncluded("core/Attacks/Necromancer.js");
+
+(function () {
+ const curseIndex = (function () {
+ /**
+ * @constructor
+ * @param {number} skillId
+ * @param {number} state
+ * @param {number} priority
+ * @param {function(): boolean} useIf
+ */
+ function Curse (skillId, priority, useIf) {
+ this.name = getSkillById(skillId);
+ this.skillId = skillId;
+ this.state = Skill.getState(skillId);
+ this.priority = priority;
+ this.useIf = useIf;
+ }
+
+ Curse.prototype.have = function () {
+ return Skill.canUse(this.skillId);
+ };
+
+ Curse.prototype.manaCost = function () {
+ return Skill.getManaCost(this.skillId);
+ };
+
+ return [
+ new Curse(sdk.skills.AmplifyDamage, 2,
+ /** @param {Monster} unit */
+ function (unit) {
+ if (!unit || unit.getState(sdk.states.Decrepify)) return false;
+ return !Attack.checkResist(unit, "magic") && !Attack.checkResist(unit, "physical");
+ }
+ ),
+ new Curse(sdk.skills.DimVision, 1,
+ /** @param {Monster} unit */
+ function (unit) {
+ if (!unit || unit.isSpecial) return false;
+ if ([
+ sdk.monsters.OblivionKnight1,
+ sdk.monsters.OblivionKnight2,
+ sdk.monsters.OblivionKnight3
+ ].includes(unit.classid)) {
+ return false;
+ }
+ return unit.distance > 15;
+ }
+ ),
+ new Curse(sdk.skills.Weaken, 3,
+ /** @param {Monster} unit */
+ function (unit) {
+ return !unit.getState(sdk.states.Decrepify)
+ && !unit.getState(sdk.states.AmplifyDamage);
+ }
+ ),
+ new Curse(sdk.skills.IronMaiden, 1,
+ function () {
+ return me.inArea(sdk.areas.DurielsLair) && me.normal;
+ }
+ ),
+ new Curse(sdk.skills.Terror, 1,
+ /**
+ * @this Curse
+ * @param {Monster} unit
+ */
+ function (unit) {
+ if (!unit || !unit.scareable) return false;
+ let _coll = (sdk.collision.BlockMissile | sdk.collision.BlockWall | sdk.collision.Casting);
+ if (me.getMobCount(6, _coll, 0, true) < 3) return false;
+ return this.manaCost() < me.mp && me.hpPercent < 75;
+ }
+ ),
+ new Curse(sdk.skills.Confuse, 2,
+ /** @param {Monster} unit */
+ function (unit) {
+ return unit.scareable && unit.distance > 8;
+ }
+ ),
+ new Curse(sdk.skills.Attract, 1,
+ /** @param {Monster} unit */
+ function (unit) {
+ return me.inArea(sdk.areas.ThroneofDestruction)
+ && unit.distance > 8 && unit.scareable;
+ }
+ ),
+ new Curse(sdk.skills.Decrepify, 1,
+ function () {
+ return true;
+ }
+ ),
+ new Curse(sdk.skills.LowerResist, 1,
+ /** @param {Monster} unit */
+ function (unit) {
+ if (SetUp.currentBuild !== "Poison") return false;
+ return Attack.checkResist(unit, "poison");
+ }
+ )
+ ];
+ })();
+
+ /** @param {Monster} unit */
+ const doCurse = function (unit) {
+ if (unit === undefined || unit.dead || !unit.curseable) return false;
+
+ let curse = (curseIndex
+ .filter(function (c) {
+ return c.have() && c.useIf(unit);
+ })
+ .sort(function (a, b) {
+ return a.priority - b.priority;
+ })
+ .find(c => c.manaCost() < me.mp) || false);
+
+ if (curse && !unit.getState(curse.state)) {
+ if (!checkCollision(me, unit, sdk.collision.Ranged)) {
+ me.overhead("Cursing " + unit.name + " with " + curse.name);
+ return Skill.cast(curse.skillId, sdk.skills.hand.Right, unit);
+ } else {
+ me.overhead(unit.name + " is blocked, skipping attempt to curse");
+ let [timed, untimed] = unit.isSpecial ? [1, 2] : [3, 5];
+ ClassAttack.doCast(unit, Config.AttackSkill[timed], Config.AttackSkill[untimed]);
+ }
+ }
+
+ return false;
+ };
+
+ ClassAttack.bpTick = 0;
+
+ /**
+ * @todo
+ * - bonemancer specific check for using bonespear vs bone spirit
+ * - classdata structure for attacks, necro is fairly simple as there are
+ * only 5 attack skills, maybe take into account fireball OSkill from trangs
+ */
+
+ // TODO: clean this up
+ ClassAttack.doAttack = function (unit, preattack, once) {
+ if (!unit) return Attack.Result.SUCCESS;
+ 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;
+ }
+ }
+ }
+
+ let mercRevive = 0;
+ let gold = me.gold;
+ const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
+ const useTerror = Skill.canUse(sdk.skills.Terror);
+ const useBP = Skill.canUse(sdk.skills.BonePrison);
+ const bpAllowedAreas = [
+ sdk.areas.CatacombsLvl4, sdk.areas.Tristram, sdk.areas.MooMooFarm,
+ sdk.areas.RockyWaste, sdk.areas.DryHills, sdk.areas.FarOasis,
+ sdk.areas.LostCity, sdk.areas.ValleyofSnakes, sdk.areas.DurielsLair,
+ sdk.areas.SpiderForest, sdk.areas.GreatMarsh, sdk.areas.FlayerJungle,
+ sdk.areas.LowerKurast, sdk.areas.KurastBazaar, sdk.areas.UpperKurast,
+ sdk.areas.KurastCauseway, sdk.areas.DuranceofHateLvl3, sdk.areas.OuterSteppes,
+ sdk.areas.PlainsofDespair, sdk.areas.CityoftheDamned, sdk.areas.ChaosSanctuary,
+ sdk.areas.BloodyFoothills, sdk.areas.FrigidHighlands, sdk.areas.ArreatSummit,
+ sdk.areas.NihlathaksTemple, sdk.areas.WorldstoneLvl1, sdk.areas.WorldstoneLvl2,
+ sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction
+ ];
+
+ // Bone prison
+ if (useBP && unit.distance > ([sdk.areas.DurielsLair, sdk.areas.ArreatSummit].includes(me.area) ? 6 : 10)
+ && bpAllowedAreas.includes(me.area)
+ && (index === 1 || [sdk.monsters.ListerTheTormenter, sdk.monsters.HellBovine].includes(unit.classid))
+ && !checkCollision(me, unit, sdk.collision.Ranged)
+ && Skill.getManaCost(sdk.skills.BonePrison) * 2 < me.mp
+ && getTickCount() - this.bpTick > 2000) {
+ if (Skill.cast(sdk.skills.BonePrison, sdk.skills.hand.Right, unit)) {
+ this.bpTick = getTickCount();
+ }
+ }
+
+ // write terrorCheck function, need to take into account if monsters are even scareable
+ let _coll = (sdk.collision.BlockMissile | sdk.collision.BlockWall | sdk.collision.Casting);
+ if (useTerror && me.getMobCount(6, _coll, 0, true) >= 3
+ && Skill.getManaCost(sdk.skills.Terror) < me.mp && me.hpPercent < 75) {
+ Skill.cast(sdk.skills.Terror, sdk.skills.hand.Right);
+ }
+
+ doCurse(unit);
+
+ if (me.expansion && index === 1 && !unit.dead) {
+ if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles)
+ && unit.getEnchant(sdk.enchant.LightningEnchanted)
+ && !unit.getState(sdk.states.SlowMissiles)
+ && unit.curseable && (gold > 500000 && !unit.isBoss)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast slow missiles
+ Attack.castCharges(sdk.skills.SlowMissiles, unit);
+ }
+ }
+
+ // maybe this should return an object with basic skill info besides the skillId. e.g timed, mana, range, and hand
+ const skills = Attack.decideSkill(unit);
+
+ /** @param {Monster} unit */
+ const switchBowAttack = function (unit) {
+ if (Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) {
+ try {
+ const checkForShamans = unit.isFallen && !me.inArea(sdk.areas.BloodMoor);
+ for (let i = 0; i < 5 && unit.attackable; i++) {
+ if (checkForShamans && !once) {
+ // before we waste time let's see if there is a shaman we should kill
+ const shaman = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.distance < 20 && mon.isShaman && mon.attackable;
+ })
+ .sort(function (a, b) {
+ return a.distance - b.distance;
+ }).first();
+ if (shaman) return ClassAttack.doAttack(shaman, null, true);
+ }
+ if (!Attack.useBowOnSwitch(unit, sdk.skills.Attack, i === 5)) return Attack.Result.FAILED;
+ if (unit.distance < 8 || me.inDanger()) {
+ if (once) return Attack.Result.FAILED;
+ let closeMob = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.distance < 10 && mon.attackable && mon.gid !== gid;
+ })
+ .sort(Attack.walkingSortMonsters)
+ .first();
+ if (closeMob) return ClassAttack.doAttack(closeMob, null, true);
+ }
+ }
+ } finally {
+ me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
+ }
+ }
+ return unit.dead ? Attack.Result.SUCCESS : Attack.Result.FAILED;
+ };
+
+ if (CharData.skillData.bow.onSwitch
+ && (index !== 1 || !unit.name.includes(getLocaleString(sdk.locale.text.Ghostly)))
+ && ([-1, sdk.skills.Attack].includes(skills.timed)
+ || Skill.getManaCost(skills.timed) > me.mp
+ || (Skill.getManaCost(skills.timed) * 3 > me.mp && [sdk.skills.Teeth].includes(skills.timed)))) {
+ if (switchBowAttack(unit) === Attack.Result.SUCCESS) return Attack.Result.SUCCESS;
+ }
+
+ if (me.normal && gold < 5000
+ && (skills.timed === -1 || Skill.getManaCost(skills.timed) > me.mp)) {
+ if (skills.timed !== sdk.skills.Teeth
+ && Skill.canUse(sdk.skills.Teeth)
+ && Skill.getManaCost(sdk.skills.Teeth) < me.mp) {
+ skills.timed = sdk.skills.Teeth;
+ } else if (Skill.canUse(sdk.skills.PoisonDagger) && Skill.getManaCost(sdk.skills.PoisonDagger) < me.mp) {
+ skills.timed = sdk.skills.PoisonDagger;
+ } else if (me.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE | Coords_1.BlockBits.BlockWall) >= 1) {
+ // I have no mana and there are mobs around me, just attack
+ skills.timed = sdk.skills.Attack;
+ }
+ }
+ const result = this.doCast(unit, skills.timed, skills.untimed);
+
+ if (result === Attack.Result.SUCCESS) {
+ 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) 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 && Pather.walkTo(spot.x, spot.y);
+ }
+
+ Config.ActiveSummon && this.raiseArmy();
+ this.explodeCorpses(unit);
+ doCurse(unit);
+ let closeMob = Attack.getNearestMonster({ skipGid: gid });
+ if (!!closeMob) {
+ let findSkill = Attack.decideSkill(closeMob);
+ (this.doCast(closeMob, findSkill.timed, findSkill.untimed) === Attack.Result.SUCCESS)
+ || (this.canCurse(unit, sdk.skills.Terror) && Skill.cast(sdk.skills.Terror, sdk.skills.hand.Right, unit));
+ }
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ return result;
+ };
+
+ /**
+ * @param {Monster} unit
+ * @param {number} timedSkill
+ * @param {number} untimedSkill
+ * @returns {AttackResult}
+ */
+ ClassAttack.doCast = function (unit, timedSkill, untimedSkill) {
+ // No valid skills can be found
+ if (timedSkill < 0 && untimedSkill < 0) {
+ return Attack.Result.CANTATTACK;
+ }
+ // Check for bodies to exploit for CorpseExplosion before committing to an attack for non-summoner type necros
+ if (Config.Skeletons + Config.SkeletonMages + Config.Revives === 0) {
+ this.checkCorpseNearMonster(unit) && this.explodeCorpses(unit);
+ }
+
+ if (Skill.canUse(sdk.skills.BoneArmor) && !me.getState(sdk.states.BoneArmor)) {
+ // make sure we keep this up
+ Skill.cast(sdk.skills.BoneArmor, sdk.skills.hand.Right);
+ }
+
+ let walk;
+ let lowMana = true;
+
+ if (timedSkill > -1
+ && (!me.getState(sdk.states.SkillDelay) || !Skill.isTimed(timedSkill))
+ && me.mp > Skill.getManaCost(timedSkill)) {
+ lowMana = false;
+ let timedSkillRange = Skill.getRange(timedSkill);
+
+ switch (timedSkill) {
+ case sdk.skills.PoisonNova:
+ if (!this.novaTick || getTickCount() - this.novaTick > Config.PoisonNovaDelay * 1000) {
+ if (unit.distance > timedSkillRange || checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, timedSkillRange, 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 > timedSkillRange || checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, timedSkillRange, sdk.collision.Ranged)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ delay(300);
+
+ break;
+ case sdk.skills.Attack:
+ case sdk.skills.PoisonDagger:
+ if (!Attack.validSpot(unit.x, unit.y)) {
+ return Attack.Result.FAILED;
+ }
+
+ if (unit.distance > Skill.getRange(timedSkill) || checkCollision(me, unit, sdk.collision.WallOrRanged)) {
+ if (!Attack.getIntoPosition(unit, Skill.getRange(timedSkill), sdk.collision.WallOrRanged, true)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ if (unit.attackable) {
+ Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
+ }
+
+ break;
+ default:
+ if (timedSkillRange < 4 && !Attack.validSpot(unit.x, unit.y)) {
+ return Attack.Result.FAILED;
+ }
+
+ if (timedSkill === sdk.skills.Teeth) {
+ let _coll = (sdk.collision.BlockMissile | sdk.collision.BlockWall | sdk.collision.Casting);
+ timedSkillRange = me.getMobCount(6, _coll) <= 3 ? 8 : timedSkillRange;
+ }
+
+ if (unit.distance > timedSkillRange || checkCollision(me, unit, sdk.collision.BlockMissile)) {
+ // Allow short-distance walking for melee skills
+ walk = (
+ timedSkillRange < 4
+ && unit.distance < 10
+ && !checkCollision(me, unit, sdk.collision.BlockWall)
+ );
+ if (!Attack.getIntoPosition(unit, timedSkillRange, sdk.collision.BlockMissile, walk)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ if (!unit.dead) {
+ // Try to find better spot
+ if (unit.distance < 4 && timedSkillRange > 6) {
+ Attack.getIntoPosition(unit, timedSkillRange, sdk.collision.BlockMissile, true);
+ }
+
+ Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
+ }
+
+ break;
+ }
+ }
+
+ if (untimedSkill > -1 && me.mp > Skill.getManaCost(untimedSkill)) {
+ lowMana = false;
+ let untimedSkillRange = Skill.getRange(untimedSkill);
+
+ if (untimedSkillRange < 4 && !Attack.validSpot(unit.x, unit.y)) {
+ return Attack.Result.FAILED;
+ }
+ if (unit.distance > untimedSkillRange || checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Allow short-distance walking for melee skills
+ walk = (
+ untimedSkillRange < 4
+ && unit.distance < 10
+ && !checkCollision(me, unit, sdk.collision.BlockWall)
+ );
+
+ if (!Attack.getIntoPosition(unit, untimedSkillRange, 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 (this.novaTick && getTickCount() - this.novaTick < Config.PoisonNovaDelay * 1000) {
+ delay(40);
+ }
+
+ return lowMana ? Attack.Result.NEEDMANA : Attack.Result.SUCCESS;
+ };
+
+ /** @param {Monster} unit */
+ ClassAttack.farCast = function (unit) {
+ let timedSkill = Config.AttackSkill[1];
+ let untimedSkill = Config.AttackSkill[2];
+
+ // No valid skills can be found
+ if (timedSkill < 0 && untimedSkill < 0) {
+ return Attack.Result.CANTATTACK;
+ }
+ // Far to low a range for far casting
+ if (Skill.getRange(timedSkill) < 4 && Skill.getRange(untimedSkill) < 4) {
+ return Attack.Result.CANTATTACK;
+ }
+
+ // Bone prison
+ if (unit.distance > 10
+ && !checkCollision(me, unit, sdk.collision.Ranged)
+ && Skill.getManaCost(sdk.skills.BonePrison) * 2 < me.mp
+ && getTickCount() - this.bpTick > 2000) {
+ if (Skill.cast(sdk.skills.BonePrison, sdk.skills.hand.Right, unit)) {
+ this.bpTick = getTickCount();
+ }
+ }
+
+ doCurse(unit);
+
+ // Check for bodies to exploit for CorpseExplosion before committing to an attack for non-summoner type necros
+ if (Config.Skeletons + Config.SkeletonMages + Config.Revives === 0) {
+ this.checkCorpseNearMonster(unit) && this.explodeCorpses(unit);
+ }
+
+ if (timedSkill > -1 && (!me.getState(sdk.states.SkillDelay) || !Skill.isTimed(timedSkill))) {
+ switch (timedSkill) {
+ case sdk.skills.PoisonNova:
+ case sdk.skills.Summoner: // Pure Summoner
+ break;
+ default:
+ if (!unit.dead && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ Skill.cast(timedSkill, Skill.getHand(timedSkill), unit);
+ }
+
+ break;
+ }
+ }
+
+ if (untimedSkill > -1) {
+ if (Skill.getRange(untimedSkill) < 4 && !Attack.validSpot(unit.x, unit.y)) {
+ return Attack.Result.FAILED;
+ }
+ if (!unit.dead && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ Skill.cast(untimedSkill, Skill.getHand(untimedSkill), unit);
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ Misc.poll(() => !me.skillDelay, 1000, 40);
+
+ return Attack.Result.SUCCESS;
+ };
+
+ /** @param {Monster} unit */
+ ClassAttack.explodeCorpses = function (unit) {
+ if (Config.ExplodeCorpses === 0 || unit.dead) return false;
+
+ let corpseList = [];
+ let useAmp = Skill.canUse(sdk.skills.AmplifyDamage);
+ let ampManaCost = Skill.getManaCost(sdk.skills.AmplifyDamage);
+ let explodeCorpsesManaCost = Skill.getManaCost(Config.ExplodeCorpses);
+ 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 (useAmp && !unit.getState(sdk.states.AmplifyDamage)
+ && !unit.getState(sdk.states.Decrepify)
+ && me.mp > (ampManaCost + explodeCorpsesManaCost)) {
+ Skill.cast(sdk.skills.AmplifyDamage, sdk.skills.hand.Right, unit);
+ }
+
+ 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 (useAmp && !unit.getState(sdk.states.AmplifyDamage)
+ && !unit.getState(sdk.states.Decrepify)
+ && me.mp > (ampManaCost + explodeCorpsesManaCost)) {
+ Skill.cast(sdk.skills.AmplifyDamage, sdk.skills.hand.Right, unit);
+ }
+
+ if (Skill.cast(Config.ExplodeCorpses, sdk.skills.hand.Right, corpse)) {
+ delay(200);
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+ };
+})();
diff --git a/libs/SoloPlay/Functions/ClassAttackOverrides/PaladinAttacks.js b/libs/SoloPlay/Functions/ClassAttackOverrides/PaladinAttacks.js
index 9d5dd562..e0a6faed 100644
--- a/libs/SoloPlay/Functions/ClassAttackOverrides/PaladinAttacks.js
+++ b/libs/SoloPlay/Functions/ClassAttackOverrides/PaladinAttacks.js
@@ -5,399 +5,408 @@
*
*/
-includeIfNotIncluded("common/Attacks/Paladin.js");
+includeIfNotIncluded("core/Attacks/Paladin.js");
+
+/**
+ * @todo build selectAura method
+ */
+
+const MercWatch = {
+ last: 0,
+};
+
// eslint-disable-next-line no-unused-vars
ClassAttack.doAttack = function (unit = undefined, preattack = false, once = false) {
- if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
-
- let gid = unit.gid;
- let mercRevive = 0;
- let gold = me.gold;
- let [attackSkill, aura] = [-1, -1];
- const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
-
- if (Config.MercWatch && Town.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;
- }
- }
- gold = me.gold; // reset value after town
- }
-
- if (me.expansion && index === 1 && unit.curseable) {
- const commonCheck = (gold > 500000 || unit.isBoss || [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area));
-
- if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles)
- && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
- && (gold > 500000 && !unit.isBoss) && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast slow missiles
- Attack.castCharges(sdk.skills.SlowMissiles, unit);
- }
-
- if (CharData.skillData.haveChargedSkill(sdk.skills.InnerSight) && !unit.getState(sdk.states.InnerSight)
- && gold > 500000 && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast Inner sight
- Attack.castCharges(sdk.skills.InnerSight, unit);
- }
-
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Decrepify)
- && !unit.getState(sdk.states.Decrepify) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast decrepify
- Attack.switchCastCharges(sdk.skills.Decrepify, unit);
- }
-
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
- && !unit.getState(sdk.states.Weaken) && !unit.getState(sdk.states.Decrepify) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast weaken
- Attack.switchCastCharges(sdk.skills.Weaken, unit);
- }
- }
-
- // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
- if (Precast.haveCTA > -1 && !unit.dead && (index === 1 || unit.isDoll)
- && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
- Skill.switchCast(sdk.skills.BattleCry, {oSkill: true});
- }
-
- 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];
- }
- }
- }
-
- if (attackSkill === sdk.skills.Attack && !unit.isFallen && Skill.canUse(sdk.skills.Sacrifice) && me.hpPercent > 75) {
- attackSkill = sdk.skills.Sacrifice;
- }
-
- // Low mana skill
- if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(attackSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) {
- [attackSkill, aura] = Config.LowManaSkill;
- }
-
- const switchBowAttack = (unit) => {
- if (Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) {
- try {
- const checkForShamans = unit.isFallen && !me.inArea(sdk.areas.BloodMoor);
- for (let i = 0; i < 5 && unit.attackable; i++) {
- if (checkForShamans && !once) {
- // before we waste time let's see if there is a shaman we should kill
- const shaman = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.distance < 20 && mon.isShaman && mon.attackable)
- .sort((a, b) => a.distance - b.distance).first();
- if (shaman) return ClassAttack.doAttack(shaman, null, true);
- }
- if (!Attack.useBowOnSwitch(unit, sdk.skills.Attack, i === 5)) return Attack.Result.FAILED;
- if (unit.distance < 8 || me.inDanger()) {
- if (once) return Attack.Result.FAILED;
- let closeMob = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.distance < 10 && mon.attackable && mon.gid !== gid)
- .sort(Attack.walkingSortMonsters).first();
- if (closeMob) return ClassAttack.doAttack(closeMob, null, true);
- }
- }
- } finally {
- me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
- }
- }
- return unit.dead ? Attack.Result.SUCCESS : Attack.Result.FAILED;
- };
-
- if (CharData.skillData.bowData.bowOnSwitch
- && (index !== 1 || !unit.name.includes(getLocaleString(sdk.locale.text.Ghostly)))
- && (unit.distance >= 12 || (unit.distance >= 8 && unit.isMoving && (unit.targetx !== me.x || unit.targety !== me.y)))
- && ([-1, sdk.skills.Attack].includes(attackSkill) || Skill.getManaCost(attackSkill) > me.mp)) {
- if (switchBowAttack(unit) === Attack.Result.SUCCESS) return Attack.Result.SUCCESS;
- }
-
- 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 && Pather.walkTo(spot.x, spot.y);
- }
-
- let closeMob = Attack.getNearestMonster({skipGid: gid});
- !!closeMob && this.doCast(closeMob, attackSkill, aura);
- }
-
- return Attack.Result.SUCCESS;
- }
-
- return result;
+ if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
+
+ let gid = unit.gid;
+ let mercRevive = 0;
+ let gold = me.gold;
+ let [attackSkill, aura] = [-1, -1];
+ const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
+
+ // prevent running back to town quickly if our merc is just weak
+ if (Config.MercWatch && me.needMerc() && getTickCount() - MercWatch.last > Time.seconds(5)) {
+ 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;
+ }
+ }
+ MercWatch.last = getTickCount();
+ gold = me.gold; // reset value after town
+ }
+
+ if (me.expansion && index === 1 && unit.curseable) {
+ const commonCheck = (gold > 500000 || unit.isBoss || [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area));
+
+ if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles)
+ && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
+ && (gold > 500000 && !unit.isBoss) && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast slow missiles
+ Attack.castCharges(sdk.skills.SlowMissiles, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkill(sdk.skills.InnerSight) && !unit.getState(sdk.states.InnerSight)
+ && gold > 500000 && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast Inner sight
+ Attack.castCharges(sdk.skills.InnerSight, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Decrepify)
+ && !unit.getState(sdk.states.Decrepify) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast decrepify
+ Attack.switchCastCharges(sdk.skills.Decrepify, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
+ && !unit.getState(sdk.states.Weaken) && !unit.getState(sdk.states.Decrepify) && commonCheck && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast weaken
+ Attack.switchCastCharges(sdk.skills.Weaken, unit);
+ }
+ }
+
+ // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
+ if (Precast.haveCTA > -1 && !unit.dead && (index === 1 || unit.isDoll)
+ && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
+ Skill.switchCast(sdk.skills.BattleCry, { oSkill: true });
+ }
+
+ 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];
+ }
+ }
+ }
+
+ if (attackSkill === sdk.skills.Attack && !unit.isFallen && Skill.canUse(sdk.skills.Sacrifice) && me.hpPercent > 75) {
+ attackSkill = sdk.skills.Sacrifice;
+ }
+
+ // Low mana skill
+ if (Config.LowManaSkill[0] > -1 && Skill.getManaCost(attackSkill) > me.mp && Attack.checkResist(unit, Config.LowManaSkill[0])) {
+ [attackSkill, aura] = Config.LowManaSkill;
+ }
+
+ /**
+ * @param {Monster} unit
+ * @returns {AttackResult}
+ */
+ const switchBowAttack = (unit) => {
+ if (Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) {
+ try {
+ const checkForShamans = unit.isFallen && !me.inArea(sdk.areas.BloodMoor);
+ for (let i = 0; i < 5 && unit.attackable; i++) {
+ if (checkForShamans && !once) {
+ // before we waste time let's see if there is a shaman we should kill
+ const shaman = getUnits(sdk.unittype.Monster)
+ .filter(mon => mon.distance < 20 && mon.isShaman && mon.attackable)
+ .sort((a, b) => a.distance - b.distance).first();
+ if (shaman) return ClassAttack.doAttack(shaman, null, true);
+ }
+ if (!Attack.useBowOnSwitch(unit, sdk.skills.Attack, i === 5)) return Attack.Result.FAILED;
+ if (unit.distance < 8 || me.inDanger()) {
+ if (once) return Attack.Result.FAILED;
+ let closeMob = getUnits(sdk.unittype.Monster)
+ .filter(mon => mon.distance < 10 && mon.attackable && mon.gid !== gid)
+ .sort(Attack.walkingSortMonsters).first();
+ if (closeMob) return ClassAttack.doAttack(closeMob, null, true);
+ }
+ }
+ } finally {
+ me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
+ }
+ }
+ return unit.dead ? Attack.Result.SUCCESS : Attack.Result.FAILED;
+ };
+
+ if (CharData.skillData.bow.onSwitch
+ && (index !== 1 || !unit.name.includes(getLocaleString(sdk.locale.text.Ghostly)))
+ && (unit.distance >= 12 || (unit.distance >= 8 && unit.isMoving && (unit.targetx !== me.x || unit.targety !== me.y)))
+ && ([-1, sdk.skills.Attack].includes(attackSkill) || Skill.getManaCost(attackSkill) > me.mp)) {
+ if (switchBowAttack(unit) === Attack.Result.SUCCESS) return Attack.Result.SUCCESS;
+ }
+
+ let result = this.doCast(unit, attackSkill, aura);
+
+ if (result === Attack.Result.CANTATTACK && Attack.canTeleStomp(unit)) {
+ let merc = me.getMerc();
+
+ while (unit.attackable) {
+ 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 && Pather.walkTo(spot.x, spot.y);
+ }
+
+ let closeMob = Attack.getNearestMonster({ skipGid: gid });
+ !!closeMob && this.doCast(closeMob, attackSkill, aura);
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ return result;
};
ClassAttack.reposition = function (x, y) {
- if (typeof x !== "number" || typeof y !== "number") return false;
- if ([x, y].distance > 1) {
- if (Pather.useTeleport()) {
- [x, y].distance > 30 ? Pather.moveTo(x, y) : Pather.teleportTo(x, y, 3);
- } else {
- if ([x, y].distance <= 4 && !CollMap.checkColl(me, { x: x, y: y }, sdk.collision.BlockWalk, 3)) {
- 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;
+ if (typeof x !== "number" || typeof y !== "number") return false;
+ if ([x, y].distance > 1) {
+ if (Pather.useTeleport()) {
+ [x, y].distance > 30 ? Pather.moveTo(x, y) : Pather.teleportTo(x, y, 3);
+ } else {
+ if ([x, y].distance <= 4 && !CollMap.checkColl(me, { x: x, y: y }, sdk.collision.BlockWalk, 3)) {
+ 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;
};
ClassAttack.getHammerPosition = function (unit) {
- let x, y, positions, baseId = getBaseStat("monstats", unit.classid, "baseid");
- let size = getBaseStat("monstats2", baseId, "sizex");
- const coll = unit.isMonsterObject ? sdk.collision.WallOrRanged : sdk.collision.BlockWalk;
- const 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, y } = unit);
- 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.distance < 1) return true;
- if (!canTele && (check.distance < 1 && !CollMap.checkColl(unit, check, coll, 1))
- || (check.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, coll, 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;
+ let x, y, positions, baseId = getBaseStat("monstats", unit.classid, "baseid");
+ let size = getBaseStat("monstats2", baseId, "sizex");
+ const coll = unit.isMonsterObject ? sdk.collision.WallOrRanged : sdk.collision.BlockWalk;
+ const 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, y } = unit);
+ 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.distance < 1) return true;
+ if (!canTele && (check.distance < 1 && !CollMap.checkColl(unit, check, coll, 1))
+ || (check.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, coll, 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;
};
ClassAttack.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;
- me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
-
- const currSkill = {
- Hand: Skill.getHand(attackSkill),
- Range: Skill.getRange(attackSkill)
- };
-
- 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, currSkill.Hand, 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, currSkill.Hand, unit);
-
- if (!unit.attackable || unit.distance > 9 || unit.isPlayer) {
- break;
- }
- }
-
- return Attack.Result.SUCCESS;
- case sdk.skills.HolyBolt:
- if (unit.distance > currSkill.Range + 3 || CollMap.checkColl(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, currSkill.Range, sdk.collision.Ranged)) {
- return Attack.Result.FAILED;
- }
- }
-
- CollMap.reset();
-
- if (unit.distance > currSkill.Range || CollMap.checkColl(me, unit, sdk.collision.FriendlyRanged, 2)) {
- if (!Attack.getIntoPosition(unit, currSkill.Range, sdk.collision.FriendlyRanged, true)) {
- return Attack.Result.FAILED;
- }
- }
-
- if (!unit.dead) {
- aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right);
- Skill.cast(attackSkill, currSkill.Hand, unit);
- }
-
- return Attack.Result.SUCCESS;
- case sdk.skills.FistoftheHeavens:
- if (!me.skillDelay) {
- if (unit.distance > currSkill.Range || CollMap.checkColl(me, unit, sdk.collision.FriendlyRanged, 2)) {
- if (!Attack.getIntoPosition(unit, currSkill.Range, sdk.collision.FriendlyRanged, true)) {
- return Attack.Result.FAILED;
- }
- }
-
- if (!unit.dead) {
- aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right);
- Skill.cast(attackSkill, currSkill.Hand, 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 (currSkill.Range < 4 && !Attack.validSpot(unit.x, unit.y, attackSkill, unit.classid)) return Attack.Result.FAILED;
-
- if (unit.distance > currSkill.Range || checkCollision(me, unit, sdk.collision.Ranged)) {
- let walk = (attackSkill !== sdk.skills.Smite && currSkill.Range < 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, currSkill.Range, sdk.collision.Ranged, walk)) return Attack.Result.FAILED;
- }
-
- if (!unit.dead) {
- aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right);
- Skill.cast(attackSkill, currSkill.Hand, unit);
- }
-
- return Attack.Result.SUCCESS;
- }
-
- Misc.poll(() => !me.skillDelay, 1000, 40);
-
- return Attack.Result.SUCCESS;
+ if (attackSkill < 0) return Attack.Result.CANTATTACK;
+ // unit became invalidated
+ if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
+ me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
+
+ const currSkill = {
+ Hand: Skill.getHand(attackSkill),
+ Range: Skill.getRange(attackSkill)
+ };
+
+ 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, currSkill.Hand, 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, currSkill.Hand, unit);
+
+ if (!unit.attackable || unit.distance > 9 || unit.isPlayer) {
+ break;
+ }
+ }
+
+ return Attack.Result.SUCCESS;
+ case sdk.skills.HolyBolt:
+ if (unit.distance > currSkill.Range + 3 || CollMap.checkColl(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, currSkill.Range, sdk.collision.Ranged)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ CollMap.reset();
+
+ if (unit.distance > currSkill.Range || CollMap.checkColl(me, unit, sdk.collision.FriendlyRanged, 2)) {
+ if (!Attack.getIntoPosition(unit, currSkill.Range, sdk.collision.FriendlyRanged, true)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ if (!unit.dead) {
+ aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right);
+ Skill.cast(attackSkill, currSkill.Hand, unit);
+ }
+
+ return Attack.Result.SUCCESS;
+ case sdk.skills.FistoftheHeavens:
+ if (!me.skillDelay) {
+ if (unit.distance > currSkill.Range || CollMap.checkColl(me, unit, sdk.collision.FriendlyRanged, 2)) {
+ if (!Attack.getIntoPosition(unit, currSkill.Range, sdk.collision.FriendlyRanged, true)) {
+ return Attack.Result.FAILED;
+ }
+ }
+
+ if (!unit.dead) {
+ aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right);
+ Skill.cast(attackSkill, currSkill.Hand, 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 (currSkill.Range < 4 && !Attack.validSpot(unit.x, unit.y, attackSkill, unit.classid)) return Attack.Result.FAILED;
+
+ if (unit.distance > currSkill.Range || checkCollision(me, unit, sdk.collision.Ranged)) {
+ let walk = (attackSkill !== sdk.skills.Smite && currSkill.Range < 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, currSkill.Range, sdk.collision.Ranged, walk)) return Attack.Result.FAILED;
+ }
+
+ if (!unit.dead) {
+ aura > -1 && Skill.setSkill(aura, sdk.skills.hand.Right);
+ Skill.cast(attackSkill, currSkill.Hand, unit);
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ Misc.poll(() => !me.skillDelay, 1000, 40);
+
+ return Attack.Result.SUCCESS;
};
ClassAttack.afterAttack = function () {
- Precast.doPrecast(false);
-
- if (Skill.canUse(sdk.skills.Cleansing) && me.hpPercent < 85 && me.getState(sdk.states.Poison)
- && !me.checkForMobs({range: 12, coll: Coords_1.BlockBits.BlockWall}) && Skill.setSkill(sdk.skills.Cleansing, sdk.skills.hand.Right)) {
- me.overhead("Delaying for a second to get rid of Poison");
- Misc.poll(() => (!me.getState(sdk.states.Poison) || 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);
- }
-
- if (Skill.canUse(sdk.skills.Redemption) && Config.Redemption instanceof Array
- && (me.hpPercent < Config.Redemption[0] || me.mpPercent < Config.Redemption[1])
- && Attack.checkNearCorpses(me) > 2 && Skill.setSkill(sdk.skills.Redemption, sdk.skills.hand.Right)) {
- Misc.poll(() => (me.hpPercent >= Config.Redemption[0] && me.mpPercent >= Config.Redemption[1]), 1500, 50);
- }
+ Precast.doPrecast(false);
+
+ if (Skill.canUse(sdk.skills.Cleansing) && me.hpPercent < 85 && me.getState(sdk.states.Poison)
+ && !me.checkForMobs({ range: 12, coll: Coords_1.BlockBits.BlockWall }) && Skill.setSkill(sdk.skills.Cleansing, sdk.skills.hand.Right)) {
+ me.overhead("Delaying for a second to get rid of Poison");
+ Misc.poll(() => (!me.getState(sdk.states.Poison) || 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);
+ }
+
+ if (Skill.canUse(sdk.skills.Redemption) && Config.Redemption instanceof Array
+ && (me.hpPercent < Config.Redemption[0] || me.mpPercent < Config.Redemption[1])
+ && Attack.checkNearCorpses(me) > 2 && Skill.setSkill(sdk.skills.Redemption, sdk.skills.hand.Right)) {
+ Misc.poll(() => (me.hpPercent >= Config.Redemption[0] && me.mpPercent >= Config.Redemption[1]), 1500, 50);
+ }
};
diff --git a/libs/SoloPlay/Functions/ClassAttackOverrides/SorceressAttacks.js b/libs/SoloPlay/Functions/ClassAttackOverrides/SorceressAttacks.js
index 35a642ea..9d042fa6 100644
--- a/libs/SoloPlay/Functions/ClassAttackOverrides/SorceressAttacks.js
+++ b/libs/SoloPlay/Functions/ClassAttackOverrides/SorceressAttacks.js
@@ -5,573 +5,750 @@
*
*/
-includeIfNotIncluded("common/Attacks/Sorceress.js");
-
-const slowable = function (unit, freezeable = false) {
- return (!!unit && unit.attackable // those that we can attack
- && Attack.checkResist(unit, "cold")
- // those that are not frozen yet and those that can be frozen or not yet chilled
- && (freezeable ? !unit.isFrozen && !unit.getStat(sdk.stats.CannotbeFrozen) : !unit.isChilled)
- && ![sdk.monsters.Andariel, sdk.monsters.Lord5].includes(unit.classid));
-};
-
-const frostNovaCheck = function () {
- // return getUnits(sdk.unittype.Monster).some(function(el) {
- // return !!el && el.distance < 7 && el.attackable
- // && ![sdk.monsters.Andariel].includes(el.classid)
- // && !el.isChilled && Attack.checkResist(el, 'cold')
- // && !checkCollision(me, el, Coords_1.Collision.BLOCK_MISSILE);
- // });
-
- // don't build whole list - since we are just trying if at least one passes the test
- // todo - test to time difference between these two methods
- let mob = Game.getMonster();
- if (mob) {
- do {
- if (mob.distance < 7 && ![sdk.monsters.Andariel].includes(mob.classid) && mob.attackable
- && !mob.isChilled && Attack.checkResist(mob, "cold") && !checkCollision(me, mob, Coords_1.Collision.BLOCK_MISSILE)) {
- return true;
- }
- } while (mob.getNext());
- }
- return false;
-};
-
-/**
- * @typedef {Object} dataObj
- * @property {number} skill
- * @property {number} reqLvl
- * @property {number} range
- * @property {boolean} have
- * @property {number} mana
- * @property {boolean} timed
- * @property {number} dmg
- * @property {Function} assignValues
- * @property {Function} calcDmg
- */
-
-/**
- * @param {number} skillId
- * @param {number} [reqLvl]
- * @param {number} [range]
- * @returns {dataObj}
- */
-const buildDataObj = (skillId = -1, reqLvl = 1, range = 0) => ({
- have: false, skill: skillId, range: range ? range : Infinity, mana: Infinity, timed: false, reqLvl: reqLvl, dmg: 0,
- assignValues: function (range) {
- this.have = Skill.canUse(this.skill);
- if (!this.have) return;
- this.range = range || Skill.getRange(this.skill);
- this.mana = Skill.getManaCost(this.skill);
- this.timed = Skill.isTimed(this.skill);
- },
- calcDmg: function (unit) {
- if (!this.have) return;
- this.dmg = GameData.avgSkillDamage(this.skill, unit);
- }
-});
-
-/**
- * @param {dataObj} main
- * @param {dataObj} check
- * @returns {boolean}
- */
-const compareDamage = (main, check) => {
- if (main.skill === check.skill) return false;
- return check.dmg > main.dmg;
-};
-
-ClassAttack.switchCurse = function (unit, force) {
- if (CharData.skillData.haveChargedSkill([sdk.skills.SlowMissiles, sdk.skills.LowerResist, sdk.skills.Weaken]) && unit.curseable) {
- const gold = me.gold;
- const isBoss = unit.isBoss;
- const dangerZone = [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area);
- if (force && checkCollision(me, unit, sdk.collision.Ranged)) {
- if (!Attack.getIntoPosition(unit, 35, sdk.collision.Ranged)) return;
- }
- // If we have slow missles we might as well use it, currently only on Lighting Enchanted mobs as they are dangerous
- // Might be worth it to use on souls too TODO: test this idea
- if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles) && gold > 500000 && !isBoss
- && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
- && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Cast slow missiles
- Attack.castCharges(sdk.skills.SlowMissiles, unit);
- }
- // Handle Switch casting
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
- && (gold > 500000 || isBoss || dangerZone)
- && !unit.getState(sdk.states.LowerResist)
- && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast lower resist
- Attack.switchCastCharges(sdk.skills.LowerResist, unit);
- }
-
- if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
- && (gold > 500000 || isBoss || dangerZone)
- && !unit.getState(sdk.states.Weaken) && !unit.getState(sdk.states.LowerResist)
- && !checkCollision(me, unit, sdk.collision.Ranged)) {
- // Switch cast weaken
- Attack.switchCastCharges(sdk.skills.Weaken, unit);
- }
- }
-};
-
-/**
- * @param {Unit} unit
- * @returns {dataObj}
- */
-ClassAttack.decideDistanceSkill = function (unit) {
- const data = {};
- const currLvl = me.charlvl;
- data.iceBlast = buildDataObj(sdk.skills.IceBlast, 6, 20);
- data.fireBall = buildDataObj(sdk.skills.FireBall, 12, 20);
- data.lightning = buildDataObj(sdk.skills.Lightning, 12);
- data.glacialSpike = buildDataObj(sdk.skills.GlacialSpike, 18, 25);
- data.blizzard = buildDataObj(sdk.skills.Blizzard, 24, 40);
- data.meteor = buildDataObj(sdk.skills.Meteor, 24, 40);
- data.frozenOrb = buildDataObj(sdk.skills.FrozenOrb, 30);
- data.hydra = buildDataObj(sdk.skills.Hydra, 30, 40);
- Object.keys(data).forEach(k => typeof data[k] === "object" && currLvl >= data[k].reqLvl && data[k].assignValues());
- Object.keys(data).forEach(k => typeof data[k] === "object" && data[k].have && data[k].calcDmg(unit));
-
- let skillCheck = Object.keys(data)
- .filter(k => typeof data[k] === "object" && data[k].have && me.mp > data[k].mana
- && (!data[k].timed || !me.skillDelay))
- .sort((a, b) => data[b].dmg - data[a].dmg).first();
- return typeof data[skillCheck] === "object" ? data[skillCheck] : buildDataObj(-1);
-};
-
-ClassAttack.doAttack = function (unit, recheckSkill = false, once = false) {
- Developer.debugging.skills && console.log(sdk.colors.Green + "Test Start-----------------------------------------//");
- // unit became invalidated
- if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
-
- const currLvl = me.charlvl;
- const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
- let gid = unit.gid;
- let tick = getTickCount();
- let gold = me.gold;
-
- if (Config.MercWatch && Town.needMerc() && gold > me.mercrevivecost * 3) {
- console.debug("mercwatch");
-
- if (Town.visitTown()) {
- // lost reference to the mob we were attacking
- if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) {
- console.debug("Lost reference to unit");
- return Attack.Result.SUCCESS;
- }
- gold = me.gold; // reset value after town
- }
- }
-
- // 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);
-
- // Handle Charge skill casting
- if (index === 1 && me.expansion && !unit.dead) {
- ClassAttack.switchCurse(unit);
- }
-
- const data = {};
- data.static = buildDataObj(sdk.skills.StaticField, 6);
- data.frostNova = buildDataObj(sdk.skills.FrostNova, 6, 7);
- data.iceBlast = buildDataObj(sdk.skills.IceBlast, 6, 15);
- data.nova = buildDataObj(sdk.skills.Nova, 12);
- data.fireBall = buildDataObj(sdk.skills.FireBall, 12);
- data.lightning = buildDataObj(sdk.skills.Lightning, 12);
- data.glacialSpike = buildDataObj(sdk.skills.GlacialSpike, 18, 15);
- data.frozenOrb = buildDataObj(sdk.skills.FrozenOrb, 30);
- data.hydra = buildDataObj(sdk.skills.Hydra, 30);
- data.customTimed = buildDataObj(-1);
- data.customUntimed = buildDataObj(-1);
- // @todo handle if these are already include in the above list, or should I just build all used skils instead and ignore these?
- data.mainTimed = buildDataObj(Config.AttackSkill[index]);
- data.mainUntimed = buildDataObj(Config.AttackSkill[index + 1]);
- data.secondaryTimed = buildDataObj(Config.AttackSkill[5]);
- data.secondaryUntimed = buildDataObj(Config.AttackSkill[6]);
-
- if (Attack.getCustomAttack(unit)) {
- let [ts, uts] = Attack.getCustomAttack(unit);
- ts > 0 && (data.customTimed = buildDataObj(ts));
- uts > 0 && (data.customUntimed = buildDataObj(uts));
- }
-
- Object.keys(data).forEach(k => typeof data[k] === "object" && currLvl >= data[k].reqLvl && data[k].assignValues());
-
- if (data.frostNova.have) {
- if (me.mp > data.frostNova.mana) {
- frostNovaCheck() && Skill.cast(sdk.skills.FrostNova, sdk.skills.hand.Right);
- let ticktwo = getTickCount();
- // if the nova cause the death of any monsters around us, its worth it
- if (GameData.calculateKillableFallensByFrostNova() > 0) {
- Developer.debugging.skills && console.log("took " + ((getTickCount() - ticktwo) / 1000) + " seconds to check calculateKillableFallensByFrostNova. frost nova will kill fallens");
- Skill.cast(sdk.skills.FrostNova, sdk.skills.hand.Right);
- }
- }
- }
-
- if (data.glacialSpike.have) {
- if (me.mp > data.glacialSpike.mana * 2) {
- let shouldSpike = unit && unit.distance < 10 &&
- getUnits(sdk.unittype.Monster).filter(function (el) {
- return getDistance(el, unit) < 4 && slowable(el, true);
- }).length > 1;
- if (shouldSpike && !Coords_1.isBlockedBetween(me, unit)) {
- Developer.debugging.skills && console.log("SPIKE");
- Skill.cast(sdk.skills.GlacialSpike, sdk.skills.hand.Right, unit);
- }
- }
- }
-
- // We lost track of the mob or killed it
- if (unit === undefined || !unit || !unit.attackable) return Attack.Result.SUCCESS;
-
- // Set damage values
- // redo gamedata to be more efficent
- Object.keys(data).forEach(k => typeof data[k] === "object" && data[k].have && data[k].calcDmg(unit));
+includeIfNotIncluded("core/Attacks/Sorceress.js");
+
+(function () {
+ /**
+ * Can we slow this monster
+ * @param {Monster} unit
+ * @param {boolean} freezeable
+ * @returns {boolean}
+ */
+ const slowable = function (unit, freezeable = false) {
+ return (!!unit && unit.attackable // those that we can attack
+ && Attack.checkResist(unit, "cold")
+ // those that are not frozen yet and those that can be frozen or not yet chilled
+ && (freezeable ? !unit.isFrozen && !unit.getStat(sdk.stats.CannotbeFrozen) : !unit.isChilled)
+ && ![sdk.monsters.Andariel, sdk.monsters.Lord5].includes(unit.classid));
+ };
+
+ const frostNovaCheck = function () {
+ // don't build whole list - since we are just trying if at least one passes the test
+ // todo - test to time difference between these two methods
+ let mob = Game.getMonster();
+ if (mob) {
+ do {
+ if (mob.distance < 7 && ![sdk.monsters.Andariel].includes(mob.classid) && mob.attackable
+ && !mob.isChilled && Attack.checkResist(mob, "cold")
+ && !checkCollision(me, mob, Coords_1.Collision.BLOCK_MISSILE)) {
+ return true;
+ }
+ } while (mob.getNext());
+ }
+ return false;
+ };
+
+ /**
+ * @param {Monster} unit
+ */
+ const battleCryCheck = function (unit, force = false) {
+ // specials and dolls for now, should make dolls much less dangerous with the reduction of their damage
+ if (Precast.haveCTA > -1 && !unit.dead && (force || unit.isSpecial || unit.isDoll)
+ && unit.distance < 5 && !unit.getState(sdk.states.BattleCry) && unit.curseable) {
+ console.debug("BATTLECRY");
+ Skill.switchCast(sdk.skills.BattleCry, { oSkill: true });
+ }
+ };
+
+ const lastAttack = {
+ skill: -1,
+ count: 0,
+ gid: -1,
+
+ setSkill: function (skill, gid) {
+ if (skill === this.skill && gid === this.gid) {
+ this.count++;
+ } else {
+ this.skill = skill;
+ this.count = 1;
+ this.gid = gid;
+ }
+ },
+ };
+
+ const Skills = new Map([
+ [sdk.skills.Attack, Skill.get(sdk.skills.Attack)],
+ [sdk.skills.FireBolt, Skill.get(sdk.skills.FireBolt)],
+ [sdk.skills.ChargedBolt, Skill.get(sdk.skills.ChargedBolt)],
+ [sdk.skills.IceBolt, Skill.get(sdk.skills.IceBolt)],
+ [sdk.skills.Inferno, Skill.get(sdk.skills.Inferno)],
+ [sdk.skills.Telekinesis, Skill.get(sdk.skills.Telekinesis)],
+ [sdk.skills.StaticField, Skill.get(sdk.skills.StaticField)],
+ [sdk.skills.IceBlast, Skill.get(sdk.skills.IceBlast)],
+ [sdk.skills.FrostNova, Skill.get(sdk.skills.FrostNova)],
+ [sdk.skills.FireBall, Skill.get(sdk.skills.FireBall)],
+ [sdk.skills.Lightning, Skill.get(sdk.skills.Lightning)],
+ [sdk.skills.Nova, Skill.get(sdk.skills.Nova)],
+ [sdk.skills.FireWall, Skill.get(sdk.skills.FireWall)],
+ [sdk.skills.ChainLightning, Skill.get(sdk.skills.ChainLightning)],
+ [sdk.skills.GlacialSpike, Skill.get(sdk.skills.GlacialSpike)],
+ [sdk.skills.Meteor, Skill.get(sdk.skills.Meteor)],
+ [sdk.skills.Blizzard, Skill.get(sdk.skills.Blizzard)],
+ [sdk.skills.Hydra, Skill.get(sdk.skills.Hydra)],
+ [sdk.skills.FrozenOrb, Skill.get(sdk.skills.FrozenOrb)],
+ ]);
+
+ /**
+ * @param {Monster} unit
+ * @param {{
+ * manaSort?: boolean,
+ * checkSkillDelay?: boolean,
+ * checkLast: boolean,
+ * checkSafeStatic: boolean,
+ * minRange?: number,
+ * maxRange?: number
+ * }} options
+ */
+ const decideAttack = function (unit, options = {}) {
+ const overrides = Object.assign({
+ manaSort: true,
+ checkSkillDelay: false,
+ checkLast: true,
+ checkSafeStatic: true,
+ minRange: 0,
+ maxRange: 0,
+ }, options);
+ let _choices = [];
+
+ for (let [skillId, skill] of Skills) {
+ if (!skill.have()) continue;
+ if (overrides.minRange && skill.range() < overrides.minRange) continue;
+ if (overrides.maxRange && skill.range() > overrides.maxRange) continue;
+ skill._dmg = GameData.avgSkillDamage(skillId, unit);
+ _choices.push(skill);
+ }
+
+ return _choices
+ .sort(function (a, b) {
+ if (overrides.manaSort) {
+ if (b._dmg === a._dmg) {
+ return b.manaCost() - a.manaCost();
+ }
+ }
+ return b._dmg - a._dmg;
+ })
+ .find(function (skill) {
+ if (overrides.checkLast
+ && _choices.length > 2
+ && lastAttack.count > 3
+ // && skill.skillId === lastAttack.skill
+ // for now only check these two, just mixing in other attacks every third attack
+ && [sdk.skills.ChargedBolt, sdk.skills.StaticField].includes(skill.skillId)
+ && unit.gid === lastAttack.gid) {
+ return false;
+ }
+ if (overrides.checkSafeStatic
+ && _choices.length > 2
+ && skill.skillId === sdk.skills.StaticField
+ && unit.distance > skill.range()
+ && me.inDanger(unit, skill.range() + 1)) {
+ return false;
+ }
+ return (me.mp > skill.manaCost()) && (!skill.timed || !overrides.checkSkillDelay || !me.skillDelay);
+ }) || -1;
+ };
+
+ /**
+ * Helper function to init damage value for unit
+ * @param {Monster} unit
+ */
+ const setDamageValues = function (unit) {
+ for (let [skillId, skill] of Skills) {
+ if (!skill.have()) continue;
+ skill._dmg = GameData.avgSkillDamage(skillId, unit);
+ }
+ };
+
+ /**
+ * Check if this skill is the most damaging
+ * @param {SkillDataInfo} checkSkill
+ * @returns {boolean}
+ */
+ const isHighestDmg = function (checkSkill) {
+ // eslint-disable-next-line no-unused-vars
+ for (let [_, skill] of Skills) {
+ if (!skill.have()) continue;
+ if (skill._dmg > checkSkill._dmg) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ /**
+ * Used to handle times when there isn't a valid skill we can use, to prevent throwing error
+ */
+ const DummyData = new function () {
+ this.have = false;
+ this.skillId = -1;
+ this.range = 0;
+ this.mana = 0;
+ this.dmg = 0;
+ this.timed = false;
+ this.reqLvl = 0;
+
+ this.manaCost = function () {
+ return 0;
+ };
+ };
+ /**
+ * Makes checking cases with it easier
+ */
+ const TELEPORT = Skill.get(sdk.skills.Teleport);
+
+ /**
+ * @param {Monster} unit
+ * @param {boolean} force
+ * @todo keep track of when, what, and who we last casted on to prevent spamming charged skills in a short period of time
+ */
+ ClassAttack.switchCurse = function (unit, force) {
+ if (!CharData.skillData.haveChargedSkill([sdk.skills.SlowMissiles, sdk.skills.LowerResist, sdk.skills.Weaken])) {
+ return;
+ }
+ if (unit.curseable) {
+ const gold = me.gold;
+ const isBoss = unit.isBoss;
+ const dangerZone = [sdk.areas.ChaosSanctuary, sdk.areas.ThroneofDestruction].includes(me.area);
+ if (force && checkCollision(me, unit, sdk.collision.Ranged)) {
+ if (!Attack.getIntoPosition(unit, 35, sdk.collision.Ranged)) return;
+ }
+ // If we have slow missles we might as well use it, currently only on Lighting Enchanted mobs as they are dangerous
+ // Might be worth it to use on souls too TODO: test this idea
+ if (CharData.skillData.haveChargedSkill(sdk.skills.SlowMissiles) && gold > 500000 && !isBoss
+ && unit.getEnchant(sdk.enchant.LightningEnchanted) && !unit.getState(sdk.states.SlowMissiles)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Cast slow missiles
+ Attack.castCharges(sdk.skills.SlowMissiles, unit);
+ }
+ // Handle Switch casting
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.LowerResist)
+ && (gold > 500000 || isBoss || dangerZone)
+ && !unit.getState(sdk.states.LowerResist)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast lower resist
+ Attack.switchCastCharges(sdk.skills.LowerResist, unit);
+ }
+
+ if (CharData.skillData.haveChargedSkillOnSwitch(sdk.skills.Weaken)
+ && (gold > 500000 || isBoss || dangerZone)
+ && !unit.getState(sdk.states.Weaken) && !unit.getState(sdk.states.LowerResist)
+ && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ // Switch cast weaken
+ Attack.switchCastCharges(sdk.skills.Weaken, unit);
+ }
+ }
+ };
+
+ /**
+ * @param {Monster} unit
+ * @param {boolean} [checkDelay]
+ * @returns {dataObj}
+ */
+ ClassAttack.decideDistanceSkill = function (unit, checkDelay = false) {
+ /**
+ * For now, no skill delay check.
+ * Things to consider:
+ * 1) If the skill we choose is timed and we are in skillDelay, how long is left to wait?
+ * 2) If not long then what is the damage difference between the skill we choose and the runner up non-timed skill
+ * 3) If the non-timed skill will do enough damage to kill this monster then use it, or if we have more than 1-2 seconds to wait
+ * and we don't need to move to cast the non-timed skill.
+ * 4) Anything else?
+ */
+ return decideAttack(unit, { checkSkillDelay: checkDelay, minRange: 20 });
+ };
+
+ /**
+ * @override
+ * @param {Monster} unit
+ * @param {boolean} recheckSkill
+ * @param {boolean} once
+ * @returns {AttackResult}
+ */
+ ClassAttack.doAttack = function (unit, recheckSkill = false, once = false) {
+ if (Developer.debugging.skills) {
+ console.log(sdk.colors.Green + "Test Start-----------------------------------------//");
+ }
+ // unit became invalidated
+ if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
+
+ const currLvl = me.charlvl;
+ const index = (unit.isSpecial || unit.isPlayer) ? 1 : 3;
+ let gid = unit.gid;
+ let tick = getTickCount();
+ let gold = me.gold;
+
+ if (Config.MercWatch && me.needMerc() && gold > me.mercrevivecost * 3) {
+ console.debug("mercwatch");
+
+ if (Town.visitTown()) {
+ // lost reference to the mob we were attacking
+ if (!unit || !copyUnit(unit).x || !Game.getMonster(-1, -1, gid) || unit.dead) {
+ console.debug("Lost reference to unit");
+ return Attack.Result.SUCCESS;
+ }
+ gold = me.gold; // reset value after town
+ }
+ }
+
+ // maybe every couple attacks or just the first one?
+ Precast.doPrecast();
+
+ // Handle Charge skill casting
+ if (index === 1 && me.expansion && !unit.dead) {
+ ClassAttack.switchCurse(unit);
+ }
+
+ TELEPORT.have();
+
+ if (Skills.get(sdk.skills.FrostNova).have()) {
+ if (me.mp > Skills.get(sdk.skills.FrostNova).manaCost()) {
+ frostNovaCheck() && Skill.cast(sdk.skills.FrostNova, sdk.skills.hand.Right);
+ let ticktwo = getTickCount();
+ // if the nova cause the death of any monsters around us, its worth it
+ if (GameData.calculateKillableFallensByFrostNova() > 0) {
+ if (Developer.debugging.skills) {
+ console.log(
+ "took " + ((getTickCount() - ticktwo) / 1000)
+ + " seconds to check calculateKillableFallensByFrostNova. frost nova will kill fallens"
+ );
+ }
+ Skill.cast(sdk.skills.FrostNova, sdk.skills.hand.Right);
+ }
+ }
+ }
+
+ if (Skills.get(sdk.skills.GlacialSpike).have()) {
+ if (me.mp > Skills.get(sdk.skills.GlacialSpike).manaCost() * 2) {
+ let shouldSpike = unit && unit.distance < 10 &&
+ getUnits(sdk.unittype.Monster).filter(function (el) {
+ return getDistance(el, unit) < 4 && slowable(el, true);
+ }).length > 1;
+ if (shouldSpike && !Coords_1.isBlockedBetween(me, unit)) {
+ Developer.debugging.skills && console.log("SPIKE");
+ Skill.cast(sdk.skills.GlacialSpike, sdk.skills.hand.Right, unit);
+ }
+ }
+ }
+
+ // We lost track of the mob or killed it
+ if (unit === undefined || !unit || !unit.attackable) return Attack.Result.SUCCESS;
+
+ // Set damage values
+ // redo gamedata to be more efficent
+ setDamageValues(unit);
+
+ // log damage values
+ // if (Developer.debugging.skills) {
+ // for (let [skillId, skill] of Skills) {
+ // console.log(getSkillById(skillId) + " : " + skill._dmg);
+ // }
+ // }
+
+ let rebuild = false;
+
+ // If we have enough mana for Static and it will do more damage than our other skills then duh use it
+ // should this return afterwards since the calulations will now be different?
+ if (Skills.get(sdk.skills.StaticField).have() && (Skills.get(sdk.skills.StaticField).manaCost() * 3) < me.mp) {
+ let closeMobCheck = getUnits(sdk.unittype.Monster)
+ .filter(function (unit) {
+ return !!unit && unit.attackable && unit.distance < Skills.get(sdk.skills.StaticField).range();
+ })
+ .find(function (unit) {
+ return Attack.checkResist(unit, "lightning") && unit.hpPercent > Config.CastStatic;
+ });
+ if (!!closeMobCheck && isHighestDmg(Skills.get(sdk.skills.StaticField))
+ && !Coords_1.isBlockedBetween(me, closeMobCheck)) {
+ Developer.debugging.skills && console.log("STATIC");
+ // check if we should use battle cry from cta if we have it
+ battleCryCheck(closeMobCheck);
+ [sdk.skills.StaticField, sdk.skills.StaticField]
+ .every(skill => Skill.cast(skill, sdk.skills.hand.Right, closeMobCheck));
+ rebuild = true;
+ }
+ }
+
+ // We lost track of the mob or killed it (recheck after using static)
+ if (unit === undefined || !unit || !unit.attackable) return Attack.Result.SUCCESS;
+
+ rebuild && setDamageValues(unit);
+
+ /**
+ * @todo static field is a good skill but if we are currently out of range,
+ * check how dangerous it is to tele to spot before choosing that as our skill
+ */
+ let selectedSkill = decideAttack(unit);
+ if (selectedSkill === -1) return Attack.Result.FAILED;
+
+ switch (selectedSkill.skillId) {
+ case sdk.skills.ChargedBolt:
+ if (selectedSkill.skillId === sdk.skills.ChargedBolt
+ && Skills.get(sdk.skills.IceBolt).have()
+ && slowable(unit)) {
+ selectedSkill = Skills.get(sdk.skills.IceBolt);
+ }
+
+ break;
+ case sdk.skills.Telekinesis:
+ // maybe check if we are able to telestomp?
+ if (!me.normal) {
+ selectedSkill = DummyData;
+ }
+
+ break;
+ case sdk.skills.Attack:
+ if (!me.normal || (me.charlvl > 6
+ && !me.checkForMobs({ range: 10, coll: (sdk.collision.BlockWall | sdk.collision.ClosedDoor) }))) {
+ selectedSkill = DummyData;
+ }
+ }
+
+ /**
+ * @param {Monster} unit
+ * @returns {AttackResult}
+ */
+ const switchBowAttack = function (unit) {
+ if (Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) {
+ try {
+ const checkForShamans = unit.isFallen && !me.inArea(sdk.areas.BloodMoor);
+ for (let i = 0; i < 5 && unit.attackable; i++) {
+ if (checkForShamans && !once) {
+ // before we waste time let's see if there is a shaman we should kill
+ const shaman = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.distance < 20 && mon.isShaman && mon.attackable;
+ })
+ .sort(function (a, b) {
+ return a.distance - b.distance;
+ })
+ .first();
+ if (shaman) return ClassAttack.doAttack(shaman, null, true);
+ }
+ if (!Attack.useBowOnSwitch(unit, sdk.skills.Attack, i === 5)) return Attack.Result.FAILED;
+ if (unit.distance < 8 || me.inDanger()) {
+ if (once) return Attack.Result.FAILED;
+ let closeMob = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.distance < 10 && mon.attackable && mon.gid !== gid;
+ })
+ .sort(Attack.walkingSortMonsters)
+ .first();
+ if (closeMob) return ClassAttack.doAttack(closeMob, null, true);
+ }
+ }
+ } finally {
+ me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
+ }
+ }
+ return unit.dead ? Attack.Result.SUCCESS : Attack.Result.FAILED;
+ };
+
+ if (CharData.skillData.bow.onSwitch
+ && (index !== 1 || !unit.name.includes(getLocaleString(sdk.locale.text.Ghostly)))
+ && ([-1, sdk.skills.Attack].includes(selectedSkill.skillId)
+ || selectedSkill.manaCost() > me.mp
+ || (selectedSkill.manaCost() * 3 > me.mp
+ && [sdk.skills.FireBolt, sdk.skills.ChargedBolt].includes(selectedSkill.skillId)))) {
+ if (switchBowAttack(unit) === Attack.Result.SUCCESS) return Attack.Result.SUCCESS;
+ }
+
+ if (selectedSkill.skillId === sdk.skills.Attack && me.inDanger(unit, 10)) {
+ // try to stay safer for now, probably should see if there are any easy targets we can pick off
+ return Attack.Result.CANTATTACK;
+ }
+
+ let result = ClassAttack.doCast(unit, selectedSkill);
+
+ switch (result) {
+ case Attack.Result.FAILED:
+ if (Developer.debugging.skills) {
+ console.log(
+ sdk.colors.Red + "Fail Test End----Time elasped["
+ + ((getTickCount() - tick) / 1000) + " seconds]----------------------//"
+ );
+ }
+
+ return Attack.Result.FAILED;
+ case Attack.Result.SUCCESS:
+ if (Developer.debugging.skills) {
+ console.log(
+ sdk.colors.Red + "Sucess Test End----Time elasped["
+ + ((getTickCount() - tick) / 1000) + " seconds]----------------------//"
+ );
+ }
+ lastAttack.setSkill(selectedSkill.skillId, gid);
+
+ return Attack.Result.SUCCESS;
+ case Attack.Result.CANTATTACK: // Try to telestomp
+ if (Pather.canTeleport() && Attack.checkResist(unit, "physical") && !!me.getMerc()
+ && Attack.validSpot(unit.x, unit.y)
+ && (Config.TeleStomp || (!me.hell && (unit.getMobCount(10) < me.maxNearMonsters && unit.isSpecial)))) {
+ let merc = me.getMerc();
+ let haveTK = Skill.canUse(sdk.skills.Telekinesis);
+ let mercRevive = 0;
+
+ while (unit.attackable) {
+ if (!unit) return Attack.Result.SUCCESS;
+
+ if (me.needMerc()) {
+ if (Config.MercWatch && mercRevive < 3) {
+ Town.visitTown() && (mercRevive++);
+ } 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 && Pather.walkTo(spot.x, spot.y);
+ }
+
+ if (Attack.checkResist(unit, "lightning") && Skills.get(sdk.skills.StaticField).have()
+ && unit.hpPercent > Config.CastStatic) {
+ Skill.cast(sdk.skills.StaticField, sdk.skills.hand.Right);
+ }
+
+ let closeMob = Attack.getNearestMonster({ skipGid: gid });
+ !!closeMob
+ ? this.doCast(closeMob, selectedSkill)
+ : haveTK && Packet.telekinesis(unit);
+ }
+
+ return Attack.Result.SUCCESS;
+ }
+
+ return Attack.Result.CANTATTACK;
+ default:
+ return result;
+ }
+ };
+
+ /**
+ * @override
+ * @param {Monster} unit
+ * @param {SkillDataInfo} choosenSkill
+ * @returns {AttackResult}
+ */
+ ClassAttack.doCast = function (unit, choosenSkill) {
+ let noMana = false;
+ let skill = choosenSkill.skillId;
+ let range = choosenSkill.range();
+ let mana = choosenSkill.manaCost();
+ let timed = choosenSkill.timed;
+ // unit became invalidated
+ if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
+ if (!!skill && me.mp < mana) {
+ return Attack.Result.NEEDMANA;
+ }
+ // No valid skills can be found
+ if (skill < 0) return Attack.Result.CANTATTACK;
- // log damage values
- if (Developer.debugging.skills) {
- Object.keys(data).forEach(k => typeof data[k] === "object" && console.log(getSkillById(data[k].skill) + " : " + data[k].dmg));
- }
-
- // If we have enough mana for Static and it will do more damage than our other skills then duh use it
- // should this return afterwards since the calulations will now be different?
- if (data.static.have && (data.static.mana * 3) < me.mp) {
- let closeMobCheck = getUnits(sdk.unittype.Monster)
- .filter(unit => !!unit && unit.attackable && unit.distance < data.static.range)
- .find(unit => Attack.checkResist(unit, "lightning") && unit.hpPercent > Config.CastStatic);
- if (!!closeMobCheck && data.static.dmg > Math.max(data.mainTimed.dmg, data.mainUntimed.dmg, data.secondaryTimed.dmg, data.secondaryUntimed.dmg) && !Coords_1.isBlockedBetween(me, closeMobCheck)) {
- Developer.debugging.skills && console.log("STATIC");
- Skill.cast(sdk.skills.StaticField, sdk.skills.hand.Right, closeMobCheck) && Skill.cast(sdk.skills.StaticField, sdk.skills.hand.Right, closeMobCheck);
- }
- }
-
- // We lost track of the mob or killed it (recheck after using static)
- if (unit === undefined || !unit || !unit.attackable) return true;
-
- /**
- * @todo static field is a good skill but if we are currently out of range, check how dangerous it is to tele to spot before choosing that as our skill
- */
- let sortedList = Object.keys(data)
- .filter(k => typeof data[k] === "object" && data[k].have && me.mp > data[k].mana
- && (!data[k].timed || !me.skillDelay) && (data[k].skill !== sdk.skills.StaticField || !recheckSkill))
- .sort((a, b) => data[b].dmg - data[a].dmg);
- let skillCheck = data[sortedList[0]].skill === sdk.skills.StaticField && unit.distance > data.static.range && me.inDanger(unit, 15)
- ? sortedList.at(1)
- : sortedList.at(0);
- let timedSkill = typeof data[skillCheck] === "object" ? data[skillCheck] : buildDataObj(-1);
-
- // throw in another attack when using charged bolt as sometimes it misses
- const lowManaData = {};
- lowManaData.fBolt = buildDataObj(sdk.skills.FireBolt);
- lowManaData.cBolt = buildDataObj(sdk.skills.ChargedBolt);
- lowManaData.iBolt = buildDataObj(sdk.skills.IceBolt);
- lowManaData.iBlast = buildDataObj(sdk.skills.IceBlast);
- lowManaData.tk = buildDataObj(sdk.skills.Telekinesis, 6, 20);
- lowManaData.attack = buildDataObj(sdk.skills.Attack, 1, 4);
- if (timedSkill.skill === sdk.skills.ChargedBolt && recheckSkill) {
- let temp = timedSkill;
- Object.keys(data).forEach(k => {
- if (typeof data[k] === "object" && data[k].have && compareDamage(temp, data[k])) {
- temp = data[k];
- }
- });
- if (temp.skill !== timedSkill.skill) {
- timedSkill = temp;
- }
- }
- if (!timedSkill.have || timedSkill.mana > me.mp) {
- Developer.debugging.skills && console.log("Choosing lower mana skill, Was I not able to use one of my better skills? (" + (!timedSkill.have) + "). Did I not have enough mana? " + (timedSkill.mana > me.mp));
- Object.keys(lowManaData).forEach(k => typeof lowManaData[k] === "object" && currLvl >= lowManaData[k].reqLvl && lowManaData[k].assignValues() && lowManaData[k].calcDmg(unit));
- const timedSkillCheck = Object.keys(lowManaData)
- .filter(k => typeof lowManaData[k] === "object" && lowManaData[k].have && me.mp > lowManaData[k].mana)
- .sort((a, b) => lowManaData[b].dmg - lowManaData[a].dmg).first();
- console.debug(timedSkillCheck);
- timedSkill = (() => {
- switch (true) {
- case !!timedSkillCheck && [lowManaData.tk.skill, lowManaData.attack.skill].indexOf(lowManaData[timedSkillCheck].skill) === -1:
- return lowManaData[timedSkillCheck];
- case !!timedSkillCheck && lowManaData[timedSkillCheck].skill === lowManaData.tk.skill && me.normal:
- return lowManaData.tk;
- default:
- if (me.charlvl < 5) return lowManaData.attack;
- return (me.normal && me.checkForMobs({range: 10, coll: (sdk.collision.BlockWall | sdk.collision.Objects | sdk.collision.ClosedDoor)}) ? lowManaData.attack : buildDataObj(-1));
- }
- })();
- Object.assign(data, lowManaData);
- }
-
- if (timedSkill.skill === sdk.skills.ChargedBolt && data.secondaryUntimed.skill === sdk.skills.IceBolt && data.secondaryUntimed.have && slowable(unit)) {
- timedSkill = data.secondaryUntimed;
- }
-
- const switchBowAttack = (unit) => {
- if (Attack.getIntoPosition(unit, 20, sdk.collision.Ranged)) {
- try {
- const checkForShamans = unit.isFallen && !me.inArea(sdk.areas.BloodMoor);
- for (let i = 0; i < 5 && unit.attackable; i++) {
- if (checkForShamans && !once) {
- // before we waste time let's see if there is a shaman we should kill
- const shaman = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.distance < 20 && mon.isShaman && mon.attackable)
- .sort((a, b) => a.distance - b.distance).first();
- if (shaman) return ClassAttack.doAttack(shaman, null, true);
- }
- if (!Attack.useBowOnSwitch(unit, sdk.skills.Attack, i === 5)) return Attack.Result.FAILED;
- if (unit.distance < 8 || me.inDanger()) {
- if (once) return Attack.Result.FAILED;
- let closeMob = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.distance < 10 && mon.attackable && mon.gid !== gid)
- .sort(Attack.walkingSortMonsters).first();
- if (closeMob) return ClassAttack.doAttack(closeMob, null, true);
- }
- }
- } finally {
- me.weaponswitch !== sdk.player.slot.Main && me.switchWeapons(sdk.player.slot.Main);
- }
- }
- return unit.dead ? Attack.Result.SUCCESS : Attack.Result.FAILED;
- };
-
- if (CharData.skillData.bowData.bowOnSwitch
- && (index !== 1 || !unit.name.includes(getLocaleString(sdk.locale.text.Ghostly)))
- && ([-1, sdk.skills.Attack].includes(timedSkill.skill)
- || timedSkill.mana > me.mp
- || (timedSkill.mana * 3 > me.mp && [sdk.skills.FireBolt, sdk.skills.ChargedBolt].includes(timedSkill.skill)))) {
- if (switchBowAttack(unit) === Attack.Result.SUCCESS) return Attack.Result.SUCCESS;
- }
-
- switch (ClassAttack.doCast(unit, timedSkill, data)) {
- case Attack.Result.FAILED:
- Developer.debugging.skills && console.log(sdk.colors.Red + "Fail Test End----Time elasped[" + ((getTickCount() - tick) / 1000) + " seconds]----------------------//");
- break;
- case Attack.Result.SUCCESS:
- Developer.debugging.skills && console.log(sdk.colors.Red + "Sucess Test End----Time elasped[" + ((getTickCount() - tick) / 1000) + " seconds]----------------------//");
- return Attack.Result.SUCCESS;
- case Attack.Result.CANTATTACK: // Try to telestomp
- if (Pather.canTeleport() && Attack.checkResist(unit, "physical") && !!me.getMerc()
- && Attack.validSpot(unit.x, unit.y)
- && (Config.TeleStomp || (!me.hell && (unit.getMobCount(10) < me.maxNearMonsters && unit.isSpecial)))) {
- let merc = me.getMerc();
- let haveTK = Skill.canUse(sdk.skills.Telekinesis);
- 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 < 3) {
- Town.visitTown() && (mercRevive++);
- } 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 && Pather.walkTo(spot.x, spot.y);
- }
-
- if (Attack.checkResist(unit, "lightning") && data.static.have && unit.hpPercent > Config.CastStatic) {
- Skill.cast(sdk.skills.StaticField, sdk.skills.hand.Right);
- }
-
- let closeMob = Attack.getNearestMonster({skipGid: gid});
- !!closeMob ? this.doCast(closeMob, timedSkill, data) : haveTK && Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit);
- }
-
- return Attack.Result.SUCCESS;
- }
-
- break;
- }
-
- return Attack.Result.FAILED;
-};
-
-ClassAttack.doCast = function (unit, choosenSkill, data) {
- let noMana = false;
- let { skill, range, mana, timed } = choosenSkill;
- // unit became invalidated
- if (!unit || !unit.attackable) return Attack.Result.SUCCESS;
- if (!!skill && me.mp < mana) {
- return Attack.Result.NEEDMANA;
- }
- // No valid skills can be found
- if (skill < 0) return Attack.Result.CANTATTACK;
-
- // print damage values
- Developer.debugging.skills && choosenSkill.have && console.log(sdk.colors.Yellow + "(Selected Main :: " + getSkillById(skill) + ") DMG: " + choosenSkill.dmg);
-
- if (![sdk.skills.FrostNova, sdk.skills.Nova, sdk.skills.StaticField].includes(skill)) {
- if (Skill.canUse(sdk.skills.Teleport) && me.mp > Skill.getManaCost(sdk.skills.Teleport) + mana && me.inDanger()) {
- //console.log("FINDING NEW SPOT");
- Attack.getIntoPosition(unit, range, 0
+ // print damage values
+ // if (Developer.debugging.skills && choosenSkill.have) {
+ if (Developer.debugging.skills && choosenSkill.have()) {
+ // console.log(sdk.colors.Yellow + "(Selected Main :: " + getSkillById(skill) + ") DMG: " + choosenSkill.dmg);
+ console.log(sdk.colors.Yellow + "(Selected Main :: " + getSkillById(skill) + ") DMG: " + choosenSkill._dmg);
+ }
+
+ if (![sdk.skills.FrostNova, sdk.skills.Nova, sdk.skills.StaticField].includes(skill)) {
+ // need like a potential danger check, sometimes while me might not be immeadiate danger because there aren't a whole
+ // lot of monsters around, we can suddenly be in danger if a ranged monsters hits us or if one of the monsters near us
+ // does a lot of damage quickly
+ // if (TELEPORT.have && me.mp > TELEPORT.mana + mana && me.inDanger()) {
+ if (TELEPORT.have() && me.mp > TELEPORT.manaCost() + mana && me.inDanger()) {
+ //console.log("FINDING NEW SPOT");
+ Attack.getIntoPosition(unit, range, 0
| Coords_1.BlockBits.LineOfSight
| Coords_1.BlockBits.Ranged
| Coords_1.BlockBits.Casting
| Coords_1.BlockBits.ClosedDoor
| Coords_1.BlockBits.Objects, false, true);
- } else if (me.inDanger()) {
- Attack.getIntoPosition(unit, range + 1, Coords_1.Collision.BLOCK_MISSILE, true);
- }
- }
-
- if (skill > -1 && (!me.skillDelay || !timed)) {
- let ranged = range > 4;
-
- if (skill === sdk.skills.ChargedBolt && !unit.hasEnchant(sdk.enchant.ManaBurn, sdk.enchant.ColdEnchanted)) {
- unit.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE) < 3 && (range = 7);
- }
-
- if (skill === sdk.skills.Attack) {
- if (me.hpPercent < 50 && me.mode !== sdk.player.mode.GettingHit && !me.checkForMobs({range: 12})) {
- console.log("Low health but safe right now, going to delay a bit");
- let tick = getTickCount();
- const howLongToDelay = Config.AttackSkill.some(sk => sk > 1 && Skill.canUse(sk)) ? Time.seconds(2) : Time.seconds(1);
-
- while (getTickCount() - tick < howLongToDelay) {
- if (me.mode === sdk.player.mode.GettingHit) {
- console.debug("no longer safe, we are being attacked");
- break;
- } else if (me.hpPercent >= 55) {
- return 3;
- }
-
- delay(40);
- }
- }
- }
-
- if (range < 4 && !Attack.validSpot(unit.x, unit.y)) return Attack.Result.FAILED;
-
- // Only delay if there are no mobs in our immediate area
- if (mana > me.mp && !me.checkForMobs({range: 12})) {
- let tick = getTickCount();
-
- while (getTickCount() - tick < 750) {
- if (mana < me.mp) {
- break;
- } else if (me.mode === sdk.player.mode.GettingHit) {
- console.debug("no longer safe, we are being attacked");
- return Attack.Result.NEEDMANA;
- }
-
- delay(25);
- }
- }
-
- // try to prevent missing when the monster is moving by getting just a bit closer
- if ([sdk.skills.FireBolt, sdk.skills.IceBolt].includes(skill)) {
- range = 12;
- }
- if (unit.distance > range || Coords_1.isBlockedBetween(me, unit)) {
- // Allow short-distance walking for melee skills
- let walk = (range < 4 || (skill === sdk.skills.ChargedBolt && range === 7)) && unit.distance < 10 && !checkCollision(me, unit, Coords_1.BlockBits.BlockWall);
-
- if (ranged) {
- if (!Attack.getIntoPosition(unit, range, Coords_1.Collision.BLOCK_MISSILE, walk)) return Attack.Result.FAILED;
- } else if (!Attack.getIntoPosition(unit, range, Coords_1.BlockBits.Ranged, walk)) {
- return Attack.Result.FAILED;
- }
- }
-
- if (!unit.dead && !checkCollision(me, unit, Coords_1.BlockBits.Ranged)) {
- if (skill === sdk.skills.ChargedBolt) {
- let preHealth = unit.hp;
- let cRetry = 0;
- unit.distance <= 1 && Attack.getIntoPosition(unit, range, Coords_1.Collision.BLOCK_MISSILE, true);
- for (let i = 0; i < 3; i++) {
- !unit.dead && Skill.cast(skill, Skill.getHand(skill), unit.x, unit.y);
- if (!Misc.poll(() => unit.dead || unit.hp < preHealth, 300, 50)) {
- cRetry++;
- // we still might of missed so pick another coord
- if (!Attack.getIntoPosition(unit, (range - cRetry), Coords_1.Collision.BLOCK_MISSILE, true)) return Attack.Result.FAILED;
- !unit.dead && Skill.cast(skill, Skill.getHand(skill), unit.x, unit.y);
- } else {
- break;
- }
- }
- } else if (skill === sdk.skills.StaticField) {
- let preHealth = unit.hp;
- let sRetry = 0;
- for (let i = 0; i < 4; i++) {
- if (!unit.dead) {
- Skill.cast(skill, Skill.getHand(skill), unit);
- if (!Misc.poll(() => unit.dead || unit.hp < preHealth, 200, 50)) {
- sRetry++;
- // we still might of missed so pick another coord
- if (!Attack.getIntoPosition(unit, (range - sRetry), Coords_1.Collision.BLOCK_MISSILE, true)) return Attack.Result.FAILED;
- !unit.dead && Skill.cast(skill, Skill.getHand(skill), unit);
- }
-
- if (data.frostNova.have && me.mp > data.frostNova.mana) {
- frostNovaCheck() && Skill.cast(sdk.skills.FrostNova, sdk.skills.hand.Right);
- }
-
- if (mana > me.mp || unit.hpPercent < Config.CastStatic) {
- break;
- }
- if (me.inDanger()) {
- Attack.deploy(unit, range, 5, 9);
- break;
- }
- } else {
- break;
- }
- }
- } else {
- let targetPoint = GameData.targetPointForSkill(skill, unit);
-
- if (unit.attackable) {
- if (targetPoint) {
- Skill.cast(skill, Skill.getHand(skill), targetPoint.x, targetPoint.y);
- } else {
- Skill.cast(skill, Skill.getHand(skill), unit);
- }
-
- if ([sdk.skills.FireBolt, sdk.skills.IceBolt].includes(skill)) {
- let preHealth = unit.hp;
- let missileDelay = GameData.timeTillMissleImpact(skill, unit);
- missileDelay > 0 && Misc.poll(() => unit.dead || unit.hp < preHealth, missileDelay, 50);
- delay(50);
- }
- }
- }
- }
-
- return Attack.Result.SUCCESS;
- } else {
- console.debug(choosenSkill);
- noMana = true;
- }
-
- for (let i = 0; i < 25; i++) {
- if (!me.skillDelay) {
- break;
- }
- if (i % 5 === 0) {
- if (me.inDanger()) {
- break;
- }
- }
-
- delay(40);
- }
-
- return noMana ? Attack.Result.NEEDMANA : Attack.Result.SUCCESS;
-};
+ } else if (me.inDanger()) {
+ Attack.getIntoPosition(unit, range + 1, Coords_1.Collision.BLOCK_MISSILE, true);
+ } else if (unit.distance < 3 && range > 4) {
+ // Attack.getIntoPositionEx(unit, {
+ // range: range,
+ // force: true,
+ // walk: Pather.useTeleport()
+ // });
+ Attack.getIntoPosition(unit, range, Coords_1.Collision.BLOCK_MISSILE, true);
+ }
+ }
+
+ if (!me.skillDelay || !timed) {
+ let ranged = range > 4;
+
+ if (skill === sdk.skills.ChargedBolt
+ && !unit.hasEnchant(sdk.enchant.ManaBurn, sdk.enchant.ColdEnchanted)) {
+ unit.getMobCount(6, Coords_1.Collision.BLOCK_MISSILE) < 3 && (range = 7);
+ }
+
+ if (skill === sdk.skills.Attack) {
+ if (me.hpPercent < 50 && me.mode !== sdk.player.mode.GettingHit && !me.checkForMobs({ range: 12 })) {
+ console.log("Low health but safe right now, going to delay a bit");
+ let tick = getTickCount();
+ const howLongToDelay = Config.AttackSkill
+ .some(sk => sk > 1 && Skill.canUse(sk)) ? Time.seconds(2) : Time.seconds(1);
+
+ while (getTickCount() - tick < howLongToDelay) {
+ if (me.mode === sdk.player.mode.GettingHit) {
+ console.debug("no longer safe, we are being attacked");
+ break;
+ } else if (me.hpPercent >= 55) {
+ return 3;
+ }
+
+ delay(40);
+ }
+ }
+ }
+
+ if (range < 4 && !Attack.validSpot(unit.x, unit.y)) {
+ return Attack.Result.FAILED;
+ }
+
+ // Only delay if there are no mobs in our immediate area
+ if (mana > me.mp && !me.checkForMobs({ range: 12 })) {
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < 750) {
+ if (mana < me.mp) {
+ break;
+ } else if (me.mode === sdk.player.mode.GettingHit) {
+ console.debug("no longer safe, we are being attacked");
+ return Attack.Result.NEEDMANA;
+ }
+
+ delay(25);
+ }
+ }
+
+ // try to prevent missing when the monster is moving by getting just a bit closer
+ if ([sdk.skills.FireBolt, sdk.skills.IceBolt].includes(skill)) {
+ range = 12;
+ }
+
+ if (unit.distance > range || Coords_1.isBlockedBetween(me, unit)) {
+ // Allow short-distance walking for melee skills
+ let walk = (
+ (range < 4 || (skill === sdk.skills.ChargedBolt && range === 7))
+ && unit.distance < 10 && !checkCollision(me, unit, sdk.collision.BlockWalk)
+ );
+
+ // todo - handle nova/frost nova, BLOCK_MISSILE doesn't apply for them
+ if (ranged) {
+ if (!Attack.getIntoPosition(unit, range, Coords_1.Collision.BLOCK_MISSILE, walk)) {
+ return Attack.Result.FAILED;
+ }
+ } else if (!Attack.getIntoPosition(unit, range, Coords_1.BlockBits.Ranged, walk)) {
+ return Attack.Result.FAILED;
+ } /* else if (!Attack.getIntoPositionEx(unit, { range: range, coll: sdk.collision.LineOfSight, walk: walk })) {
+ return Attack.Result.FAILED;
+ } */
+ }
+
+ if (!unit.dead && !checkCollision(me, unit, Coords_1.BlockBits.Ranged)) {
+ if (skill === sdk.skills.ChargedBolt) {
+ let preHealth = unit.hp;
+ let cRetry = 0;
+ unit.distance <= 1 && Attack.getIntoPosition(unit, range, Coords_1.Collision.BLOCK_MISSILE, true);
+ for (let i = 0; i < 3; i++) {
+ !unit.dead && Skill.cast(skill, Skill.getHand(skill), unit.x, unit.y);
+ if (!Misc.poll(() => unit.dead || unit.hp < preHealth, 300, 50)) {
+ cRetry++;
+ // we still might of missed so pick another coord
+ if (!Attack.getIntoPosition(unit, (range - cRetry), Coords_1.Collision.BLOCK_MISSILE, true)) {
+ return Attack.Result.FAILED;
+ }
+ !unit.dead && Skill.cast(skill, Skill.getHand(skill), unit.x, unit.y);
+ } else {
+ break;
+ }
+ }
+ } else if (skill === sdk.skills.StaticField) {
+ let preHealth = unit.hp;
+ let sRetry = 0;
+ for (let i = 0; i < 4; i++) {
+ if (!unit.dead) {
+ // if we are already in close then it might be worth it to use battle cry if we have it
+ battleCryCheck(unit);
+ // if we are in danger then don't cast and move - this is causing too much rubberbanding
+ // if (!unit.isPrimeEvil
+ // && (unit.distance <= 3 && !unit.isStunned && !unit.isFrozen) || me.inDanger()) {
+ // // don't cast, just run
+ // Attack.deploy(unit, 25, 5, 9);
+ // return Attack.Result.FAILED;
+ // }
+ Skill.cast(skill, Skill.getHand(skill), unit);
+ if (!Misc.poll(() => unit.dead || unit.hp < preHealth, 200, 50)) {
+ sRetry++;
+ // we still might of missed so pick another coord
+ if (!Attack.getIntoPosition(unit, (range - sRetry), Coords_1.Collision.BLOCK_MISSILE, true)) {
+ return Attack.Result.FAILED;
+ }
+ !unit.dead && Skill.cast(skill, Skill.getHand(skill), unit);
+ }
+
+ if (Skills.get(sdk.skills.FrostNova).have() && me.mp > Skills.get(sdk.skills.FrostNova).manaCost()) {
+ frostNovaCheck() && Skill.cast(sdk.skills.FrostNova, sdk.skills.hand.Right);
+ }
+
+ if (mana > me.mp || unit.hpPercent < Config.CastStatic) {
+ break;
+ }
+ if (me.inDanger()) {
+ Attack.deploy(unit, range, 5, 9);
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ } else {
+ let targetPoint = GameData.targetPointForSkill(skill, unit);
+
+ if (unit.attackable) {
+ if (targetPoint) {
+ Skill.cast(skill, Skill.getHand(skill), targetPoint.x, targetPoint.y);
+ } else {
+ Skill.cast(skill, Skill.getHand(skill), unit);
+ }
+
+ if ([sdk.skills.FireBolt, sdk.skills.IceBolt].includes(skill)) {
+ let preHealth = unit.hp;
+ let missileDelay = GameData.timeTillMissleImpact(skill, unit);
+ if (missileDelay > 0) {
+ Misc.poll(function () {
+ return unit.dead || unit.hp < preHealth;
+ }, missileDelay, 50);
+ }
+ delay(50);
+ }
+ }
+ }
+ }
+
+ return Attack.Result.SUCCESS;
+ } else {
+ noMana = true;
+ }
+
+ for (let i = 0; i < 25; i++) {
+ if (!me.skillDelay) {
+ break;
+ }
+ if (i % 5 === 0) {
+ if (me.inDanger()) {
+ break;
+ }
+ }
+
+ delay(40);
+ }
+
+ return noMana ? Attack.Result.NEEDMANA : Attack.Result.SUCCESS;
+ };
+})();
diff --git a/libs/SoloPlay/Functions/ConfigOverrides.js b/libs/SoloPlay/Functions/ConfigOverrides.js
index 37f8a8c4..31f62530 100644
--- a/libs/SoloPlay/Functions/ConfigOverrides.js
+++ b/libs/SoloPlay/Functions/ConfigOverrides.js
@@ -5,65 +5,58 @@
*
*/
-includeIfNotIncluded("common/Config.js");
+includeIfNotIncluded("core/Config.js");
Config.init = function (notify) {
- 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",
- }))(sdk.player.class.nameOf(me.classid), me.profile, me.charname, me.realm);
- let configFilename = "";
-
- for (let i = 1; i <= 5; i++) {
- configFilename = formats[i];
-
- if (configFilename && FileTools.exists("libs/SoloPlay/Config/" + configFilename)) {
- break;
- }
- }
-
- try {
- if (!include("SoloPlay/Config/" + configFilename)) {
- throw new Error();
- }
- notify && console.log("ÿc2Loaded: ÿc9SoloPlay/Config/" + configFilename);
- } catch (e1) {
- console.error("ÿc1" + e1 + "\nÿc0If you are seeing this message you likely did not copy over all the files or are using the wrong kolbot version.");
- D2Bot.printToConsole("Please return to the kolbot-SoloPlay main github page and read the readMe. https://github.com/blizzhackers/kolbot-SoloPlay#readme", sdk.colors.D2Bot.Orange);
-
- throw new Error("Failed to load character config.");
- }
-
- try {
- LoadConfig.call();
- } catch (e2) {
- if (notify) {
- console.log("ÿ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) => console.log("Tryed to say: " + what);
- }
-
- try {
- if (Config.AutoBuild.Enabled === true && include("SoloPlay/Functions/AutoBuildOverrides.js")) {
- AutoBuild.initialize();
- }
- } catch (e3) {
- console.log("ÿc8Error in libs/SoloPlay/Functions/AutoBuildOverrides.js (AutoBuild system is not active!)");
- console.log(e3.toSource());
- }
+ 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",
+ }))(sdk.player.class.nameOf(me.classid), me.profile, me.charname, me.realm);
+ let configFilename = "";
+
+ for (let i = 1; i <= 5; i++) {
+ configFilename = formats[i];
+
+ if (configFilename && FileTools.exists("libs/SoloPlay/Config/" + configFilename)) {
+ break;
+ }
+ }
+
+ try {
+ if (!include("SoloPlay/Config/" + configFilename)) {
+ throw new Error();
+ }
+ notify && console.log("ÿc2Loaded: ÿc9SoloPlay/Config/" + configFilename);
+ } catch (e1) {
+ console.error("ÿc1" + e1 + "\nÿc0If you are seeing this message you likely did not copy over all the files or are using the wrong kolbot version.");
+ D2Bot.printToConsole("Please return to the kolbot-SoloPlay main github page and read the readMe. https://github.com/blizzhackers/kolbot-SoloPlay#readme", sdk.colors.D2Bot.Orange);
+
+ throw new Error("Failed to load 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 = function (what) {
+ console.log("Tryed to say: " + what);
+ };
+ }
+
+ try {
+ if (Config.AutoBuild.Enabled === true && include("SoloPlay/Functions/AutoBuild.js")) {
+ AutoBuild.initialize();
+ }
+ } catch (e3) {
+ console.log("ÿc8Error in libs/SoloPlay/Functions/AutoBuild.js (AutoBuild system is not active!)");
+ console.error(e3);
+ }
};
+Config.UseMerc = true;
diff --git a/libs/SoloPlay/Functions/CubingOverrides.js b/libs/SoloPlay/Functions/CubingOverrides.js
index 0dd8e910..68a2298b 100644
--- a/libs/SoloPlay/Functions/CubingOverrides.js
+++ b/libs/SoloPlay/Functions/CubingOverrides.js
@@ -5,7 +5,7 @@
*
*/
-includeIfNotIncluded("common/Cubing.js");
+includeIfNotIncluded("core/Cubing.js");
Recipe.Reroll.Charm = 56;
Recipe.Socket.LowMagic = 57;
@@ -13,600 +13,708 @@ Recipe.Socket.HighMagic = 58;
Recipe.Socket.Rare = 59;
Cubing.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
- && ((Config.Recipes[i][0] !== Recipe.Reroll.Charm && typeof Config.Recipes[i][2] !== "number") || (Config.Recipes[i][0] === Recipe.Reroll.Charm && typeof Config.Recipes[i][2] !== "object")))
- || 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;
- // Crafting Recipes----------------------------------------------------------------------------------------------------------------------------------//
- 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});
+ this.recipes = [];
+
+ for (let i = 0; i < Config.Recipes.length; i += 1) {
+ if (typeof Config.Recipes[i] !== "object"
+ || (Config.Recipes[i].length > 2
+ && ((Config.Recipes[i][0] !== Recipe.Reroll.Charm && typeof Config.Recipes[i][2] !== "number") || (Config.Recipes[i][0] === Recipe.Reroll.Charm && typeof Config.Recipes[i][2] !== "object")))
+ || 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;
+ // Crafting Recipes----------------------------------------------------------------------------------------------------------------------------------//
+ 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.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.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.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.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.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.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.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.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.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.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;
- // Upgrading Recipes----------------------------------------------------------------------------------------------------------------------------------//
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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;
- // Socketing Recipes----------------------------------------------------------------------------------------------------------------------------------//
- 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.Socket.LowMagic:
- this.recipes.push({Ingredients: [Config.Recipes[i][1], "cgem", "cgem", "cgem"], Level: 25, Index: Recipe.Socket.LowMagic});
-
- break;
- case Recipe.Socket.HighMagic:
- this.recipes.push({Ingredients: [Config.Recipes[i][1], "fgem", "fgem", "fgem"], Level: 30, Index: Recipe.Socket.HighMagic});
-
- break;
- case Recipe.Socket.Rare:
- this.recipes.push({Ingredients: [Config.Recipes[i][1], sdk.items.Ring, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull], Index: Recipe.Socket.Rare});
-
- break;
- // Re-rolling Recipes----------------------------------------------------------------------------------------------------------------------------------//
- 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.Charm:
- this.recipes.push({Ingredients: [Config.Recipes[i][1], "pgem", "pgem", "pgem"], Level: Object.assign({"cm1": 95, "cm2": 91, "cm3": 91}, Config.Recipes[i][2]), Index: Recipe.Reroll.Charm});
-
- 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;
- // Rune Recipes----------------------------------------------------------------------------------------------------------------------------------//
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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 || Developer.addLadderRW) {
- 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;
- }
- }
+ 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;
+ // Upgrading Recipes----------------------------------------------------------------------------------------------------------------------------------//
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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;
+ // Socketing Recipes----------------------------------------------------------------------------------------------------------------------------------//
+ 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.Socket.LowMagic:
+ this.recipes.push({ Ingredients: [Config.Recipes[i][1], "cgem", "cgem", "cgem"], Level: 25, Index: Recipe.Socket.LowMagic });
+
+ break;
+ case Recipe.Socket.HighMagic:
+ this.recipes.push({ Ingredients: [Config.Recipes[i][1], "fgem", "fgem", "fgem"], Level: 30, Index: Recipe.Socket.HighMagic });
+
+ break;
+ case Recipe.Socket.Rare:
+ this.recipes.push({ Ingredients: [Config.Recipes[i][1], sdk.items.Ring, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull, sdk.items.gems.Perfect.Skull], Index: Recipe.Socket.Rare });
+
+ break;
+ // Re-rolling Recipes----------------------------------------------------------------------------------------------------------------------------------//
+ 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.Charm:
+ this.recipes.push({ Ingredients: [Config.Recipes[i][1], "pgem", "pgem", "pgem"], Level: Object.assign({ "cm1": 95, "cm2": 91, "cm3": 91 }, Config.Recipes[i][2]), Index: Recipe.Reroll.Charm });
+
+ 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;
+ // Rune Recipes----------------------------------------------------------------------------------------------------------------------------------//
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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 || Developer.addLadderRW) {
+ 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;
+ }
+ }
};
+/** @this Cubing */
Cubing.buildLists = function () {
- CraftingSystem.checkSubrecipes();
- SoloWants.checkSubrecipes();
-
- this.validIngredients = [];
- this.neededIngredients = [];
- let items = me.getItemsEx()
- .filter(item => [sdk.items.mode.inStorage, sdk.items.mode.Equipped].includes(item.mode))
- .sort((a, b) => b.ilvl - a.ilvl);
-
- 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] === "fgem" && [sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Topaz, sdk.items.gems.Flawed.Sapphire, sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull].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, quality: items[k].quality, ilvl: items[k].ilvl, gid: items[k].gid, recipe: this.recipes[i]});
-
- // 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
- // Enable rune recipe after 2 bases are found
- if (this.recipes[i].Index !== Recipe.Rune || (this.recipes[i].Index === Recipe.Rune && j >= 1)) {
- 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)
-
- 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 flawless amethyst
- if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Amethyst) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Amethyst || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Amethyst) > -1))) {
- this.recipes.push({Ingredients: [sdk.items.gems.Normal.Amethyst, sdk.items.gems.Normal.Amethyst, sdk.items.gems.Normal.Amethyst], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index});
- this.subRecipes.push(sdk.items.gems.Flawless.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 flawless topaz
- if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Topaz) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Topaz || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Topaz) > -1))) {
- this.recipes.push({Ingredients: [sdk.items.gems.Normal.Topaz, sdk.items.gems.Normal.Topaz, sdk.items.gems.Normal.Topaz], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index});
- this.subRecipes.push(sdk.items.gems.Flawless.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 flawless sapphire
- if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Sapphire) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Sapphire || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Sapphire) > -1))) {
- this.recipes.push({Ingredients: [sdk.items.gems.Normal.Sapphire, sdk.items.gems.Normal.Sapphire, sdk.items.gems.Normal.Sapphire], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index});
- this.subRecipes.push(sdk.items.gems.Flawless.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 flawless emerald
- if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Emerald) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Emerald || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Emerald) > -1))) {
- this.recipes.push({Ingredients: [sdk.items.gems.Normal.Emerald, sdk.items.gems.Normal.Emerald, sdk.items.gems.Normal.Emerald], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index});
- this.subRecipes.push(sdk.items.gems.Flawless.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 flawless ruby
- if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Ruby) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Ruby || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Ruby) > -1))) {
- this.recipes.push({Ingredients: [sdk.items.gems.Normal.Ruby, sdk.items.gems.Normal.Ruby, sdk.items.gems.Normal.Ruby], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index});
- this.subRecipes.push(sdk.items.gems.Flawless.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 flawless diamond
- if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Diamond) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Diamond || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Diamond) > -1))) {
- this.recipes.push({Ingredients: [sdk.items.gems.Normal.Diamond, sdk.items.gems.Normal.Diamond, sdk.items.gems.Normal.Diamond], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index});
- this.subRecipes.push(sdk.items.gems.Flawless.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);
- }
-
- // Make flawless skull
- if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Skull) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Skull || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Skull) > -1))) {
- this.recipes.push({Ingredients: [sdk.items.gems.Normal.Skull, sdk.items.gems.Normal.Skull, sdk.items.gems.Normal.Skull], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index});
- this.subRecipes.push(sdk.items.gems.Flawless.Skull);
- }
- }
- }
+ CraftingSystem.checkSubrecipes();
+ SoloWants.checkSubrecipes();
+
+ this.validIngredients = [];
+ this.neededIngredients = [];
+ let items = me.getItemsEx()
+ .filter(item => [sdk.items.mode.inStorage, sdk.items.mode.Equipped].includes(item.mode))
+ .sort((a, b) => b.ilvl - a.ilvl);
+ /**
+ * @param {ItemUnit} item
+ * @param {*} recipe
+ */
+ const ingredientObj = (item, recipe) => ({
+ classid: item.classid,
+ type: item.itemType,
+ quality: item.quality,
+ ilvl: item.ilvl,
+ gid: item.gid,
+ recipe: recipe,
+ });
+
+ 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] === "fgem" && [sdk.items.gems.Flawless.Amethyst, sdk.items.gems.Flawless.Topaz, sdk.items.gems.Flawed.Sapphire, sdk.items.gems.Flawless.Emerald, sdk.items.gems.Flawless.Ruby, sdk.items.gems.Flawless.Diamond, sdk.items.gems.Flawless.Skull].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(ingredientObj(items[k], Cubing.recipes[i]));
+
+ // 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
+ // Enable rune recipe after 2 bases are found
+ if (this.recipes[i].Index !== Recipe.Rune || (this.recipes[i].Index === Recipe.Rune && j >= 1)) {
+ 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)
+
+ 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 flawless amethyst
+ if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Amethyst) === -1
+ && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Amethyst
+ || (this.recipes[i].Ingredients[j] === "fgem"
+ && this.gemList.indexOf(sdk.items.gems.Flawless.Amethyst) > -1))) {
+ this.recipes.push({
+ Ingredients: [
+ sdk.items.gems.Normal.Amethyst,
+ sdk.items.gems.Normal.Amethyst,
+ sdk.items.gems.Normal.Amethyst
+ ],
+ Index: Recipe.Gem,
+ AlwaysEnabled: true,
+ MainRecipe: this.recipes[i].Index
+ });
+ this.subRecipes.push(sdk.items.gems.Flawless.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 flawless topaz
+ if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Topaz) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Topaz || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Topaz) > -1))) {
+ this.recipes.push({ Ingredients: [sdk.items.gems.Normal.Topaz, sdk.items.gems.Normal.Topaz, sdk.items.gems.Normal.Topaz], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index });
+ this.subRecipes.push(sdk.items.gems.Flawless.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 flawless sapphire
+ if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Sapphire) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Sapphire || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Sapphire) > -1))) {
+ this.recipes.push({ Ingredients: [sdk.items.gems.Normal.Sapphire, sdk.items.gems.Normal.Sapphire, sdk.items.gems.Normal.Sapphire], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index });
+ this.subRecipes.push(sdk.items.gems.Flawless.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 flawless emerald
+ if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Emerald) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Emerald || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Emerald) > -1))) {
+ this.recipes.push({ Ingredients: [sdk.items.gems.Normal.Emerald, sdk.items.gems.Normal.Emerald, sdk.items.gems.Normal.Emerald], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index });
+ this.subRecipes.push(sdk.items.gems.Flawless.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 flawless ruby
+ if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Ruby) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Ruby || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Ruby) > -1))) {
+ this.recipes.push({ Ingredients: [sdk.items.gems.Normal.Ruby, sdk.items.gems.Normal.Ruby, sdk.items.gems.Normal.Ruby], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index });
+ this.subRecipes.push(sdk.items.gems.Flawless.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 flawless diamond
+ if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Diamond) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Diamond || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Diamond) > -1))) {
+ this.recipes.push({ Ingredients: [sdk.items.gems.Normal.Diamond, sdk.items.gems.Normal.Diamond, sdk.items.gems.Normal.Diamond], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index });
+ this.subRecipes.push(sdk.items.gems.Flawless.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);
+ }
+
+ // Make flawless skull
+ if (this.subRecipes.indexOf(sdk.items.gems.Flawless.Skull) === -1 && (this.recipes[i].Ingredients[j] === sdk.items.gems.Flawless.Skull || (this.recipes[i].Ingredients[j] === "fgem" && this.gemList.indexOf(sdk.items.gems.Flawless.Skull) > -1))) {
+ this.recipes.push({ Ingredients: [sdk.items.gems.Normal.Skull, sdk.items.gems.Normal.Skull, sdk.items.gems.Normal.Skull], Index: Recipe.Gem, AlwaysEnabled: true, MainRecipe: this.recipes[i].Index });
+ this.subRecipes.push(sdk.items.gems.Flawless.Skull);
+ }
+ }
+ }
};
// Added try again to emptying cube if it fails it will clear inventory then organize it
Cubing.emptyCube = function () {
- let cube = me.getItem(sdk.items.quest.Cube);
- let items = me.findItems(-1, -1, sdk.storage.Cube);
- if (!cube || !items) return false;
-
- while (items.length) {
- !getUIFlag(sdk.uiflags.Cube) && Cubing.openCube();
-
- if (!Storage.Stash.MoveTo(items[0]) && !Storage.Inventory.MoveTo(items[0])) {
- Town.clearInventory();
- Town.sortInventory();
-
- if (!Storage.Stash.MoveTo(items[0]) && !Storage.Inventory.MoveTo(items[0])) {
- return false;
- }
- }
-
- items.shift();
- }
-
- return true;
+ const locToName = {};
+ locToName[sdk.storage.Cube] = "Cube";
+ locToName[sdk.storage.Inventory] = "Inventory";
+ locToName[sdk.storage.Stash] = "Stash";
+ /** @param {ItemUnit} item */
+ const prettyPrint = function (item) {
+ return item && ("- " + item.prettyPrint + " Location: " + (locToName[item.location] || "") + "\n");
+ };
+
+ const cube = me.getItem(sdk.items.quest.Cube);
+ if (!cube) return false;
+
+ const items = me.findItems(-1, -1, sdk.storage.Cube);
+ if (!items) return true;
+
+ items.sort(function (a, b) {
+ return b.sizex * b.sizey - a.sizex * a.sizey;
+ });
+
+ /** @type {ItemUnit[]} */
+ const failedItems = [];
+ let [invoSorted, stashSorted, failed] = [false, false, false];
+
+ while (items.length) {
+ const item = items[0];
+
+ item.isInCube && !getUIFlag(sdk.uiflags.Cube) && Cubing.openCube();
+
+ if (item.isInCube && Storage.Inventory.CanFit(item) && Storage.Inventory.MoveTo(item)) {
+ // Move anything we can to the inventory first, so we don't have to open/close the cube
+ items.push(item);
+ items.shift();
+ continue;
+ }
+
+ if (!invoSorted && !Storage.Inventory.CanFit(item)) {
+ Town.clearInventory();
+ me.sortInventory();
+ invoSorted = true;
+ }
+
+ if (!stashSorted && !Storage.Stash.CanFit(item)) {
+ Town.sortStash();
+ stashSorted = true;
+ }
+
+ if (!Storage.Stash.MoveTo(item)) {
+ failed = true;
+ failedItems.push(item);
+ }
+
+ items.shift();
+ }
+
+ this.closeCube();
+
+ if (failed) {
+ console.log("Failed to get all items from cube to stash. Items left: \n" + failedItems.map(prettyPrint).join(", "));
+ }
+
+ return !failed;
};
-
+/** @param {ItemUnit} unit */
Cubing.checkItem = function (unit) {
- if (!Config.Cubing || !unit) return false;
-
- for (let i = 0; i < this.validIngredients.length; i++) {
- // not the same item but the same type of item
- if (unit.mode !== sdk.items.mode.Equipped && unit.gid !== this.validIngredients[i].gid && unit.classid === this.validIngredients[i].classid && unit.quality === this.validIngredients[i].quality) {
- // item is better than the one we currently have, so add it to validIngredient array and remove old item
- if (unit.ilvl > this.validIngredients[i].ilvl && this.validItem(unit, this.validIngredients[i].recipe)) {
- this.validIngredients.push({classid: unit.classid, quality: unit.quality, ilvl: unit.ilvl, gid: unit.gid, recipe: this.validIngredients[i].recipe});
- this.validIngredients.splice(i, 1);
- return true;
- }
- }
- }
-
- if (this.keepItem(unit)) {
- return true;
- }
-
- for (let i = 0; i < this.neededIngredients.length; i++) {
- if (unit.classid === this.neededIngredients[i].classid && this.validItem(unit, this.neededIngredients[i].recipe)) {
- return true;
- }
- }
-
- return false;
+ if (!Config.Cubing || !unit) return false;
+
+ for (let i = 0; i < this.validIngredients.length; i++) {
+ // not the same item but the same type of item
+ if (unit.mode !== sdk.items.mode.Equipped && unit.gid !== this.validIngredients[i].gid
+ && unit.classid === this.validIngredients[i].classid && unit.quality === this.validIngredients[i].quality) {
+ // item is better than the one we currently have, so add it to validIngredient array and remove old item
+ if (unit.ilvl > this.validIngredients[i].ilvl && this.validItem(unit, this.validIngredients[i].recipe)) {
+ this.validIngredients.push({
+ classid: unit.classid,
+ quality: unit.quality,
+ ilvl: unit.ilvl,
+ gid: unit.gid,
+ recipe: this.validIngredients[i].recipe
+ });
+ this.validIngredients.splice(i, 1);
+ return true;
+ }
+ }
+ // its an item meant for socketing so lets be sure we have the best base
+ if (this.validIngredients[i].recipe.Index >= Recipe.Socket.Shield
+ && this.validIngredients[i].recipe.Index <= Recipe.Socket.Helm) {
+ // not the same item but the same type of item
+ if (!unit.isEquipped && unit.gid !== this.validIngredients[i].gid
+ && unit.itemType === this.validIngredients[i].type
+ && unit.quality === this.validIngredients[i].quality) {
+ // console.debug(this.validIngredients[i], "\n//~~~~//\n", unit, "\n//~~~~~/\n", Item.betterThanStashed(unit, true));
+ // item is better than the one we currently have, so add it to validIngredient array and remove old item
+ if (Item.betterThanStashed(unit, true) && this.validItem(unit, this.validIngredients[i].recipe)) {
+ this.validIngredients.push({
+ classid: unit.classid,
+ type: unit.itemType,
+ quality: unit.quality,
+ ilvl: unit.ilvl,
+ gid: unit.gid,
+ recipe: this.validIngredients[i].recipe
+ });
+ this.validIngredients.splice(i, 1);
+ return true;
+ }
+ }
+ }
+ }
+
+ if (this.keepItem(unit)) {
+ return true;
+ }
+
+ for (let el of this.neededIngredients) {
+ if (unit.classid === el.classid && this.validItem(unit, el.recipe)) {
+ return true;
+ }
+ }
+
+ return false;
};
/**
@@ -614,277 +722,295 @@ Cubing.checkItem = function (unit) {
* @param {*} recipe
*/
Cubing.validItem = function (unit, recipe) {
- // Excluded items
- // Don't use items in locked inventory space
- if (unit.isInInventory && Storage.Inventory.IsLocked(unit, Config.Inventory)) return false;
- // Don't use items that are wanted by other systems
- if (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
- let valid = true;
- const ntipResult = NTIP.CheckItem(unit);
- const ntipToTierResult = NTIP.CheckItem(unit, NTIP_CheckListNoTier);
-
- if (recipe.Index >= Recipe.HitPower.Helm && recipe.Index <= Recipe.Safety.Weapon) {
- if (Math.floor(me.charlvl / 2) + Math.floor(unit.ilvl / 2) < recipe.Level) {
- if (me.charlvl < 50) {
- // set it equal to ilvl 31 where 60% chance of 2 affixes and 20% chance each of 3 or 4 affixes
- recipe.Level = 31;
- } else if (me.charlvl > 50 && me.charlvl < 70) {
- // set it equal to ilvl 51 where 80% chance of 3 affixes and 20% chance of 4 affixes
- recipe.Level = 51;
- } else if (me.charlvl > 70 && me.charlvl < 93) {
- // set it equal to ilvl 71 where 100% chance of 4 affixes
- recipe.Level = 71;
- }
- }
- // 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
- && NTIP.CheckItem(unit, NTIP_CheckListNoTier) === Pickit.Result.UNWANTED) {
- return true;
- }
-
- return false;
- } else if (recipe.Index >= Recipe.Unique.Weapon.ToExceptional && recipe.Index <= Recipe.Unique.Armor.ToElite) {
- // If item is equipped, ensure we can use the upgraded version
- if (unit.isEquipped) {
- if (me.charlvl < unit.upgradedLvlReq || me.trueStr < unit.upgradedStrReq || me.trueDex < unit.upgradedDexReq) {
- return false;
- }
- }
- // Unique item matching pickit entry
- if (unit.unique && ntipResult === Pickit.Result.WANTED) {
- // check items name (prevents upgrading lavagout when we want to be upgrading magefist for the second time)
- if (recipe.Name !== undefined) {
- valid = !!unit.fname.toLowerCase().includes(recipe.Name.toLowerCase());
- if (valid) {
- // check to see if we are using this already and if so compare base stats to see if this one would be better
- // ignore things that get re-rolled like defense or min/max dmg just focus on base stats like enhanced defense/damage
- let equipped = me.getItemsEx(-1, sdk.storage.Equipped).find(item => item.fname.toLowerCase().includes(recipe.Name.toLowerCase()));
- if (equipped) {
- switch (recipe.Name.toLowerCase()) {
- case "magefist":
- // compare enhanced defense - keep "equal to" because base defense gets re-rolled so it might turn out better
- valid = (unit.getStat(sdk.stats.ArmorPercent) >= equipped.getStat(sdk.stats.ArmorPercent));
- break;
- }
- }
- }
- }
- switch (recipe.Ethereal) {
- case Roll.All:
- case undefined:
- return valid && ntipResult === Pickit.Result.WANTED;
- case Roll.Eth:
- return valid && unit.ethereal && ntipResult === Pickit.Result.WANTED;
- case Roll.NonEth:
- return valid && !unit.ethereal && ntipResult === Pickit.Result.WANTED;
- }
- }
-
- return false;
- } else if (recipe.Index >= Recipe.Rare.Weapon.ToExceptional && recipe.Index <= Recipe.Rare.Armor.ToElite) {
- // If item is equipped, ensure we can use the upgraded version
- if (unit.isEquipped) {
- if (me.charlvl < unit.upgradedLvlReq || me.trueStr < unit.upgradedStrReq || me.trueDex < unit.upgradedDexReq) {
- return false;
- }
- }
- // Rare item matching pickit entry
- if (unit.rare && ntipResult === Pickit.Result.WANTED) {
- 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;
- } else if (recipe.Index >= Recipe.Socket.Shield && recipe.Index <= Recipe.Socket.Helm) {
- // Normal item matching pickit entry, no sockets
- if (unit.normal && unit.sockets === 0) {
- 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;
- } else if (recipe.Index === Recipe.Reroll.Magic) {
- if (unit.magic && unit.ilvl >= recipe.Level) {
- if (ntipResult === Pickit.Result.UNWANTED) return true;
- // should allow for charms that aren't immeaditly wanted by equip and not nip wanted
- if (unit.isCharm && !Item.autoEquipCharmCheck(unit) && ntipToTierResult === Pickit.Result.UNWANTED) return true;
- return true;
- }
-
- return false;
- } else if (recipe.Index === Recipe.Reroll.Charm) {
- if (unit.isCharm && unit.magic && (ntipResult === Pickit.Result.UNWANTED || (!Item.autoEquipCharmCheck(unit) && ntipToTierResult === Pickit.Result.UNWANTED))) {
- switch (unit.itemType) {
- case sdk.items.type.SmallCharm:
- if (unit.ilvl >= recipe.Level[unit.code].ilvl) {
- return true;
- }
- break;
- case sdk.items.type.LargeCharm:
- if (unit.ilvl >= recipe.Level.cm2.ilvl) {
- return true;
- }
- break;
- case sdk.items.type.GrandCharm:
- if (unit.ilvl >= recipe.Level.cm2.ilvl) {
- return true;
- }
- break;
- }
- }
-
- return false;
- } else if (recipe.Index === Recipe.Reroll.Rare) {
- if (unit.rare && ntipResult === Pickit.Result.UNWANTED) {
- return true;
- }
-
- return false;
- } else 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;
- } else if (recipe.Index === Recipe.LowToNorm.Armor || recipe.Index === Recipe.LowToNorm.Weapon) {
- if (unit.lowquality && ntipResult === Pickit.Result.UNWANTED) {
- return true;
- }
- }
-
- return false;
+ // Excluded items
+ // Don't use items in locked inventory space
+ if (unit.isInInventory && Storage.Inventory.IsLocked(unit, Config.Inventory)) return false;
+ // Don't use items that are wanted by other systems
+ if (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
+ let valid = true;
+ const ntipResult = NTIP.CheckItem(unit);
+ const ntipNoTierResult = NTIP.CheckItem(unit, NTIP.CheckList);
+
+ if (recipe.Index >= Recipe.HitPower.Helm && recipe.Index <= Recipe.Safety.Weapon) {
+ if (Math.floor(me.charlvl / 2) + Math.floor(unit.ilvl / 2) < recipe.Level) {
+ if (me.charlvl < 50) {
+ // set it equal to ilvl 31 where 60% chance of 2 affixes and 20% chance each of 3 or 4 affixes
+ recipe.Level = 31;
+ } else if (me.charlvl > 50 && me.charlvl < 70) {
+ // set it equal to ilvl 51 where 80% chance of 3 affixes and 20% chance of 4 affixes
+ recipe.Level = 51;
+ } else if (me.charlvl > 70 && me.charlvl < 93) {
+ // set it equal to ilvl 71 where 100% chance of 4 affixes
+ recipe.Level = 71;
+ }
+ }
+ // 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
+ && ntipNoTierResult === Pickit.Result.UNWANTED) {
+ return true;
+ }
+
+ return false;
+ } else if (recipe.Index >= Recipe.Unique.Weapon.ToExceptional && recipe.Index <= Recipe.Unique.Armor.ToElite) {
+ // If item is equipped, ensure we can use the upgraded version
+ if (unit.isEquipped) {
+ if (me.charlvl < unit.upgradedLvlReq
+ || me.trueStr < unit.upgradedStrReq
+ || me.trueDex < unit.upgradedDexReq) {
+ return false;
+ }
+ }
+ // Unique item matching pickit entry
+ if (unit.unique && ntipResult === Pickit.Result.WANTED) {
+ // check items name (prevents upgrading lavagout when we want to be upgrading magefist for the second time)
+ if (recipe.Name !== undefined) {
+ valid = !!unit.fname.toLowerCase().includes(recipe.Name.toLowerCase());
+ if (valid) {
+ // check to see if we are using this already and if so compare base stats to see if this one would be better
+ // ignore things that get re-rolled like defense or min/max dmg just focus on base stats like enhanced defense/damage
+ let equipped = me.getItemsEx(-1, sdk.storage.Equipped)
+ .find(item => item.fname.toLowerCase().includes(recipe.Name.toLowerCase()));
+ if (equipped) {
+ switch (recipe.Name.toLowerCase()) {
+ case "magefist":
+ // compare enhanced defense - keep "equal to" because base defense gets re-rolled so it might turn out better
+ valid = (unit.getStat(sdk.stats.ArmorPercent) >= equipped.getStat(sdk.stats.ArmorPercent));
+ break;
+ }
+ }
+ }
+ }
+ switch (recipe.Ethereal) {
+ case Roll.All:
+ case undefined:
+ return valid && ntipResult === Pickit.Result.WANTED;
+ case Roll.Eth:
+ return valid && unit.ethereal && ntipResult === Pickit.Result.WANTED;
+ case Roll.NonEth:
+ return valid && !unit.ethereal && ntipResult === Pickit.Result.WANTED;
+ }
+ }
+
+ return false;
+ } else if (recipe.Index >= Recipe.Rare.Weapon.ToExceptional && recipe.Index <= Recipe.Rare.Armor.ToElite) {
+ // If item is equipped, ensure we can use the upgraded version
+ if (unit.isEquipped) {
+ if (me.charlvl < unit.upgradedLvlReq
+ || me.trueStr < unit.upgradedStrReq
+ || me.trueDex < unit.upgradedDexReq) {
+ return false;
+ }
+ }
+ // Rare item matching pickit entry
+ if (unit.rare && ntipResult === Pickit.Result.WANTED) {
+ 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;
+ } else if (recipe.Index >= Recipe.Socket.Shield && recipe.Index <= Recipe.Socket.Helm) {
+ // Normal item matching pickit entry, no sockets
+ if (unit.normal && unit.sockets === 0) {
+ if (Pickit.Result.WANTED === ntipResult
+ && [
+ sdk.items.type.Wand, sdk.items.type.VoodooHeads,
+ sdk.items.type.AuricShields, sdk.items.type.PrimalHelm,
+ sdk.items.type.Pelt
+ ].includes(unit.itemType)) {
+ if (!Item.betterThanStashed(unit) || !Item.betterBaseThanWearing(unit)) return false;
+ }
+ 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;
+ } else if (recipe.Index === Recipe.Reroll.Magic) {
+ if (unit.magic && unit.ilvl >= recipe.Level) {
+ if (ntipResult === Pickit.Result.UNWANTED) return true;
+ // should allow for charms that aren't immeaditly wanted by equip and not nip wanted
+ if (unit.isCharm && !CharmEquip.check(unit) && ntipNoTierResult === Pickit.Result.UNWANTED) return true;
+ return true;
+ }
+
+ return false;
+ } else if (recipe.Index === Recipe.Reroll.Charm) {
+ if (unit.isCharm && unit.magic
+ && (ntipResult === Pickit.Result.UNWANTED
+ || (!CharmEquip.check(unit) && ntipNoTierResult === Pickit.Result.UNWANTED))) {
+ switch (unit.itemType) {
+ case sdk.items.type.SmallCharm:
+ if (unit.ilvl >= recipe.Level[unit.code].ilvl) {
+ return true;
+ }
+ break;
+ case sdk.items.type.LargeCharm:
+ if (unit.ilvl >= recipe.Level.cm2.ilvl) {
+ return true;
+ }
+ break;
+ case sdk.items.type.GrandCharm:
+ if (unit.ilvl >= recipe.Level.cm2.ilvl) {
+ return true;
+ }
+ break;
+ }
+ }
+
+ return false;
+ } else if (recipe.Index === Recipe.Reroll.Rare) {
+ if (unit.rare && ntipResult === Pickit.Result.UNWANTED) {
+ return true;
+ }
+
+ return false;
+ } else 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;
+ } else if (recipe.Index === Recipe.LowToNorm.Armor || recipe.Index === Recipe.LowToNorm.Weapon) {
+ if (unit.lowquality && ntipResult === Pickit.Result.UNWANTED) {
+ return true;
+ }
+ }
+
+ return false;
};
Cubing.doCubing = function () {
- if (!Config.Cubing || !me.getItem(sdk.items.quest.Cube)) return false;
+ if (!Config.Cubing || !me.getItem(sdk.items.quest.Cube)) return false;
- let wasEquipped = false;
+ let wasEquipped = false;
- this.update();
- // Randomize the recipe array to prevent recipe blocking (multiple caster items etc.)
- let tempArray = this.recipes.slice().shuffle();
+ 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++) {
- let string = "Transmuting: ";
- let items = this.checkRecipe(tempArray[i]);
+ for (let i = 0; i < tempArray.length; i++) {
+ 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;
+ 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();
+ this.cursorCheck();
- i = -1;
+ i = -1;
- while (items.length) {
- string += (items[0].name.trim() + (items.length > 1 ? " + " : ""));
- items[0].isEquipped && (wasEquipped = true);
- if (!Storage.Cube.MoveTo(items[0])) return false;
- items.shift();
- }
+ while (items.length) {
+ string += (items[0].name.trim() + (items.length > 1 ? " + " : ""));
+ items[0].isEquipped && (wasEquipped = true);
+ if (!Storage.Cube.MoveTo(items[0])) return false;
+ items.shift();
+ }
- if (!this.openCube()) 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);
+ transmute();
+ delay(700 + me.ping);
+ console.log("ÿc4Cubing: " + string);
+ Config.ShowCubingInfo && D2Bot.printToConsole(string, sdk.colors.D2Bot.Green);
- this.update();
+ this.update();
- items = me.findItems(-1, -1, sdk.storage.Cube);
+ items = me.findItems(-1, -1, sdk.storage.Cube);
- if (items) {
- for (let j = 0; j < items.length; j++) {
- let result = Pickit.checkItem(items[j]);
+ if (items) {
+ for (let j = 0; j < items.length; j++) {
+ let result = Pickit.checkItem(items[j]);
- switch (result.result) {
- case Pickit.Result.UNWANTED:
- // keep if item is worth selling
- if (items[j].getItemCost(sdk.items.cost.ToSell) / (items[j].sizex * items[j].sizey) >= (me.normal ? 50 : me.nightmare ? 500 : 1000)) {
- if (Storage.Inventory.CanFit(items[j])) {
- Storage.Inventory.MoveTo(items[j]);
- } else {
- Misc.itemLogger("Dropped", items[j], "doCubing");
- items[j].drop();
- }
- }
+ switch (result.result) {
+ case Pickit.Result.UNWANTED:
+ // keep if item is worth selling
+ if (items[j].getItemCost(sdk.items.cost.ToSell) / (items[j].sizex * items[j].sizey) >= (me.normal ? 50 : me.nightmare ? 500 : 1000)) {
+ if (Storage.Inventory.CanFit(items[j])) {
+ Storage.Inventory.MoveTo(items[j]);
+ } else {
+ Item.logger("Dropped", items[j], "doCubing");
+ items[j].drop();
+ }
+ }
- Developer.debugging.crafting && Misc.logItem("Crafted but didn't want", items[j]);
+ Developer.debugging.crafting && Item.logItem("Crafted but didn't want", items[j]);
- break;
- case Pickit.Result.WANTED:
- case Pickit.Result.SOLOWANTS:
- Misc.itemLogger("Cubing Kept", items[j]);
- Misc.logItem("Cubing Kept", items[j], result.line);
+ break;
+ case Pickit.Result.WANTED:
+ case Pickit.Result.SOLOWANTS:
+ Item.logger("Cubing Kept", items[j]);
+ Item.logItem("Cubing Kept", items[j], result.line);
- break;
- case Pickit.Result.CRAFTING: // Crafting System
- CraftingSystem.update(items[j]);
+ break;
+ case Pickit.Result.CRAFTING: // Crafting System
+ CraftingSystem.update(items[j]);
- break;
- case Pickit.Result.SOLOSYSTEM: // SoloWants System
- SoloWants.update(items[j]);
+ break;
+ case Pickit.Result.SOLOSYSTEM: // SoloWants System
+ SoloWants.update(items[j]);
- break;
- }
- }
- }
+ break;
+ }
+ }
+ }
- if (!this.emptyCube()) {
- break;
- }
- }
- }
+ if (!this.emptyCube()) {
+ break;
+ }
+ }
+ }
- if (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) {
- delay(1000);
+ if (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) {
+ delay(1000);
- while (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) {
- me.cancel();
- delay(300);
- }
- }
+ while (getUIFlag(sdk.uiflags.Cube) || getUIFlag(sdk.uiflags.Stash)) {
+ me.cancel();
+ delay(300);
+ }
+ }
- wasEquipped && Item.autoEquip();
+ wasEquipped && Item.autoEquip();
- return true;
+ return true;
};
diff --git a/libs/SoloPlay/Functions/DynamicTiers.js b/libs/SoloPlay/Functions/DynamicTiers.js
index 6b25c201..1f6d3e98 100644
--- a/libs/SoloPlay/Functions/DynamicTiers.js
+++ b/libs/SoloPlay/Functions/DynamicTiers.js
@@ -11,657 +11,681 @@
* no point checking for stats that cannot ever exist. Also handle some of the misc stats that appear as they can be helpful.
*/
-
-/**
- * @param {ItemUnit} item
- */
-const sumElementalDmg = function (item) {
- if (!item) return 0;
- let fire = item.getStatEx(sdk.stats.FireMinDamage) + item.getStatEx(sdk.stats.FireMaxDamage);
- let light = item.getStatEx(sdk.stats.LightMinDamage) + item.getStatEx(sdk.stats.LightMaxDamage);
- let magic = item.getStatEx(sdk.stats.MagicMinDamage) + item.getStatEx(sdk.stats.MagicMaxDamage);
- let cold = item.getStatEx(sdk.stats.ColdMinDamage) + item.getStatEx(sdk.stats.ColdMaxDamage);
- let poison = (item.getStatEx(sdk.stats.PoisonMinDamage) * 125 / 256); // PSN damage adjusted for damage per frame (125/256)
- return (fire + light + magic + cold + poison);
-};
-
-/**
- * @param {ItemUnit} item
- */
-const mercscore = function (item) {
- const mercWeights = {
- IAS: 3.5,
- MINDMG: 3, // min damage
- MAXDMG: 3, // max damage
- SECMINDMG: 3, // secondary min damage
- SECMAXDMG: 3, // secondary max damage
- ELEDMG: 2, // elemental damage
- AR: 0.1, // attack rating
- CB: 3, // crushing blow
- OW: 3, // open wounds
- LL: 8, //lifeleach
- // CTC on attack
- CTCOAAMP: 5,
- CTCOADECREP: 10,
- // CTC on striking
- CTCOSAMP: 3,
- CTCOSDECREP: 8,
- // regen
- HPREGEN: 2,
- FHR: 3, // faster hit recovery
- DEF: 0.05, // defense
- HP: 2,
- STR: 1.5,
- DEX: 1.5,
- ALL: 60, // + all skills
- FR: 2, // fire resist
- LR: 2, // lightning resist
- CR: 1.5, // cold resist
- PR: 1, // poison resist
- ABS: 2.7, // absorb damage (fire light magic cold)
- DR: 2, // Damage resist
- MR: 3, // Magic damage resist
- };
-
- let mercRating = 1;
- // start
- item.prefixnum === sdk.locale.items.Treachery && (mercRating += item.getStatEx(sdk.stats.SkillWhenStruck, 2) * 1000); // fade
- mercRating += item.getStatEx(sdk.stats.SkillOnAura, sdk.skills.Conviction) * 1000; // conviction aura
- mercRating += item.getStatEx(sdk.stats.SkillOnAura, sdk.skills.Meditation) * 100; // meditation aura
- mercRating += item.getStatEx(sdk.stats.AllSkills) * mercWeights.ALL; // add all skills
- mercRating += item.getStatEx(sdk.stats.IAS) * mercWeights.IAS; // add IAS
- mercRating += item.getStatEx(sdk.stats.ToHit) * mercWeights.AR; // add AR
- mercRating += item.getStatEx(sdk.stats.CrushingBlow) * mercWeights.CB; // add crushing blow
- mercRating += item.getStatEx(sdk.stats.OpenWounds) * mercWeights.OW; // add open wounds
- mercRating += item.getStatEx(sdk.stats.LifeLeech) * mercWeights.LL; // add LL
- mercRating += item.getStatEx(sdk.stats.HpRegen) * mercWeights.HPREGEN; // add hp regeneration
- mercRating += item.getStatEx(sdk.stats.FHR) * mercWeights.FHR; // add faster hit recovery
- mercRating += item.getStatEx(sdk.stats.Defense) * mercWeights.DEF; // add Defense
- mercRating += item.getStatEx(sdk.stats.Strength) * mercWeights.STR; // add STR
- mercRating += item.getStatEx(sdk.stats.Dexterity) * mercWeights.DEX; // add DEX
- mercRating += item.getStatEx(sdk.stats.FireResist) * mercWeights.FR; // add FR
- mercRating += item.getStatEx(sdk.stats.ColdResist) * mercWeights.CR; // add CR
- mercRating += item.getStatEx(sdk.stats.LightResist) * mercWeights.LR; // add LR
- mercRating += item.getStatEx(sdk.stats.PoisonResist) * mercWeights.PR; // add PR
- mercRating += (item.getStatEx(sdk.stats.Vitality) + item.getStatEx(sdk.stats.MaxHp) + (item.getStatEx(sdk.stats.PerLevelHp) / 2048 * me.charlvl)) * mercWeights.HP; // add HP
- mercRating += sumElementalDmg(item) * mercWeights.ELEDMG; // add elemental damage
- mercRating += (item.getStatEx(sdk.stats.AbsorbFirePercent) + item.getStatEx(sdk.stats.AbsorbLightPercent) + item.getStatEx(sdk.stats.AbsorbMagicPercent) + item.getStatEx(sdk.stats.AbsorbColdPercent)) * mercWeights.ABS; // add absorb damage
- mercRating += item.getStatEx(sdk.stats.NormalDamageReduction) * mercWeights.DR; // add integer damage resist
- mercRating += item.getStatEx(sdk.stats.DamageResist) * mercWeights.DR * 2; // add damage resist %
- mercRating += item.getStatEx(sdk.stats.MagicDamageReduction) * mercWeights.MR; // add integer magic damage resist
- mercRating += item.getStatEx(sdk.stats.MagicResist) * mercWeights.MR * 2; // add magic damage resist %
-
- if (!myData) {
- myData = CharData.getStats();
- }
-
- switch (myData.merc.classid) {
- case sdk.mercs.Rogue:
- case sdk.mercs.IronWolf:
- mercRating += item.getStatEx(sdk.stats.MinDamage) * mercWeights.MINDMG; // add MIN damage
- mercRating += item.getStatEx(sdk.stats.MaxDamage) * mercWeights.MAXDMG; // add MAX damage
-
- break;
- case sdk.mercs.A5Barb:
- if ([item.getStatEx(sdk.stats.SecondaryMinDamage), item.getStatEx(sdk.stats.SecondaryMaxDamage)].includes(0)) {
- mercRating += item.getStatEx(sdk.stats.MinDamage) * mercWeights.MINDMG; // add MIN damage
- mercRating += item.getStatEx(sdk.stats.MaxDamage) * mercWeights.MAXDMG; // add MAX damage
-
- break;
- }
- // eslint-disable-next-line no-fallthrough
- case sdk.mercs.Guard:
- default:
- mercRating += item.getStatEx(sdk.stats.SecondaryMinDamage) * mercWeights.SECMINDMG;
- mercRating += item.getStatEx(sdk.stats.SecondaryMaxDamage) * mercWeights.SECMAXDMG;
-
- break;
- }
-
- if (!me.sorceress && !me.necromancer && !me.assassin) {
- mercRating += item.getStatEx(sdk.stats.SkillOnAttack, 4238) * mercWeights.CTCOAAMP; // add CTC amplify damage on attack
- mercRating += item.getStatEx(sdk.stats.SkillOnAttack, 4225) * mercWeights.CTCOAAMP; // add CTC amplify damage on attack (magic items)
- mercRating += item.getStatEx(sdk.stats.SkillOnAttack, 5583) * mercWeights.CTCOADECREP; // add CTC decrepify on attack
- mercRating += item.getStatEx(sdk.stats.SkillOnAttack, 5631) * mercWeights.CTCOADECREP; // add CTC decrepify on attack (magic items)
- mercRating += item.getStatEx(sdk.stats.SkillOnHit, 4238) * mercWeights.CTCOSAMP; // add CTC amplify damage on strikng
- mercRating += item.getStatEx(sdk.stats.SkillOnHit, 4225) * mercWeights.CTCOSAMP; // add CTC amplify damage on strikng (magic items)
- mercRating += item.getStatEx(sdk.stats.SkillOnHit, 5583) * mercWeights.CTCOSDECREP; // add CTC decrepify on strikng
- mercRating += item.getStatEx(sdk.stats.SkillOnHit, 5631) * mercWeights.CTCOSDECREP; // add CTC decrepify on strikng (magic items)
- }
-
- for (let x = 0; x < Config.Runewords.length; x += 1) {
- let [sockets, baseCID] = [Config.Runewords[x][0].length, Config.Runewords[x][1]];
- if (item.classid === baseCID && item.isBaseType && item.sockets === sockets && !item.isRuneword) return -1;
- }
-
- return mercRating;
-};
-
-/**
- * @param {ItemUnit} item
- * @param {number} [skillId]
- * @param {object} [buildInfo]
- */
-const chargeditemscore = function (item, skillId, buildInfo) {
- if (!item) return 0;
-
- let tier = 0;
- !buildInfo && (buildInfo = Check.currentBuild());
-
- const chargedWeights = {
- Teleport: Pather.canTeleport() ? 0 : 5,
- Enchant: buildInfo.caster ? 0 : 10,
- InnerSight: me.amazon || buildInfo.caster ? 0 : 10,
- SlowMissiles: me.amazon ? 0 : 10,
- };
-
- let stats = item.getStat(-2);
- let chargedItems = [];
-
- if (stats.hasOwnProperty(sdk.stats.ChargedSkill)) {
- if (stats[sdk.stats.ChargedSkill] instanceof Array) {
- for (let i = 0; i < stats[sdk.stats.ChargedSkill].length; i++) {
- if (stats[sdk.stats.ChargedSkill][i] !== undefined) {
- chargedItems.push({
- 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 {
- chargedItems.push({
- skill: stats[sdk.stats.ChargedSkill].skill,
- level: stats[sdk.stats.ChargedSkill].level,
- charges: stats[sdk.stats.ChargedSkill].charges,
- maxcharges: stats[sdk.stats.ChargedSkill].maxcharges
- });
- }
- }
-
- chargedItems = chargedItems.filter((v, i, a) => a.findIndex(el => ["skill", "level"].every(k => el[k] === v[k])) === i);
-
- if (skillId > 0) {
- chargedItems = chargedItems.filter(check => check.skill === skillId);
- chargedItems.forEach(el => tier += el.level * 5);
- } else {
- chargedItems.forEach(function (el) {
- try {
- let skillName = getSkillById(el.skill).split(" ").join("");
- if (skillName === "Teleport") {
- chargedWeights[skillName] > 0 && (tier += el.maxcharges * 2);
- } else if (!!chargedWeights[skillName]) {
- tier += el.level * chargedWeights[skillName];
- }
- } catch (e) {
- return;
- }
- });
- }
-
- return tier;
-};
-
-const tierWeights = {
- useHardcoreWeights: !!(me.hardcore),
- resistWeights: {
- FR: (this.useHardcoreWeights ? 5 : 3), // fire resist
- LR: (this.useHardcoreWeights ? 5 : 3), // lightning resist
- CR: (this.useHardcoreWeights ? 3 : 1.5), // cold resist
- PR: (this.useHardcoreWeights ? 5 : 1), // poison resist
- MAXFR: 5,
- MAXLR: 5,
- MAXCR: 3,
- MAXPR: 3,
- ABS: (this.useHardcoreWeights ? 5 : 3), // absorb damage (fire light magic cold)
- DR: (this.useHardcoreWeights ? 4 : 2), // Damage resist
- MR: 3, // Magic damage resist
- },
-
- generalWeights: {
- CBF: 25, // cannot be frozen
- FRW: 1, // faster run/walk
- FHR: 3, // faster hit recovery
- DEF: 0.05, // defense
- ICB: 2, // increased chance to block
- BELTSLOTS: 2, // belt potion storage
- MF: 1, //Magic Find
- // base stats
- HP: 0.5,
- MANA: 0.5,
- STR: 1,
- DEX: 1,
- },
-
- casterWeights: {
- //breakpoint stats
- FCR: (me.assassin ? 2 : 5),
- IAS: (me.assassin ? 4 : 0),
- // regen
- HPREGEN: 2,
- MANAREGEN: 2.2,
- },
-
- meleeWeights: {
- // breakpoint stats - todo actually take breakpoints into account
- FCR: 0.5,
- IAS: (me.barbarian && me.classic ? 2 : 4),
- // Attack
- MINDMG: 3,
- MAXDMG: 3,
- SECMINDMG: 2,
- SECMAXDMG: 2,
- AVGDMG: 3,
- ELEDMG: 0.5,
- AR: 0.2,
- CB: 4,
- OW: 1,
- DS: 1.5,
- DMGTOUNDEAD: 0.5,
- DMGTODEMONS: 0.5,
-
- // leaching
- LL: 4,
- ML: 2,
-
- // regen
- HPREGEN: 2,
- MANAREGEN: 2,
- },
-
- ctcWeights: {
- whenStruck: 2,
- onAttack: 2,
- onStrike: 1,
- skills: {
- // Sorc skills
- Nova: 2,
- FrostNova: 4,
- IceBlast: 4,
- ChargedBolt: 4,
- StaticField: 5,
- GlacialSpike: 6,
- ChainLightning: 6,
- Blizzard: 4,
- FrozenOrb: 8,
- Hydra: 4,
- // Necro skills
- AmplifyDamage: 5,
- Decrepify: 10,
- LifeTap: 10,
- BoneArmor: 10,
- BoneSpear: 8,
- BoneSpirit: 8,
- PoisonNova: 10,
- // Barb skills
- Taunt: 5,
- Howl: 5,
- // Druid skills
- CycloneArmor: 10,
- Twister: 5,
- // Sin skills
- Fade: 10,
- Venom: 8,
- }
- },
-
- skillsWeights: {
- ALL: 200,
- CLASS: 175,
- TAB: 125,
- WANTED: 45,
- USEFUL: 30, // + wanted supportive skills
- },
-
- charmWeights: {
- ALL: 180, // + all skills
- CLASS: 175, // + class tab
- TAB: 300, // + skill tab
- FR: 3, // fire resist
- LR: 5, // lightning resist
- CR: 2, // cold resist
- PR: 0.5, // poison resist
- FRW: 1, // faster run/walk
- FHR: (me.barbarian ? 4 : 2), // faster hit recovery
- DEF: 0.05, // defense
- MF: 2, //Magic Find
- // base stats
- HP: 1.75,
- MANA: 0.8,
- STR: 1.0,
- DEX: 1.0,
- }
-};
-
-/**
- * @param {ItemUnit} item
- * @param {number} [bodyloc]
- */
-const tierscore = function (item, bodyloc) {
- const buildInfo = Check.currentBuild();
-
- const generalScore = () => {
- let generalRating = 0;
- let canTele = Pather.canTeleport();
-
- // get item body location
- let itembodyloc = Item.getBodyLoc(item);
- bodyloc === undefined && (bodyloc = itembodyloc.last()); // extract bodyloc from array
-
- // get item cbf stat from olditem equipped on body location
- let eqItem = me.getEquippedItem(bodyloc);
-
- if (!canTele && item.getStatEx(sdk.stats.CannotbeFrozen)) {
- // check if we have cbf but make sure its not from the item we are trying to un-equip
- let haveCBF = (me.getStat(sdk.stats.CannotbeFrozen) > 0 && (eqItem && !eqItem.getStatEx(sdk.stats.CannotbeFrozen)));
- // Cannot be frozen is very important for Melee chars
- !haveCBF && (generalRating += buildInfo.caster ? tierWeights.generalWeights.CBF : tierWeights.generalWeights.CBF * 4);
- }
-
- // faster run/walk
- !canTele && (generalRating += item.getStatEx(sdk.stats.FRW) * tierWeights.generalWeights.FRW);
-
- // belt slots
- if (item.itemType === sdk.items.type.Belt) {
- const beltSize = ["lbl", "vbl"].includes(item.code) ? 2 : ["mbl", "tbl"].includes(item.code) ? 3 : 4;
- // if our current belt-size is better, don't down-grade even if the other stats on the new item are better, not worth the town visits
- generalRating += (Storage.BeltSize() > beltSize ? -50 : (beltSize * 4 * tierWeights.generalWeights.BELTSLOTS));
- }
-
- // start generalRating
- generalRating += item.getStatEx(sdk.stats.MagicBonus) * tierWeights.generalWeights.MF; // add magic find
- generalRating += item.getStatEx(sdk.stats.FHR) * tierWeights.generalWeights.FHR; // add faster hit recovery
- generalRating += item.getStatEx(sdk.stats.Defense) * tierWeights.generalWeights.DEF; // add Defense
- generalRating += (item.getStatEx(sdk.stats.ToBlock) + item.getStatEx(sdk.stats.FBR)) * tierWeights.generalWeights.ICB; //add increased chance to block
- generalRating += (item.getStatEx(sdk.stats.Vitality) + item.getStatEx(sdk.stats.MaxHp) + (item.getStatEx(sdk.stats.PerLevelHp) / 2048 * me.charlvl)) * tierWeights.generalWeights.HP; // add HP
- generalRating += (item.getStatEx(sdk.stats.Energy) + item.getStatEx(sdk.stats.MaxMana) + (item.getStatEx(sdk.stats.PerLevelMana) / 2048 * me.charlvl)) * tierWeights.generalWeights.MANA;// add mana
- generalRating += item.getStatEx(sdk.stats.Strength) * tierWeights.generalWeights.STR; // add STR
- generalRating += item.getStatEx(sdk.stats.Dexterity) * tierWeights.generalWeights.DEX; // add DEX
-
- return generalRating;
- };
-
- const resistScore = () => {
- let itembodyloc = Item.getBodyLoc(item);
- if (!itembodyloc) return 0;
- bodyloc === undefined && (bodyloc = itembodyloc.last()); // extract bodyloc from array
-
- let resistRating = 0;
- // current total resists
- let [currFR, currCR, currLR, currPR] = [me.fireRes, me.coldRes, me.lightRes, me.poisonRes];
- // get item resists stats from olditem equipped on body location
- let eqItem = me.getEquippedItem(bodyloc);
- let [olditemFR, olditemCR, olditemLR, olditemPR] = [0, 0, 0, 0];
- if (eqItem) {
- // equipped resists
- [olditemFR, olditemCR] = [eqItem.getStatEx(sdk.stats.FireResist), eqItem.getStatEx(sdk.stats.ColdResist)];
- [olditemLR, olditemPR] = [eqItem.getStatEx(sdk.stats.LightResist), eqItem.getStatEx(sdk.stats.PoisonResist)];
- }
- // subtract olditem resists from current total resists
- let [baseFR, baseCR, baseLR, basePR] = [(currFR - olditemFR), (currCR - olditemCR), (currLR - olditemLR), (currPR - olditemPR)];
- // if baseRes < max resists give score value upto max resists reached
- const maxRes = me.hell ? 75 : (75 + me.getResPenalty(me.diff + 1) - me.getResPenalty(me.diff));
- let [FRlimit, CRlimit, LRlimit, PRlimit] = [Math.max(maxRes - baseFR, 0), Math.max(maxRes - baseCR, 0), Math.max(maxRes - baseLR, 0), Math.max(maxRes - basePR, 0)];
- // get new item stats
- let [newitemFR, newitemCR] = [Math.max(item.getStatEx(sdk.stats.FireResist), 0), Math.max(item.getStatEx(sdk.stats.ColdResist), 0)];
- let [newitemLR, newitemPR] = [Math.max(item.getStatEx(sdk.stats.LightResist), 0), Math.max(item.getStatEx(sdk.stats.PoisonResist), 0)];
- // newitemRes upto reslimit
- let [effectiveFR, effectiveCR] = [Math.min(newitemFR, FRlimit), Math.min(newitemCR, CRlimit)];
- let [effectiveLR, effectivePR] = [Math.min(newitemLR, LRlimit), Math.min(newitemPR, PRlimit)];
- // sum resistRatings
- resistRating += effectiveFR * tierWeights.resistWeights.FR; // add fireresist
- resistRating += effectiveCR * tierWeights.resistWeights.CR; // add coldresist
- resistRating += effectiveLR * tierWeights.resistWeights.LR; // add literesist
- resistRating += effectivePR * tierWeights.resistWeights.PR; // add poisonresist
- // sum max resists weights
- resistRating += (item.getStatEx(sdk.stats.MaxFireResist) * tierWeights.resistWeights.MAXFR);
- resistRating += (item.getStatEx(sdk.stats.MaxLightResist) * tierWeights.resistWeights.MAXLR);
- resistRating += (item.getStatEx(sdk.stats.MaxColdResist) * tierWeights.resistWeights.MAXCR);
- resistRating += (item.getStatEx(sdk.stats.MaxPoisonResist) * tierWeights.resistWeights.MAXPR);
- // sum absorb and magic/damage reduction
- resistRating += (item.getStatEx(sdk.stats.AbsorbFirePercent) + item.getStatEx(sdk.stats.AbsorbLightPercent) + item.getStatEx(sdk.stats.AbsorbMagicPercent) + item.getStatEx(sdk.stats.AbsorbColdPercent)) * tierWeights.resistWeights.ABS; // add absorb damage
- resistRating += item.getStatEx(sdk.stats.NormalDamageReduction) * tierWeights.resistWeights.DR / 2; // add integer damage resist
- resistRating += item.getStatEx(sdk.stats.DamageResist) * tierWeights.resistWeights.DR * 2; // add damage resist %
- resistRating += item.getStatEx(sdk.stats.MagicDamageReduction) * tierWeights.resistWeights.MR / 2; // add integer magic damage resist
- resistRating += item.getStatEx(sdk.stats.MagicResist) * tierWeights.resistWeights.MR * 2; // add magic damage resist %
-
- return resistRating;
- };
-
- const buildScore = () => {
- let buildRating = 0;
- let buildWeights = buildInfo.caster ? tierWeights.casterWeights : tierWeights.meleeWeights;
-
- me.amazon && item.getStatEx(sdk.stats.ReplenishQuantity) && (buildRating += 50);
- //!Pather.canTeleport() && item.getStatEx(sdk.stats.ChargedSkill, 3461) && (buildRating += 50);
-
- buildRating += item.getStatEx(sdk.stats.FCR) * buildWeights.FCR; // add FCR
- buildRating += item.getStatEx(sdk.stats.IAS) * buildWeights.IAS; // add IAS
- buildRating += item.getStatEx(sdk.stats.HpRegen) * buildWeights.HPREGEN; // add hp regeneration
- buildRating += item.getStatEx(sdk.stats.ManaRecovery) * buildWeights.MANAREGEN; // add mana recovery
- !item.isRuneword && (buildRating += (item.sockets * 10)); // priortize sockets
-
- // pierce/mastery's not sure how I want to weight this so for now just its base value
- buildInfo.usefulStats.forEach(stat => buildRating += item.getStatEx(stat));
-
- // Melee Specific
- if (!buildInfo.caster
- || Config.AttackSkill.includes(sdk.skills.Attack)
- || Config.LowManaSkill.includes(sdk.skills.Attack)
- || ([sdk.items.type.Bow, sdk.items.type.AmazonBow, sdk.items.type.Crossbow].includes(item.itemType) && CharData.skillData.bowData.bowOnSwitch)) {
- let meleeRating = 0;
- let eleDmgModifer = [sdk.items.type.Ring, sdk.items.type.Amulet].includes(item.itemType) ? 2 : 1;
-
- item.getStatEx(sdk.stats.ReplenishDurability) && (meleeRating += 15);
- item.getStatEx(sdk.stats.IgnoreTargetDefense) && (meleeRating += 50);
-
- // should these be added and calc avg dmg instead?
- // Sometimes we replace good weps with 2-300 ED weps that may be high dmg but aren't as good as the item we replaced
- //buildRating += item.getStatEx(sdk.stats.MinDamage) * buildWeights.MINDMG; // add MIN damage
- //buildRating += item.getStatEx(sdk.stats.MaxDamage) * buildWeights.MAXDMG; // add MAX damage
- //buildRating += item.getStatEx(sdk.stats.SecondaryMinDamage) * buildWeights.SECMINDMG; // add MIN damage
- //buildRating += item.getStatEx(sdk.stats.SecondaryMaxDamage) * buildWeights.SECMAXDMG; // add MAX damage
- meleeRating += ((item.getStatEx(sdk.stats.MaxDamage) + item.getStatEx(sdk.stats.MinDamage)) / 2) * tierWeights.meleeWeights.AVGDMG;
-
- meleeRating += sumElementalDmg(item) * (tierWeights.meleeWeights.ELEDMG / eleDmgModifer); // add elemental damage
- meleeRating += item.getStatEx(sdk.stats.ToHit) * tierWeights.meleeWeights.AR; // add AR
- meleeRating += item.getStatEx(sdk.stats.CrushingBlow) * tierWeights.meleeWeights.CB; // add crushing blow
- meleeRating += item.getStatEx(sdk.stats.OpenWounds) * tierWeights.meleeWeights.OW; // add open wounds
- meleeRating += item.getStatEx(sdk.stats.DeadlyStrike) * tierWeights.meleeWeights.DS; // add deadly strike
- meleeRating += item.getStatEx(sdk.stats.LifeLeech) * tierWeights.meleeWeights.LL; // add LL
- meleeRating += item.getStatEx(sdk.stats.ManaDrainMinDamage) * tierWeights.meleeWeights.ML; // add ML
- meleeRating += item.getStatEx(sdk.stats.SkillOnAura, sdk.skills.Sanctuary) * 25; // sanctuary aura
- meleeRating += item.getStatEx(sdk.stats.DemonDamagePercent) * tierWeights.meleeWeights.DMGTODEMONS; // add damage % to demons
- meleeRating += item.getStatEx(sdk.stats.UndeadDamagePercent) * tierWeights.meleeWeights.DMGTOUNDEAD; // add damage % to undead
-
- buildRating += (buildInfo.caster ? (meleeRating / 2) : meleeRating);
- }
-
- return buildRating;
- };
-
- const skillsScore = () => {
- let skillsRating = 0;
- let weaponModifer = !buildInfo.caster && item.getItemType() === "Weapon" ? 4 : 1;
-
- skillsRating += item.getStatEx(sdk.stats.AllSkills) * (tierWeights.skillsWeights.ALL / weaponModifer); // + all skills
- skillsRating += item.getStatEx(sdk.stats.AddClassSkills, me.classid) * (tierWeights.skillsWeights.CLASS / weaponModifer); // + class skills
- skillsRating += item.getStatEx(sdk.stats.AddSkillTab, buildInfo.tabSkills) * (tierWeights.skillsWeights.TAB / weaponModifer); // + TAB skills
- let selectedWeights = [tierWeights.skillsWeights.WANTED, tierWeights.skillsWeights.USEFUL];
- let selectedSkills = [buildInfo.wantedSkills, buildInfo.usefulSkills];
-
- for (let i = 0; i < selectedWeights.length; i++) {
- for (let j = 0; j < selectedSkills.length; j++) {
- for (let k = 0; k < selectedSkills[j].length; k++) {
- skillsRating += item.getStatEx(107, selectedSkills[j][k]) * selectedWeights[i];
- }
- }
- }
-
- // Spirit Fix for barb
- (item.prefixnum === sdk.locale.items.Spirit && !buildInfo.caster) && (skillsRating -= 400);
-
- return skillsRating;
- };
-
- const ctcScore = () => {
- // chance to cast doesn't exist in classic
- if (me.classic) return 0;
-
- const ctcSkillObj = (ctcType, skill, level) => ({ ctcType: ctcType, skill: skill, level: level });
- const meleeCheck = !buildInfo.caster;
- let ctcRating = 0, ctcItems = [];
- let skill, level;
- let stats = item.getStat(-2);
-
- if (stats.hasOwnProperty(sdk.stats.SkillWhenStruck)) {
- if (stats[sdk.stats.SkillWhenStruck] instanceof Array) {
- for (let i = 0; i < stats[sdk.stats.SkillWhenStruck].length; i++) {
- if (stats[sdk.stats.SkillWhenStruck][i] !== undefined) {
- ({ skill, level } = stats[sdk.stats.SkillWhenStruck][i]);
- ctcItems.push(ctcSkillObj(sdk.stats.SkillWhenStruck, skill, level));
- }
- }
- } else {
- ({ skill, level } = stats[sdk.stats.SkillWhenStruck]);
- ctcItems.push(ctcSkillObj(sdk.stats.SkillWhenStruck, skill, level));
- }
- }
-
- if (meleeCheck) {
- if (stats.hasOwnProperty(sdk.stats.SkillOnAttack)) {
- if (stats[sdk.stats.SkillOnAttack] instanceof Array) {
- for (let i = 0; i < stats[sdk.stats.SkillOnAttack].length; i++) {
- if (stats[sdk.stats.SkillOnAttack][i] !== undefined) {
- ({ skill, level } = stats[sdk.stats.SkillOnAttack][i]);
- ctcItems.push(ctcSkillObj(sdk.stats.SkillOnAttack, skill, level));
- }
- }
- } else {
- ({ skill, level } = stats[sdk.stats.SkillOnAttack]);
- ctcItems.push(ctcSkillObj(sdk.stats.SkillOnAttack, skill, level));
- }
- }
-
- if (stats.hasOwnProperty(sdk.stats.SkillOnStrike)) {
- if (stats[sdk.stats.SkillOnStrike] instanceof Array) {
- for (let i = 0; i < stats[sdk.stats.SkillOnStrike].length; i++) {
- if (stats[sdk.stats.SkillOnStrike][i] !== undefined) {
- ({ skill, level } = stats[sdk.stats.SkillOnStrike][i]);
- ctcItems.push(ctcSkillObj(sdk.stats.SkillOnStrike, skill, level));
- }
- }
- } else {
- ({ skill, level } = stats[sdk.stats.SkillOnStrike]);
- ctcItems.push(ctcSkillObj(sdk.stats.SkillOnStrike, skill, level));
- }
- }
- } else {
- tierWeights.ctcWeights.skills.Venom = 0;
- if (me.charlvl > 50) {
- tierWeights.ctcWeights.skills.ChargedBolt = 2;
- }
- }
-
- ctcItems = ctcItems.filter((v, i, a) => a.findIndex(el => ["ctcType", "skill"].every(k => el[k] === v[k])) === i);
-
- // might come back to redo the tierWieghts object but quick map for ctc
- const ctcType = {};
- ctcType[sdk.stats.SkillOnAttack] = tierWeights.ctcWeights.onAttack;
- ctcType[sdk.stats.SkillOnStrike] = tierWeights.ctcWeights.onStrike;
- ctcType[sdk.stats.SkillWhenStruck] = tierWeights.ctcWeights.whenStruck;
-
- for (let i = 0; i < ctcItems.length; i++) {
- try {
- let skillName = getSkillById(ctcItems[i].skill).split(" ").join("");
- if (!!tierWeights.ctcWeights.skills[skillName] && ctcType[ctcItems[i].ctcType]) {
- ctcRating += (ctcItems[i].level * tierWeights.ctcWeights.skills[skillName] * ctcType[ctcItems[i].ctcType]);
- }
- } catch (e) {
- console.error(e);
- }
- }
-
- return ctcRating;
- };
-
- let tier = 1; // set to 1 for native autoequip to use items.
- tier += generalScore();
- tier += resistScore();
- tier += buildScore();
- tier += skillsScore();
- tier += ctcScore();
- tier += chargeditemscore(item, -1, buildInfo);
-
- for (let x = 0; x < Config.Runewords.length; x += 1) {
- let [sockets, baseCID] = [Config.Runewords[x][0].length, Config.Runewords[x][1]];
- if (item.classid === baseCID && item.isBaseType && item.sockets === sockets && !item.isRuneword && !item.getItemsEx().length) return -1;
- }
-
- return item.questItem ? -1 : Math.max(1, tier);
-};
-
-/**
- * @param {ItemUnit} item
- */
-const secondaryscore = function (item) {
- let tier = 0;
-
- tier += item.getStatEx(sdk.stats.AllSkills) * 200; // + all skills
- tier += item.getStatEx(sdk.stats.AddClassSkills, me.classid) * 100; // + class skills
- tier += item.getStatEx(sdk.stats.AddSkillTab, Check.finalBuild().tabSkills) * 75; // + TAB skills
- let precastSkills = [Check.finalBuild().precastSkills];
-
- for (let i = 0; i < precastSkills.length; i++) {
- tier += item.getStatEx(107, precastSkills[i]) * 50;
- }
-
- tier += item.getStatEx(sdk.stats.FCR) * 5; // add FCR
- tier += item.getStatEx(sdk.stats.FHR) * 3; // add faster hit recovery
-
- return tier;
-};
-
-/**
- * @param {ItemUnit} item
- */
-const charmscore = function (item) {
- if (myData.me.charmGids.includes(item.gid)) return 1000;
- let charmRating = 1;
-
- if (!item.unique && item.classid === sdk.items.GrandCharm && !me.getSkillTabs().some(s => item.getStatEx(sdk.stats.AddSkillTab, s))) return -1;
- const buildInfo = Check.currentBuild();
-
- charmRating += item.getStatEx(sdk.stats.AddSkillTab, buildInfo.tabSkills) * tierWeights.charmWeights.TAB; // + TAB skills
- charmRating += item.getStatEx(sdk.stats.FireResist) * tierWeights.charmWeights.FR; // add FR
- charmRating += item.getStatEx(sdk.stats.ColdResist) * tierWeights.charmWeights.CR; // add CR
- charmRating += item.getStatEx(sdk.stats.LightResist) * tierWeights.charmWeights.LR; // add LR
- charmRating += item.getStatEx(sdk.stats.PoisonResist) * tierWeights.charmWeights.PR; // add PR
- charmRating += item.getStatEx(sdk.stats.FRW) * tierWeights.charmWeights.FRW; // add faster run walk
- charmRating += item.getStatEx(sdk.stats.FHR) * tierWeights.charmWeights.FHR; // add faster hit recovery
- charmRating += item.getStatEx(sdk.stats.Defense) * tierWeights.charmWeights.DEF; // add Defense
- charmRating += item.getStatEx(sdk.stats.MagicBonus) * tierWeights.charmWeights.MF; // add magic find
- charmRating += (item.getStatEx(sdk.stats.Vitality) + item.getStatEx(sdk.stats.MaxHp) + (item.getStatEx(sdk.stats.PerLevelHp) / 2048 * me.charlvl)) * tierWeights.charmWeights.HP; // add HP
- charmRating += (item.getStatEx(sdk.stats.Energy) + item.getStatEx(sdk.stats.MaxMana) + (item.getStatEx(sdk.stats.PerLevelMana) / 2048 * me.charlvl)) * tierWeights.charmWeights.MANA;// add mana
- charmRating += item.getStatEx(sdk.stats.Strength) * tierWeights.charmWeights.STR; // add STR
- charmRating += item.getStatEx(sdk.stats.Dexterity) * tierWeights.charmWeights.DEX; // add DEX
-
- if (!buildInfo.caster) {
- charmRating += item.getStatEx(sdk.stats.MinDamage) * 3; // add MIN damage
- charmRating += item.getStatEx(sdk.stats.MaxDamage) * 3; // add MAX damage
- charmRating += sumElementalDmg(item); // add elemental damage
- charmRating += item.getStatEx(sdk.stats.ToHit) * 0.5; // add AR
- }
-
- // Gheeds, Torch, annhi
- if (item.unique) {
- charmRating += item.getStatEx(sdk.stats.AllSkills) * tierWeights.charmWeights.ALL; // + all skills
- charmRating += item.getStatEx(sdk.stats.AddClassSkills, me.classid) * tierWeights.charmWeights.CLASS; // + class skills
- charmRating += item.getStatEx(sdk.stats.GoldBonus); // add gold find
- charmRating += item.getStatEx(sdk.stats.ReducedPrices) * 1.5; // add reduced vendor prices
- charmRating += item.getStatEx(sdk.stats.Strength); // add STR
- }
-
- return charmRating;
-};
+(function (global) {
+ /**
+ * @param {ItemUnit} item
+ */
+ const sumElementalDmg = function (item) {
+ if (!item) return 0;
+ let fire = item.getStatEx(sdk.stats.FireMinDamage) + item.getStatEx(sdk.stats.FireMaxDamage);
+ let light = item.getStatEx(sdk.stats.LightMinDamage) + item.getStatEx(sdk.stats.LightMaxDamage);
+ let magic = item.getStatEx(sdk.stats.MagicMinDamage) + item.getStatEx(sdk.stats.MagicMaxDamage);
+ let cold = item.getStatEx(sdk.stats.ColdMinDamage) + item.getStatEx(sdk.stats.ColdMaxDamage);
+ let poison = (item.getStatEx(sdk.stats.PoisonMinDamage) * 125 / 256); // PSN damage adjusted for damage per frame (125/256)
+ return (fire + light + magic + cold + poison);
+ };
+
+ /**
+ * @param {ItemUnit} item
+ */
+ const mercscore = function (item) {
+ const mercWeights = {
+ IAS: 3.5,
+ MINDMG: 3, // min damage
+ MAXDMG: 3, // max damage
+ SECMINDMG: 3, // secondary min damage
+ SECMAXDMG: 3, // secondary max damage
+ ELEDMG: 2, // elemental damage
+ AR: 0.1, // attack rating
+ CB: 3, // crushing blow
+ OW: 3, // open wounds
+ LL: 8, //lifeleach
+ // CTC on attack
+ CTCOAAMP: 5,
+ CTCOADECREP: 10,
+ // CTC on striking
+ CTCOSAMP: 3,
+ CTCOSDECREP: 8,
+ // regen
+ HPREGEN: 2,
+ FHR: 3, // faster hit recovery
+ DEF: 0.05, // defense
+ HP: 2,
+ STR: 1.5,
+ DEX: 1.5,
+ ALL: 60, // + all skills
+ FR: 2, // fire resist
+ LR: 2, // lightning resist
+ CR: 1.5, // cold resist
+ PR: 1, // poison resist
+ ABS: 2.7, // absorb damage (fire light magic cold)
+ DR: 2, // Damage resist
+ MR: 3, // Magic damage resist
+ };
+
+ let mercRating = 1;
+ // start
+ item.prefixnum === sdk.locale.items.Treachery && (mercRating += item.getStatEx(sdk.stats.SkillWhenStruck, 2) * 1000); // fade
+ mercRating += item.getStatEx(sdk.stats.SkillOnAura, sdk.skills.Conviction) * 1000; // conviction aura
+ mercRating += item.getStatEx(sdk.stats.SkillOnAura, sdk.skills.Meditation) * 100; // meditation aura
+ mercRating += item.getStatEx(sdk.stats.AllSkills) * mercWeights.ALL; // add all skills
+ mercRating += item.getStatEx(sdk.stats.IAS) * mercWeights.IAS; // add IAS
+ mercRating += item.getStatEx(sdk.stats.ToHit) * mercWeights.AR; // add AR
+ mercRating += item.getStatEx(sdk.stats.CrushingBlow) * mercWeights.CB; // add crushing blow
+ mercRating += item.getStatEx(sdk.stats.OpenWounds) * mercWeights.OW; // add open wounds
+ mercRating += item.getStatEx(sdk.stats.LifeLeech) * mercWeights.LL; // add LL
+ mercRating += item.getStatEx(sdk.stats.HpRegen) * mercWeights.HPREGEN; // add hp regeneration
+ mercRating += item.getStatEx(sdk.stats.FHR) * mercWeights.FHR; // add faster hit recovery
+ mercRating += item.getStatEx(sdk.stats.Defense) * mercWeights.DEF; // add Defense
+ mercRating += item.getStatEx(sdk.stats.Strength) * mercWeights.STR; // add STR
+ mercRating += item.getStatEx(sdk.stats.Dexterity) * mercWeights.DEX; // add DEX
+ mercRating += item.getStatEx(sdk.stats.FireResist) * mercWeights.FR; // add FR
+ mercRating += item.getStatEx(sdk.stats.ColdResist) * mercWeights.CR; // add CR
+ mercRating += item.getStatEx(sdk.stats.LightResist) * mercWeights.LR; // add LR
+ mercRating += item.getStatEx(sdk.stats.PoisonResist) * mercWeights.PR; // add PR
+ mercRating += (item.getStatEx(sdk.stats.Vitality) + item.getStatEx(sdk.stats.MaxHp) + (item.getStatEx(sdk.stats.PerLevelHp) / 2048 * me.charlvl)) * mercWeights.HP; // add HP
+ mercRating += sumElementalDmg(item) * mercWeights.ELEDMG; // add elemental damage
+ mercRating += (item.getStatEx(sdk.stats.AbsorbFirePercent) + item.getStatEx(sdk.stats.AbsorbLightPercent) + item.getStatEx(sdk.stats.AbsorbMagicPercent) + item.getStatEx(sdk.stats.AbsorbColdPercent)) * mercWeights.ABS; // add absorb damage
+ mercRating += item.getStatEx(sdk.stats.NormalDamageReduction) * mercWeights.DR; // add integer damage resist
+ mercRating += item.getStatEx(sdk.stats.DamageResist) * mercWeights.DR * 2; // add damage resist %
+ mercRating += item.getStatEx(sdk.stats.MagicDamageReduction) * mercWeights.MR; // add integer magic damage resist
+ mercRating += item.getStatEx(sdk.stats.MagicResist) * mercWeights.MR * 2; // add magic damage resist %
+
+ switch (me.data.merc.classid) {
+ case sdk.mercs.Rogue:
+ case sdk.mercs.IronWolf:
+ mercRating += item.getStatEx(sdk.stats.MinDamage) * mercWeights.MINDMG; // add MIN damage
+ mercRating += item.getStatEx(sdk.stats.MaxDamage) * mercWeights.MAXDMG; // add MAX damage
+
+ break;
+ case sdk.mercs.A5Barb:
+ if ([item.getStatEx(sdk.stats.SecondaryMinDamage), item.getStatEx(sdk.stats.SecondaryMaxDamage)].includes(0)) {
+ mercRating += item.getStatEx(sdk.stats.MinDamage) * mercWeights.MINDMG; // add MIN damage
+ mercRating += item.getStatEx(sdk.stats.MaxDamage) * mercWeights.MAXDMG; // add MAX damage
+
+ break;
+ }
+ // eslint-disable-next-line no-fallthrough
+ case sdk.mercs.Guard:
+ default:
+ mercRating += item.getStatEx(sdk.stats.SecondaryMinDamage) * mercWeights.SECMINDMG;
+ mercRating += item.getStatEx(sdk.stats.SecondaryMaxDamage) * mercWeights.SECMAXDMG;
+
+ break;
+ }
+
+ if (!me.sorceress && !me.necromancer && !me.assassin) {
+ mercRating += item.getStatEx(sdk.stats.SkillOnAttack, 4238) * mercWeights.CTCOAAMP; // add CTC amplify damage on attack
+ mercRating += item.getStatEx(sdk.stats.SkillOnAttack, 4225) * mercWeights.CTCOAAMP; // add CTC amplify damage on attack (magic items)
+ mercRating += item.getStatEx(sdk.stats.SkillOnAttack, 5583) * mercWeights.CTCOADECREP; // add CTC decrepify on attack
+ mercRating += item.getStatEx(sdk.stats.SkillOnAttack, 5631) * mercWeights.CTCOADECREP; // add CTC decrepify on attack (magic items)
+ mercRating += item.getStatEx(sdk.stats.SkillOnHit, 4238) * mercWeights.CTCOSAMP; // add CTC amplify damage on strikng
+ mercRating += item.getStatEx(sdk.stats.SkillOnHit, 4225) * mercWeights.CTCOSAMP; // add CTC amplify damage on strikng (magic items)
+ mercRating += item.getStatEx(sdk.stats.SkillOnHit, 5583) * mercWeights.CTCOSDECREP; // add CTC decrepify on strikng
+ mercRating += item.getStatEx(sdk.stats.SkillOnHit, 5631) * mercWeights.CTCOSDECREP; // add CTC decrepify on strikng (magic items)
+ }
+
+ if (item.isBaseType && !item.isRuneword) {
+ for (let runeword of Config.Runewords) {
+ let [sockets, baseCID] = [runeword[0].sockets, runeword[1]];
+ if (item.classid === baseCID && item.sockets === sockets && !item.getItemsEx().length) return -1;
+ }
+ }
+
+ return mercRating;
+ };
+
+ /**
+ * @param {ItemUnit} item
+ * @param {number} [skillId]
+ * @param {object} [buildInfo]
+ */
+ const chargeditemscore = function (item, skillId, buildInfo) {
+ if (!item) return 0;
+
+ let tier = 0;
+ !buildInfo && (buildInfo = Check.currentBuild());
+
+ /**
+ * @constructor
+ * @param {{ skill: number, level: number, charges: number, maxcharges: number}} obj
+ */
+ function ChargedItem (obj = {}) {
+ this.skill = obj.skill;
+ this.level = obj.level;
+ this.charges = obj.charges;
+ this.maxcharges = obj.maxcharges;
+ }
+ const _chargedWeights = new Map([
+ [sdk.skills.Teleport, (Pather.canTeleport() ? 0 : 5)],
+ [sdk.skills.Enchant, (buildInfo.caster ? 0 : 10)],
+ [sdk.skills.InnerSight, (me.amazon || buildInfo.caster ? 0 : 10)],
+ [sdk.skills.SlowMissiles, (me.amazon ? 0 : 10)],
+ ]);
+
+ let stats = item.getStat(-2);
+ let chargedItems = [];
+
+ if (stats.hasOwnProperty(sdk.stats.ChargedSkill)) {
+ if (stats[sdk.stats.ChargedSkill] instanceof Array) {
+ for (let i = 0; i < stats[sdk.stats.ChargedSkill].length; i++) {
+ if (stats[sdk.stats.ChargedSkill][i] !== undefined) {
+ chargedItems.push(new ChargedItem(stats[sdk.stats.ChargedSkill][i]));
+ }
+ }
+ } else {
+ chargedItems.push(new ChargedItem(stats[sdk.stats.ChargedSkill]));
+ }
+ }
+
+ chargedItems = chargedItems
+ .filter(function (v, i, a) {
+ return a.findIndex(function (el) {
+ return ["skill", "level"].every(function (k) {
+ return el[k] === v[k];
+ });
+ }) === i;
+ });
+
+ if (skillId > 0) {
+ chargedItems
+ .filter(function (check) {
+ return check.skill === skillId;
+ })
+ .forEach(function (el) {
+ tier += el.level * 5;
+ });
+ } else {
+ chargedItems.forEach(function (el) {
+ if (el.skill === sdk.skills.Teleport) {
+ tier += el.maxcharges * 2;
+ } else if (_chargedWeights.has(el.skill)) {
+ tier += el.level * _chargedWeights.get(el.skill);
+ }
+ });
+ }
+
+ return tier;
+ };
+
+ const _tierWeights = (function () {
+ const hc = me.hardcore;
+ const buildInfo = Check.currentBuild();
+ /** @type {Map} */
+ const res = new Map([
+ [sdk.stats.FireResist, hc ? 5 : 3],
+ [sdk.stats.LightningResist, hc ? 5 : 3],
+ [sdk.stats.ColdResist, hc ? 3 : 1.5],
+ [sdk.stats.PoisonResist, hc ? 5 : 1],
+ [sdk.stats.MaxFireResist, 5],
+ [sdk.stats.MaxLightResist, 5],
+ [sdk.stats.MaxColdResist, 3],
+ [sdk.stats.MaxPoisonResist, 3],
+ [sdk.stats.AbsorbFire, hc ? 2 : 1],
+ [sdk.stats.AbsorbFirePercent, hc ? 3 : 1.5],
+ [sdk.stats.AbsorbLight, hc ? 2 : 1],
+ [sdk.stats.AbsorbLightPercent, hc ? 3 : 1.5],
+ [sdk.stats.AbsorbCold, hc ? 1 : 0.5],
+ [sdk.stats.AbsorbColdPercent, hc ? 1.5 : 0.75],
+ [sdk.stats.AbsorbMagic, hc ? 2 : 1],
+ [sdk.stats.AbsorbMagicPercent, hc ? 3 : 1.5],
+ [sdk.stats.NormalDamageReduction, hc ? 1 : 0.5],
+ [sdk.stats.MagicDamageReduction, hc ? 1 : 0.5],
+ [sdk.stats.DamageResist, hc ? 5 : 2],
+ [sdk.stats.MagicResist, hc ? 6 : 3],
+ ]);
+ /** @type {Map} */
+ const gen = new Map([
+ [sdk.stats.CannotbeFrozen, buildInfo.caster ? 25 : 100],
+ [sdk.stats.FRW, 1],
+ [sdk.stats.FHR, 3],
+ [sdk.stats.FBR, 1],
+ [sdk.stats.ToBlock, 1],
+ [sdk.stats.IAS, buildInfo.caster && !me.assassin ? 0 : 4],
+ [sdk.stats.FCR, buildInfo.caster ? me.assassin ? 2 : 5 : 0.5],
+ [sdk.stats.Defense, 0.05],
+ [sdk.stats.MagicBonus, 1],
+ [sdk.stats.GoldBonus, 0.5],
+ [sdk.stats.Vitality, 1],
+ [sdk.stats.MaxHp, 1],
+ [sdk.stats.PerLevelHp, 1],
+ [sdk.stats.HpRegen, 2],
+ [sdk.stats.Energy, 1],
+ [sdk.stats.MaxMana, 1],
+ [sdk.stats.PerLevelMana, 1],
+ [sdk.stats.ManaRecovery, buildInfo.caster ? 2.5 : 1],
+ [sdk.stats.Strength, 1],
+ [sdk.stats.Dexterity, me.amazon ? 3 : 1],
+ [sdk.stats.ReplenishQuantity, me.amazon ? 50 : 0],
+ [sdk.stats.ToHit, 0.2],
+ [sdk.stats.CrushingBlow, 4],
+ [sdk.stats.OpenWounds, 1],
+ [sdk.stats.DeadlyStrike, 1.5],
+ [sdk.stats.LifeLeech, 4],
+ [sdk.stats.HealAfterKill, 1],
+ [sdk.stats.ManaLeech, 2],
+ [sdk.stats.ManaAfterKill, 1],
+ [sdk.stats.DemonDamagePercent, 0.5],
+ [sdk.stats.UndeadDamagePercent, 0.5],
+ [sdk.stats.ReplenishDurability, 15],
+ [sdk.stats.IgnoreTargetDefense, 50],
+ [sdk.stats.MinDamage, 3],
+ [sdk.stats.MaxDamage, 3],
+ [sdk.stats.SecondaryMinDamage, 2],
+ [sdk.stats.SecondaryMaxDamage, 2],
+ ]);
+ /** @type {Map} */
+ const skill = new Map([
+ [sdk.stats.AllSkills, 200],
+ [sdk.stats.AddClassSkills, 175],
+ [sdk.stats.AddSkillTab, 125],
+ ]);
+ /** @type {Map} */
+ const charms = new Map([
+ [sdk.stats.AllSkills, 180],
+ [sdk.stats.AddClassSkills, 175],
+ [sdk.stats.AddSkillTab, 300],
+ [sdk.stats.FireResist, 3],
+ [sdk.stats.LightningResist, 5],
+ [sdk.stats.ColdResist, 2],
+ [sdk.stats.PoisonResist, 1],
+ [sdk.stats.FRW, 1],
+ [sdk.stats.FHR, (me.barbarian ? 4 : 2)],
+ [sdk.stats.Defense, 0.05],
+ [sdk.stats.MagicBonus, 2],
+ [sdk.stats.GoldBonus, 0.5],
+ [sdk.stats.MaxHp, 1.75],
+ [sdk.stats.PerLevelHp, 1],
+ [sdk.stats.HpRegen, 2],
+ [sdk.stats.MaxMana, 1],
+ [sdk.stats.PerLevelMana, 1],
+ [sdk.stats.Strength, 1],
+ [sdk.stats.Dexterity, me.amazon ? 3 : 1],
+ [sdk.stats.Vitality, 1],
+ [sdk.stats.Energy, 0.8],
+ ]);
+
+ /** @type {Map} */
+ const ctc = new Map([
+ [sdk.stats.SkillWhenStruck, 2],
+ [sdk.stats.SkillOnAttack, 2],
+ [sdk.stats.SkillOnStrike, 1],
+ [sdk.skills.Nova, 2],
+ [sdk.skills.FrostNova, 4],
+ [sdk.skills.IceBlast, 4],
+ [sdk.skills.ChargedBolt, 4],
+ [sdk.skills.StaticField, 5],
+ [sdk.skills.GlacialSpike, 6],
+ [sdk.skills.ChainLightning, 6],
+ [sdk.skills.Blizzard, 4],
+ [sdk.skills.FrozenOrb, 8],
+ [sdk.skills.Hydra, 4],
+ [sdk.skills.AmplifyDamage, 5],
+ [sdk.skills.Decrepify, 10],
+ [sdk.skills.LifeTap, 10],
+ [sdk.skills.BoneArmor, 10],
+ [sdk.skills.BoneSpear, 8],
+ [sdk.skills.BoneSpirit, 8],
+ [sdk.skills.PoisonNova, 10],
+ [sdk.skills.Taunt, 5],
+ [sdk.skills.Howl, 5],
+ [sdk.skills.CycloneArmor, 10],
+ [sdk.skills.Twister, 5],
+ [sdk.skills.Fade, 10],
+ [sdk.skills.Venom, 8],
+ ]);
+
+ return {
+ res: res,
+ gen: gen,
+ skill: skill,
+ charms: charms,
+ ctc: ctc,
+ };
+ })();
+
+ /**
+ * @param {ItemUnit} item
+ * @param {number} [bodyloc]
+ * @todo Breakpoint scoring similar to how res is scored
+ */
+ const tierscore = function (item, tier, bodyloc) {
+ if (item.questItem) return -1;
+ const itembodyloc = Item.getBodyLoc(item);
+ if (!itembodyloc.length) return -1;
+ bodyloc = bodyloc || itembodyloc.last();
+
+ if (item.isBaseType && !item.isRuneword && me.charlvl > 10) {
+ for (let runeword of Config.Runewords) {
+ let [sockets, baseCID] = [runeword[0].sockets, runeword[1]];
+ if (item.classid === baseCID && item.sockets === sockets && !item.getItemsEx().length) return -1;
+ }
+ }
+
+ const buildInfo = Check.currentBuild();
+ const canTele = Pather.canTeleport();
+ // const eqItem = me.equipped.get(bodyloc);
+ const eqItem = me.getEquippedItem(bodyloc);
+
+ const generalScore = function () {
+ let generalRating = 0;
+
+ // get item cbf stat from olditem equipped on body location
+ if (!canTele && item.getStatEx(sdk.stats.CannotbeFrozen)) {
+ // check if we have cbf but make sure its not from the item we are trying to un-equip
+ if (!me.getStat(sdk.stats.CannotbeFrozen)) {
+ // Cannot be frozen is very important for Melee chars
+ generalRating += _tierWeights.gen.get(sdk.stats.CannotbeFrozen);
+ }
+ }
+
+ // belt slots
+ if (item.itemType === sdk.items.type.Belt) {
+ const beltSizes = { lbl: 2, vbl: 2, mbl: 3, tbl: 3 };
+ const beltSize = beltSizes[item.code] || 4;
+ // if our current belt-size is better, don't down-grade even if the other stats on the new item are better, not worth the town visits
+ generalRating += (Storage.BeltSize() > beltSize ? -50 : (beltSize * 4 * 2));
+ }
+
+ // pierce/mastery's not sure how I want to weight this so for now just its base value
+ buildInfo.usefulStats.forEach(function (stat) {
+ generalRating += item.getStatEx(stat);
+ });
+
+ // start generalRating
+ !item.isRuneword && (generalRating += (item.sockets * 10)); // priortize sockets
+ generalRating += ((item.getStatEx(sdk.stats.PerLevelHp) / 2048 * me.charlvl)) * _tierWeights.gen.get(sdk.stats.PerLevelHp);
+ generalRating += ((item.getStatEx(sdk.stats.PerLevelMana) / 2048 * me.charlvl)) * _tierWeights.gen.get(sdk.stats.PerLevelMana);
+
+ return [
+ sdk.stats.FHR, sdk.stats.FRW, sdk.stats.FBR, sdk.stats.FCR, sdk.stats.ToBlock,
+ sdk.stats.MagicBonus, sdk.stats.GoldBonus, sdk.stats.Defense, sdk.stats.ManaRecovery,
+ sdk.stats.Strength, sdk.stats.Dexterity, sdk.stats.Vitality, sdk.stats.Energy,
+ sdk.stats.MaxHp, sdk.stats.MaxMana, sdk.stats.ReplenishQuantity, sdk.stats.HpRegen,
+ ].reduce(function (acc, stat) {
+ return acc + item.getStatEx(stat) * _tierWeights.gen.get(stat);
+ }, generalRating);
+ };
+
+ const resistScore = function () {
+ let resistRating = 0;
+
+ // get new item stats
+ let [newitemFR, newitemCR, newitemLR, newitemPR] = [
+ item.getStatEx(sdk.stats.FireResist),
+ item.getStatEx(sdk.stats.ColdResist),
+ item.getStatEx(sdk.stats.LightResist),
+ item.getStatEx(sdk.stats.PoisonResist)
+ ];
+ // only enter next block if we have a new item with resists
+ if (newitemFR || newitemCR || newitemLR || newitemPR) {
+ const maxRes = me.hell ? 80 : (80 + me.getResPenalty(me.diff + 1) - me.getResPenalty(me.diff));
+ // get item resists stats from olditem equipped on body location
+ let [olditemFR, olditemCR, olditemLR, olditemPR] = [0, 0, 0, 0];
+ if (eqItem) {
+ // equipped resists
+ [olditemFR, olditemCR, olditemLR, olditemPR] = [
+ eqItem.getStatEx(sdk.stats.FireResist), eqItem.getStatEx(sdk.stats.ColdResist),
+ eqItem.getStatEx(sdk.stats.LightResist), eqItem.getStatEx(sdk.stats.PoisonResist)
+ ];
+ }
+ // subtract olditem resists from current total resists
+ const [baseFR, baseCR, baseLR, basePR] = [
+ me.fireRes - olditemFR,
+ me.coldRes - olditemCR,
+ me.lightRes - olditemLR,
+ me.poisonRes - olditemPR,
+ ];
+ // if baseRes < max resists give score value upto max resists reached
+ const [FRlimit, CRlimit, LRlimit, PRlimit] = [
+ Math.max(maxRes - baseFR, 0),
+ Math.max(maxRes - baseCR, 0),
+ Math.max(maxRes - baseLR, 0),
+ Math.max(maxRes - basePR, 0)
+ ];
+ // newitemRes upto reslimit
+ let [effectiveFR, effectiveCR, effectiveLR, effectivePR] = [
+ Math.min(newitemFR, FRlimit),
+ Math.min(newitemCR, CRlimit),
+ Math.min(newitemLR, LRlimit),
+ Math.min(newitemPR, PRlimit)
+ ];
+ // sum resistRatings
+ resistRating += effectiveFR * _tierWeights.res.get(sdk.stats.FireResist);
+ resistRating += effectiveCR * _tierWeights.res.get(sdk.stats.ColdResist);
+ resistRating += effectiveLR * _tierWeights.res.get(sdk.stats.LightResist);
+ resistRating += effectivePR * _tierWeights.res.get(sdk.stats.PoisonResist);
+ }
+
+ return ([
+ sdk.stats.MaxFireResist, sdk.stats.MaxLightResist,
+ sdk.stats.MaxColdResist, sdk.stats.MaxPoisonResist,
+ sdk.stats.AbsorbFire, sdk.stats.AbsorbLight,
+ sdk.stats.AbsorbMagic, sdk.stats.AbsorbCold,
+ sdk.stats.AbsorbFirePercent, sdk.stats.AbsorbLightPercent,
+ sdk.stats.AbsorbMagicPercent, sdk.stats.AbsorbColdPercent,
+ sdk.stats.NormalDamageReduction, sdk.stats.DamageResist,
+ sdk.stats.MagicDamageReduction, sdk.stats.MagicResist
+ ].reduce(function (acc, stat) {
+ return acc + item.getStatEx(stat) * _tierWeights.res.get(stat);
+ }, resistRating));
+ };
+
+ const buildScore = function () {
+ // dirty fix maybe?
+ if (me.barbarian && SetUp.currentBuild !== "Immortalwhirl" && item.strictlyTwoHanded) {
+ return 0;
+ }
+ // Melee Specific
+ if (!buildInfo.caster
+ || Config.AttackSkill.includes(sdk.skills.Attack)
+ || Config.LowManaSkill.includes(sdk.skills.Attack)
+ || ([sdk.items.type.Bow, sdk.items.type.AmazonBow, sdk.items.type.Crossbow].includes(item.itemType)
+ && CharData.skillData.bow.onSwitch)) {
+ let meleeRating = 0;
+ const eleDmgWeight = 0.5;
+ const eleDmgModifer = [sdk.items.type.Ring, sdk.items.type.Amulet].includes(item.itemType) ? 2 : 1;
+
+ meleeRating += ((item.getStatEx(sdk.stats.MaxDamage) + item.getStatEx(sdk.stats.MinDamage)) / 2) * 3;
+ meleeRating += sumElementalDmg(item) * (eleDmgWeight / eleDmgModifer); // add elemental damage
+ meleeRating += item.getStatEx(sdk.stats.SkillOnAura, sdk.skills.Sanctuary) * 25; // sanctuary aura
+
+ [
+ sdk.stats.ReplenishDurability, sdk.stats.IgnoreTargetDefense, sdk.stats.ToHit, sdk.stats.CrushingBlow,
+ sdk.stats.OpenWounds, sdk.stats.DeadlyStrike, sdk.stats.LifeLeech, sdk.stats.ManaLeech,
+ sdk.stats.DemonDamagePercent, sdk.stats.UndeadDamagePercent,
+ ].reduce(function (acc, stat) {
+ return acc + item.getStatEx(stat) * _tierWeights.gen.get(stat);
+ }, meleeRating);
+ buildInfo.caster && (meleeRating /= 2);
+
+ return meleeRating;
+ }
+
+ return 0;
+ };
+
+ const skillsScore = function () {
+ let skillsRating = [
+ [sdk.stats.AllSkills, 0],
+ [sdk.stats.AddClassSkills, me.classid],
+ [sdk.stats.AddSkillTab, buildInfo.tabSkills],
+ ].reduce(function (acc, [stat, subId]) {
+ return acc + item.getStatEx(stat, subId) * _tierWeights.skill.get(stat);
+ }, 0);
+ (!buildInfo.caster && item.getItemType() === "Weapon") && (skillsRating /= 4);
+ const _misc = { wanted: 40, useful: 35 };
+
+ let selectedWeights = [_misc.wanted, _misc.useful];
+ let selectedSkills = [buildInfo.wantedSkills, buildInfo.usefulSkills];
+
+ for (let weight of selectedWeights) {
+ for (let type of selectedSkills) {
+ for (let skill of type) {
+ skillsRating += item.getStatEx(sdk.stats.SingleSkill, skill) * weight;
+ }
+ }
+ }
+
+ // Spirit Fix for barb
+ (item.prefixnum === sdk.locale.items.Spirit && !buildInfo.caster) && (skillsRating -= 400);
+
+ return skillsRating;
+ };
+
+ const ctcScore = function () {
+ // chance to cast doesn't exist in classic
+ if (me.classic) return 0;
+
+ let ctcRating = 0;
+ let ctcItems = [];
+ const stats = item.getStat(-2);
+ const ctcSkillObj = (ctcType, skill, level) => ({ ctcType: ctcType, skill: skill, level: level });
+ const meleeCheck = !buildInfo.caster;
+ /**
+ * @param {number} type
+ */
+ const buildList = function (type) {
+ let skill, level;
+ if (stats.hasOwnProperty(type)) {
+ if (stats[type] instanceof Array) {
+ for (let i = 0; i < stats[type].length; i++) {
+ if (stats[type][i] !== undefined) {
+ ({ skill, level } = stats[type][i]);
+ ctcItems.push(ctcSkillObj(type, skill, level));
+ }
+ }
+ } else {
+ ({ skill, level } = stats[type]);
+ ctcItems.push(ctcSkillObj(type, skill, level));
+ }
+ }
+ };
+
+ buildList(sdk.stats.SkillWhenStruck);
+
+ if (meleeCheck) {
+ buildList(sdk.stats.SkillOnAttack);
+ buildList(sdk.stats.SkillOnStrike);
+ } else {
+ _tierWeights.ctc.set(sdk.skills.Venom, 0);
+ if (me.charlvl > 50) {
+ _tierWeights.ctc.set(sdk.skills.ChargedBolt, 2);
+ }
+ }
+ if (!ctcItems.length) return 0;
+
+ ctcItems
+ .filter(function (v, i, a) {
+ return a.findIndex(function (el) {
+ return ["ctcType", "skill"].every(function (k) {
+ return el[k] === v[k];
+ });
+ }) === i;
+ })
+ .forEach(function (el) {
+ if (!_tierWeights.ctc.has(el.skill)) return;
+ ctcRating += (el.level * _tierWeights.ctc.get(el.skill) * _tierWeights.ctc.get(el.ctcType));
+ });
+
+ return ctcRating;
+ };
+
+ tier === undefined && (tier = 1); // set to 1 for native autoequip to use items.
+ tier += generalScore();
+ tier += resistScore();
+ tier += buildScore();
+ tier += skillsScore();
+ tier += ctcScore();
+ tier += chargeditemscore(item, -1, buildInfo);
+
+ if (tier > 1 && tier < 50000 /* NTIP.MAX_TIER */ && NTIP.CheckItem(item, NTIP.FinalGear) === Pickit.Result.WANTED) {
+ // console.debug(item.prettyPrint + "~~~" + tier);
+ tier += NTIP.MAX_TIER;
+ }
+
+ return Math.max(1, tier);
+ };
+
+ /**
+ * @param {ItemUnit} item
+ */
+ const secondaryscore = function (item) {
+ let tier = 0;
+
+ Check.finalBuild().precastSkills
+ .forEach(function (skill) {
+ tier += item.getStatEx(sdk.stats.SingleSkill, skill) * 50;
+ });
+
+ tier += item.getStatEx(sdk.stats.FCR) * 5; // add FCR
+ tier += item.getStatEx(sdk.stats.FHR) * 3; // add faster hit recovery
+
+ return [
+ [sdk.stats.AllSkills, -1],
+ [sdk.stats.AddClassSkills, me.classid],
+ [sdk.stats.AddSkillTab, (me.barbarian ? sdk.skills.tabs.Warcries : Check.finalBuild().tabSkills)],
+ ].reduce(function (acc, [stat, subId]) {
+ return acc + item.getStatEx(stat, subId) * _tierWeights.skill.get(stat);
+ }, tier);
+ };
+
+ /**
+ * @param {ItemUnit} item
+ */
+ const charmscore = function (item) {
+ if (me.data.charmGids.includes(item.gid)) return 1000;
+ // depending on invo space it might be worth it early on to keep 1 or 2 non-skiller grandcharms - @todo test that out
+ if (!item.unique && item.classid === sdk.items.GrandCharm
+ && !me.getSkillTabs().some(s => item.getStatEx(sdk.stats.AddSkillTab, s))) {
+ return -1;
+ }
+ const buildInfo = Check.currentBuild();
+
+ let charmRating = 1;
+
+ // Gheeds, Torch, annhi - we know possible attributes so don't waste resources checking for all the others
+ if (item.unique) {
+ charmRating += item.getStatEx(sdk.stats.Strength); // handle +all atrributes
+ charmRating += item.getStatEx(sdk.stats.AllRes);
+ if (item.isAnni) {
+ charmRating += item.getStatEx(sdk.stats.AllSkills) * _tierWeights.charms.get(sdk.stats.AllSkills);
+ charmRating += item.getStatEx(sdk.stats.AddExperience);
+ } else if (item.isGheeds) {
+ charmRating += item.getStatEx(sdk.stats.GoldBonus);
+ charmRating += item.getStatEx(sdk.stats.ReducedPrices) * 1.5;
+ charmRating += item.getStatEx(sdk.stats.MagicBonus) * _tierWeights.charms.get(sdk.stats.MagicBonus);
+ } else {
+ charmRating += item.getStatEx(sdk.stats.AddClassSkills, me.classid) * _tierWeights.charms.get(sdk.stats.AddClassSkills);
+ }
+ } else {
+ charmRating += item.getStatEx(sdk.stats.AddSkillTab, buildInfo.tabSkills) * _tierWeights.charms.get(sdk.stats.AddSkillTab);
+ charmRating += ((item.getStatEx(sdk.stats.PerLevelHp) / 2048 * me.charlvl)) * _tierWeights.charms.get(sdk.stats.PerLevelHp);
+ charmRating += ((item.getStatEx(sdk.stats.PerLevelMana) / 2048 * me.charlvl)) * _tierWeights.charms.get(sdk.stats.PerLevelMana);
+
+ if (!buildInfo.caster) {
+ charmRating += item.getStatEx(sdk.stats.MinDamage) * 3; // add MIN damage
+ charmRating += item.getStatEx(sdk.stats.MaxDamage) * 3; // add MAX damage
+ charmRating += sumElementalDmg(item); // add elemental damage
+ charmRating += item.getStatEx(sdk.stats.ToHit) * 0.5; // add AR
+ }
+
+ return [
+ sdk.stats.MaxHp, sdk.stats.MaxMana,
+ sdk.stats.FireResist, sdk.stats.LightResist, sdk.stats.ColdResist, sdk.stats.PoisonResist,
+ sdk.stats.FHR, sdk.stats.FRW, sdk.stats.MagicBonus, sdk.stats.GoldBonus, sdk.stats.Defense,
+ sdk.stats.Strength, sdk.stats.Dexterity, sdk.stats.Vitality, sdk.stats.Energy,
+ ].reduce(function (acc, stat) {
+ return acc + item.getStatEx(stat) * _tierWeights.charms.get(stat);
+ }, charmRating);
+ }
+ return charmRating;
+ };
+
+ // export to global scope
+ global.mercscore = mercscore;
+ global.chargeditemscore = chargeditemscore;
+ global.tierscore = tierscore;
+ global.secondaryscore = secondaryscore;
+ global.charmscore = charmscore;
+})([].filter.constructor("return this")());
diff --git a/libs/SoloPlay/Functions/Globals.js b/libs/SoloPlay/Functions/Globals.js
index 07ebf8d9..fce5e0d0 100644
--- a/libs/SoloPlay/Functions/Globals.js
+++ b/libs/SoloPlay/Functions/Globals.js
@@ -10,1368 +10,1106 @@
* @todo
* - split up this file into appropriate sections
*/
-includeIfNotIncluded("OOG.js");
+
+// all we really need from oog is D2Bot
+includeIfNotIncluded("oog/D2Bot.js");
includeIfNotIncluded("SoloPlay/Tools/Developer.js");
includeIfNotIncluded("SoloPlay/Tools/CharData.js");
includeIfNotIncluded("SoloPlay/Functions/PrototypeOverrides.js");
+// not every thread needs these
/** @global */
const Overrides = require("../../modules/Override");
/** @global */
const Coords_1 = require("../Modules/Coords");
/** @global */
-const PotData = require("../modules/PotData");
-/** @global */
-const GameData = require("../Modules/GameData");
-/** @global */
-const AreaData = require("../Modules/AreaData");
+const GameData = require("../Modules/GameData/GameData");
const MYCLASSNAME = sdk.player.class.nameOf(me.classid).toLowerCase();
includeIfNotIncluded("SoloPlay/BuildFiles/" + MYCLASSNAME + "/" + MYCLASSNAME + ".js");
-let myData = CharData.getStats();
-
-// these builds are not possible to do on classic
-const impossibleClassicBuilds = ["Bumper", "Socketmule", "Witchyzon", "Auradin", "Torchadin", "Immortalwhirl", "Sancdreamer", "Faithbowzon", "Wfzon"];
-// these builds are not possible to do without ladder runewords
-const impossibleNonLadderBuilds = ["Auradin", "Sancdreamer", "Faithbowzon"];
-
-Unit.prototype.__defineGetter__("mercid", function () {
- return !!myData ? myData.merc.classid : me.getMerc().classid;
-});
-
-Unit.prototype.__defineGetter__("trueStr", function () {
- return !!myData ? myData.me.strength : me.rawStrength;
-});
-
-Unit.prototype.__defineGetter__("trueDex", function () {
- return !!myData ? myData.me.dexterity : me.rawDexterity;
-});
-
function myPrint (str = "", toConsole = false, color = 0) {
- console.log("ÿc8Kolbot-SoloPlayÿ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("Kolbot-SoloPlay :: " + str, color);
-}
-
-function updateMyData () {
- let obj = JSON.stringify(Misc.copy(myData));
- let myThread = getScript(true).name;
- CharData.threads.forEach(function (script) {
- let curr = getScript(script);
- if (curr && myThread !== curr.name) {
- curr.send("data--" + obj);
- }
- });
+ console.log("ÿc8Kolbot-SoloPlayÿ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("Kolbot-SoloPlay :: " + str, color);
}
// general settings
const SetUp = {
- mercEnabled: true,
-
- init: function () {
- let myData = CharData.getStats();
-
- if (!myData.initialized) {
- myData.me.startTime = me.gamestarttime;
- myData.me.level = me.charlvl;
- myData.me.classid = me.classid;
- myData.me.charName = me.name;
- myData.me.strength = me.rawStrength;
- myData.me.dexterity = me.rawDexterity;
-
- if (me.expansion) {
- myData.me.charms = Check.finalBuild().finalCharms;
- }
-
- myData.initialized = true;
- CharData.updateData("me", myData);
- }
-
- let temp = Misc.copy(myData);
-
- if (myData.me.currentBuild !== CharInfo.getActiveBuild()) {
- myData.me.currentBuild = CharInfo.getActiveBuild();
- }
-
- let currDiffStr = sdk.difficulty.nameOf(me.diff).toLowerCase();
-
- if (sdk.difficulty.Difficulties.indexOf(myData.me.highestDifficulty) < sdk.difficulty.Difficulties.indexOf(sdk.difficulty.nameOf(me.diff))) {
- myData.me.highestDifficulty = sdk.difficulty.nameOf(me.diff);
- }
-
- if (!!me.smith && myData[currDiffStr].imbueUsed === false) {
- myData[currDiffStr].imbueUsed = true;
- }
-
- if (!!me.respec && myData[currDiffStr].respecUsed === false) {
- myData[currDiffStr].respecUsed = true;
- }
-
- myData.me.level !== me.charlvl && (myData.me.level = me.charlvl);
- myData.me.strength !== me.rawStrength && (myData.me.strength = me.rawStrength);
- myData.me.dexterity !== me.rawDexterity && (myData.me.dexterity = me.rawDexterity);
-
- // expansion check
- let [cUpdate, mUpdate] = [false, false];
-
- if (me.expansion) {
- if (!myData.merc.gear) {
- myData.merc.gear = [];
- mUpdate = true;
- }
-
- // merc check
- if (!!me.getMercEx()) {
- // TODO: figure out how to ensure we are already using the right merc to prevent re-hiring
- // can't do an aura check as merc auras are bugged, only useful info from getUnit is the classid
- let merc = me.getMercEx();
- let mercItems = merc.getItemsEx();
- let preLength = myData.merc.gear.length;
- let check = myData.merc.gear.filter(i => mercItems.some(item => item.prefixnum === i));
-
- if (check !== preLength) {
- mUpdate = true;
- myData.merc.gear = check;
- }
-
- let mercInfo = Mercenary.getMercInfo(merc);
- mercInfo.classid !== myData.merc.classid && (myData.merc.classid = mercInfo.classid);
- mercInfo.act !== myData.merc.act && (myData.merc.act = mercInfo.act);
- mercInfo.difficulty !== myData.merc.difficulty && (myData.merc.difficulty = mercInfo.difficulty);
-
- if (merc.classid === sdk.mercs.Guard && !Mercenary.checkMercSkill(myData.merc.type)) {
- // go back, need to make sure this works properly.
- // only "go back" if we are past the difficulty we need to be in to hire merc. Ex. In hell but want holy freeze merc
- // only if we have enough gold on hand to hire said merc
- // return to our orignal difficulty afterwards
- }
- }
-
- // charm check
- if (!myData.me.charms || !Object.keys(myData.me.charms).length) {
- myData.me.charms = Check.finalBuild().finalCharms;
- cUpdate = true;
- }
-
- if (!myData.me.charmGids || myData.me.charmGids.length > 0) {
- myData.me.charmGids = [];
- cUpdate = true;
- }
-
- const finalCharmKeys = Object.keys(myData.me.charms);
- // gids change from game to game so reset our list
- for (let i = 0; i < finalCharmKeys.length; i++) {
- let cKey = finalCharmKeys[i];
- if (myData.me.charms[cKey].have.length) {
- myData.me.charms[cKey].have = [];
- cUpdate = true;
- }
- }
-
- if (!!me.shenk && myData[currDiffStr].socketUsed === false) {
- myData[currDiffStr].socketUsed = true;
- }
-
- if (mUpdate) {
- CharData.updateData("merc", myData);
- }
- }
-
- let changed = Misc.recursiveSearch(myData, temp);
-
- if (Object.keys(changed).length > 0 || cUpdate) {
- CharData.updateData("me", myData);
- }
- },
-
- // Should this be moved elsewhere? Currently have to include Globals then call this to include rest of overrides
- // which in doing so would include globals anyway but does this always need to be included first?
- // really need a centralized way to make sure all files use/have the custom functions and all threads stay updated without having to
- // scriptBroadcast all the time
- include: function () {
- let files = dopen("libs/SoloPlay/Functions/").getFiles();
- if (!files.length) throw new Error("Failed to find my files");
- if (!files.includes("Globals.js")) {
- console.warn("Incorrect Files?", files);
- // something went wrong?
- while (!files.includes("Globals.js")) {
- files = dopen("libs/SoloPlay/Functions/").getFiles();
- delay(50);
- }
- }
- Array.isArray(files) && files
- .filter(file => file.endsWith(".js"))
- .sort(a => a.startsWith("PrototypeOverrides.js") ? 0 : 1) // Dirty fix to load new prototypes first
- .forEach(function (x) {
- if (!isIncluded("SoloPlay/Functions/" + x)) {
- if (!include("SoloPlay/Functions/" + x)) {
- throw new Error("Failed to include " + "SoloPlay/Functions/" + x);
- }
- }
- });
- },
-
- // Storage Settings
- sortSettings: {
- 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.SuperHealingPotion
- ],
- 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
- ],
- },
-
- currentBuild: this.currentBuild,
- finalBuild: this.finalBuild,
-
- // setter for Developer option to stop a profile once it reaches a certain level
- stopAtLevel: (function () {
- if (!Developer.stopAtLevel.enabled) return false;
- let level = Developer.stopAtLevel.profiles.find(profile => profile[0].toLowerCase() === me.profile.toLowerCase()) || false;
- return level ? level[1] : false;
- })(),
-
- // pulls respec requirments from final build file
- finalRespec: function () {
- let respec = Check.finalBuild().respec() ? me.charlvl : 100;
-
- if (respec === me.charlvl && me.charlvl < 60) {
- showConsole();
- console.log("ÿc8Kolbot-SoloPlayÿc0: Bot has respecTwo items but is too low a level to respec.");
- console.log("ÿc8Kolbot-SoloPlayÿc0: This only happens with user intervention. Remove the items you gave the bot until at least level 60");
- respec = 100;
- }
-
- return respec;
- },
-
- getTemplate: function () {
- let build = SetUp.currentBuild + "Build" ;
- let template = "SoloPlay/BuildFiles/" + MYCLASSNAME + "/" + MYCLASSNAME + "." + build + ".js";
-
- return {
- buildType: SetUp.currentBuild,
- template: template.toLowerCase()
- };
- },
-
- specPush: function (specType) {
- let buildInfo = SetUp.getTemplate();
- if (!includeIfNotIncluded(buildInfo.template)) throw new Error("Failed to include template: " + buildInfo.template);
-
- let specCheck = [];
- let final = buildInfo.buildType === SetUp.finalBuild;
-
- switch (specType) {
- case "skills":
- // Push skills value from template file
- specCheck = JSON.parse(JSON.stringify((final ? finalBuild.skills : build.skills)));
-
- break;
- case "stats":
- // Push stats value from template file
- specCheck = JSON.parse(JSON.stringify((final ? finalBuild.stats : build.stats)));
-
- break;
- }
-
- return specCheck;
- },
-
- makeNext: function () {
- includeIfNotIncluded("SoloPlay/Tools/NameGen.js");
- includeIfNotIncluded("SoloPlay/Tools/Tracker.js");
- let gameObj, printTotalTime = Developer.logPerformance;
- printTotalTime && (gameObj = Developer.readObj(Tracker.GTPath));
-
- // log info
- myPrint(this.finalBuild + " goal reached. On to the next.");
- D2Bot.printToConsole("Kolbot-SoloPlay: " + this.finalBuild + " goal reached" + (printTotalTime ? " (" + (Developer.formatTime(gameObj.Total + Developer.timer(gameObj.LastSave))) + "). " : ". ") + "Making next...", sdk.colors.D2Bot.Gold);
-
- D2Bot.setProfile(null, null, NameGen());
- CharData.delete(true);
- delay(250);
- D2Bot.restart();
- },
-
- belt: function () {
- let beltSlots = Math.max(1, Storage.BeltSize() - 1);
- Config.BeltColumn.forEach(function (col, index) {
- Config.MinColumn[index] = col.toLowerCase() !== "rv" ? beltSlots : 0;
- });
- },
-
- buffers: function () {
- const isCaster = Check.currentBuild().caster;
- const beltModifer = 4 - Storage.BeltSize();
- const mpFactor = isCaster ? 80 : 50;
- Config.MPBuffer = Math.floor(mpFactor / Math.sqrt(me.mpmax)) + (beltModifer * 2);
- !myData.merc.gear.includes(sdk.locale.items.Insight) && (Config.MPBuffer += 2);
- const hpFactor = isCaster ? 65 : 80;
- Config.HPBuffer = Math.floor(hpFactor / Math.sqrt(me.hpmax)) + (beltModifer * 2);
- },
-
- bowQuiver: function () {
- NTIP.resetRuntimeList();
- if (CharData.skillData.bowData.bowOnSwitch) {
- if ([sdk.items.type.Bow, sdk.items.type.AmazonBow].includes(CharData.skillData.bowData.bowType)) {
- NTIP.addToRuntime("[type] == bowquiver # # [maxquantity] == 1");
- } else if (CharData.skillData.bowData.bowType === sdk.items.type.Crossbow) {
- NTIP.addToRuntime("[type] == crossbowquiver # # [maxquantity] == 1");
- } else if (me.charlvl < 10) {
- NTIP.addToRuntime("[type] == bowquiver # # [maxquantity] == 1");
- }
- }
- },
-
- imbueItems: function () {
- if (SetUp.finalBuild === "Imbuemule") return [];
- let temp = [];
- for (let imbueItem of Config.imbueables) {
- try {
- if (imbueItem.condition()) {
- temp.push("[name] == " + imbueItem.name + " && [quality] >= normal && [quality] <= superior && [flag] != ethereal # [Sockets] == 0 # [maxquantity] == 1");
- }
- } catch (e) {
- console.log(e);
- }
- }
- return temp;
- },
-
- config: function () {
- Config.socketables = [];
-
- if (me.expansion) {
- if (Storage.Stash === undefined) {
- Storage.Init();
- }
- // sometimes it seems hard to find skillers, if we have the room lets try to cube some
- if (Storage.Stash.UsedSpacePercent() < 60 && Item.autoEquipGC().keep.length < CharData.charmData.grand.getCountInfo().max) {
- Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]);
- }
- // switch bow - only for zon/sorc/pal/necro classes right now
- if (!me.barbarian && !me.assassin && !me.druid) {
- NTIP.addLine("([type] == bow || [type] == crossbow) && [quality] >= normal # [itemchargedskill] >= 0 # [secondarytier] == tierscore(item)");
- }
- const expansionExtras = [
- // Special Charms
- "[name] == smallcharm && [quality] == unique # [itemallskills] == 1 # [charmtier] == 100000",
- "[name] == largecharm && [quality] == unique # [itemaddclassskills] == 3 # [charmtier] == 100000",
- "[name] == grandcharm && [quality] == unique # [itemmagicbonus] >= 30 || [itemgoldbonus] >= 150 # [charmtier] == 100000",
- // Merc
- "([type] == circlet || [type] == helm) && ([quality] >= magic || [flag] == runeword) # [itemchargedskill] >= 0 # [Merctier] == mercscore(item)",
- "[type] == armor && ([quality] >= magic || [flag] == runeword) # [itemchargedskill] >= 0 # [Merctier] == mercscore(item)",
- // Rogue
- "me.mercid === 271 && [type] == bow && ([quality] >= magic || [flag] == runeword) # [itemchargedskill] >= 0 # [Merctier] == mercscore(item)",
- // A2 Guard
- "me.mercid === 338 && ([type] == polearm || [type] == spear) && ([quality] >= magic || [flag] == runeword) # [itemchargedskill] >= 0 # [Merctier] == mercscore(item)",
- ];
- NTIP.arrayLooping(expansionExtras);
- this.bowQuiver();
- }
-
- /* General configuration. */
- Config.MinGameTime = 400;
- Config.MaxGameTime = 7200;
- Config.MiniShopBot = true;
- Config.PacketShopping = true;
- Config.TownCheck = true;
- Config.LogExperience = false;
- Config.PingQuit = [{Ping: 600, Duration: 10}];
- Config.Silence = true;
- Config.OpenChests.Enabled = true;
- Config.LowGold = me.normal ? 25000 : me.nightmare ? 50000 : 100000;
- Config.PrimarySlot = 0;
- Config.PacketCasting = 1;
- Config.WaypointMenu = true;
- Config.Cubing = !!me.getItem(sdk.items.quest.Cube);
- Config.MakeRunewords = true;
-
- /* Shrine scan configuration. */
- if (Check.currentBuild().caster) {
- Config.ScanShrines = [
- sdk.shrines.Refilling, sdk.shrines.Health, sdk.shrines.Mana, sdk.shrines.Gem, sdk.shrines.Monster, sdk.shrines.HealthExchange,
- sdk.shrines.ManaExchange, sdk.shrines.Experience, sdk.shrines.Armor, sdk.shrines.ResistFire, sdk.shrines.ResistCold,
- sdk.shrines.ResistLightning, sdk.shrines.ResistPoison, sdk.shrines.Skill, sdk.shrines.ManaRecharge, sdk.shrines.Stamina
- ];
- } else {
- Config.ScanShrines = [
- sdk.shrines.Refilling, sdk.shrines.Health, sdk.shrines.Mana, sdk.shrines.Gem, sdk.shrines.Monster, sdk.shrines.HealthExchange,
- sdk.shrines.ManaExchange, sdk.shrines.Experience, sdk.shrines.Combat, sdk.shrines.Skill, sdk.shrines.Armor, sdk.shrines.ResistFire,
- sdk.shrines.ResistCold, sdk.shrines.ResistLightning, sdk.shrines.ResistPoison, sdk.shrines.ManaRecharge, sdk.shrines.Stamina
- ];
- }
-
- /* General logging. */
- Config.ItemInfo = false;
- Config.LogKeys = false;
- Config.LogOrgans = false;
- Config.LogMiddleRunes = true;
- Config.LogHighRunes = true;
- Config.ShowCubingInfo = true;
-
- /* DClone. */
- Config.StopOnDClone = !!me.expansion;
- Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled
- Config.KillDclone = !!me.expansion;
- Config.DCloneQuit = false;
-
- /* Town configuration. */
- Config.HealHP = 99;
- Config.HealMP = 99;
- Config.HealStatus = true;
- Config.UseMerc = me.expansion;
- Config.MercWatch = SetUp.mercwatch;
- Config.StashGold = me.charlvl * 100;
- Config.ClearInvOnStart = false;
-
- /* Inventory buffers and lock configuration. */
- Config.HPBuffer = 0;
- Config.MPBuffer = 0;
- Config.RejuvBuffer = 4;
- Config.Inventory[0] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
- Config.Inventory[1] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
- Config.Inventory[2] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
- Config.Inventory[3] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
-
- Config.SkipId.push(sdk.monsters.FireTower);
-
- /* FastMod configuration. */
- Config.FCR = 0;
- Config.FHR = 0;
- Config.FBR = 0;
- Config.IAS = 0;
-
- /* AutoStat configuration. */
- Config.AutoStat.Enabled = true;
- Config.AutoStat.Save = 0;
- Config.AutoStat.BlockChance = me.paladin ? 75 : 57;
- Config.AutoStat.UseBulk = true;
- Config.AutoStat.Build = SetUp.specPush("stats");
-
- /* AutoSkill configuration. */
- Config.AutoSkill.Enabled = true;
- Config.AutoSkill.Save = 0;
- Config.AutoSkill.Build = SetUp.specPush("skills");
-
- /* AutoBuild configuration. */
- Config.AutoBuild.Enabled = true;
- Config.AutoBuild.Verbose = false;
- Config.AutoBuild.DebugMode = false;
- Config.AutoBuild.Template = SetUp.currentBuild;
- }
+ mercEnabled: true,
+ _buildTemplate: "",
+
+ init: function () {
+ // ensure finalBuild is properly formatted
+ const checkBuildTemplate = function () {
+ let build = (["Bumper", "Socketmule", "Imbuemule"].includes(SetUp.finalBuild)
+ ? ["Javazon", "Cold", "Bone", "Hammerdin", "Whirlwind", "Wind", "Trapsin"][me.classid]
+ : SetUp.finalBuild) + "Build";
+ return ("libs/SoloPlay/BuildFiles/" + MYCLASSNAME + "/" + MYCLASSNAME + "." + build + ".js").toLowerCase();
+ };
+ SetUp._buildTemplate = checkBuildTemplate();
+
+ if (!FileTools.exists(SetUp._buildTemplate)) {
+ let errors = [];
+ /** @type {string[]} */
+ let possibleBuilds = dopen("libs/SoloPlay/BuildFiles/" + MYCLASSNAME + "/")
+ .getFiles()
+ .filter(file => file.includes("Build"))
+ .map(file => file.substring(file.indexOf(".") + 1, file.indexOf("Build")));
+
+ // try to see if we can correct the finalBuild
+ for (let build of possibleBuilds) {
+ let match = me.data.finalBuild.match(build, "gi");
+
+ if (match) {
+ console.log(match);
+ let old = me.data.finalBuild;
+ me.data.finalBuild = match[0].trim().capitalize(true);
+ errors.push(
+ "~Info tag :: " + old + " was incorrect, I have attempted to remedy this."
+ + " If it is still giving you an error please re-read the documentation. \n"
+ + "New InfoTag/finalBuild :: " + SetUp.finalBuild
+ );
+
+ break;
+ }
+ }
+
+ if (errors.length) {
+ D2Bot.printToConsole("Kolbot-SoloPlay Final Build Error :: \n" + errors.join("\n"), sdk.colors.D2Bot.Red);
+ SetUp._buildTemplate = checkBuildTemplate(); // check again
+ if (!FileTools.exists(SetUp._buildTemplate)) {
+ console.error(
+ "ÿc8Kolbot-SoloPlayÿc0: Failed to find finalBuild template."
+ + " Please check that you have actually entered it in correctly,"
+ + " and that you have the build in to BuildFiles folder."
+ + " Here is what you currently have: " + SetUp.finalBuild);
+ throw new Error("finalBuild(): Failed to find template: " + SetUp._buildTemplate);
+ }
+ D2Bot.setProfile(null, null, null, null, null, SetUp.finalBuild);
+ CharData.updateData("me", "finalBuild", SetUp.finalBuild);
+ }
+ }
+
+ if (!me.data.initialized) {
+ me.data.startTime = me.gamestarttime;
+ me.data.level = me.charlvl;
+ me.data.classid = me.classid;
+ me.data.charName = me.name;
+ me.data.strength = me.rawStrength;
+ me.data.dexterity = me.rawDexterity;
+
+ if (me.expansion) {
+ me.data.charms = Check.finalBuild().finalCharms;
+ }
+
+ me.data.initialized = true;
+ CharData.updateData("me", me.data);
+ }
+
+ let temp = copyObj(me.data);
+
+ if (me.data.currentBuild !== CharInfo.getActiveBuild()) {
+ me.data.currentBuild = CharInfo.getActiveBuild();
+ }
+
+ let currDiffStr = sdk.difficulty.nameOf(me.diff).toLowerCase();
+
+ if (sdk.difficulty.Difficulties.indexOf(me.data.highestDifficulty) < me.diff) {
+ me.data.highestDifficulty = sdk.difficulty.nameOf(me.diff);
+ }
+
+ if (me.smith && me.data[currDiffStr].imbueUsed === false) {
+ me.data[currDiffStr].imbueUsed = true;
+ }
+
+ if (me.respec && me.data[currDiffStr].respecUsed === false) {
+ me.data[currDiffStr].respecUsed = true;
+ }
+
+ me.data.level !== me.charlvl && (me.data.level = me.charlvl);
+ me.data.strength !== me.rawStrength && (me.data.strength = me.rawStrength);
+ me.data.dexterity !== me.rawDexterity && (me.data.dexterity = me.rawDexterity);
+
+ // expansion check
+ let [cUpdate, mUpdate] = [false, false];
+
+ if (me.expansion) {
+ if (!me.data.merc.gear) {
+ me.data.merc.gear = [];
+ mUpdate = true;
+ }
+
+ // merc check
+ /** @type {MercUnit} */
+ let merc = me.getMercEx();
+ if (merc) {
+ // TODO: figure out how to ensure we are already using the right merc to prevent re-hiring
+ // can't do an aura check as merc auras are bugged, only useful info from getUnit is the classid
+ let _tempMerc = copyObj(me.data.merc);
+ let mercItems = merc.getItemsEx();
+ let preLength = me.data.merc.gear.length;
+ let check = me.data.merc.gear.filter(function (i) {
+ return mercItems.some(function (item) {
+ return item.prefixnum === i;
+ });
+ });
+
+ if (check !== preLength) {
+ mUpdate = true;
+ me.data.merc.gear = check;
+ }
+
+ let mercInfo = Mercenary.getMercInfo(merc);
+ if (merc.classid !== me.data.merc.classid) {
+ me.data.merc.classid = merc.classid;
+ }
+ if (mercInfo.act !== me.data.merc.act) {
+ me.data.merc.act = mercInfo.act;
+ }
+ if (mercInfo.difficulty !== me.data.merc.difficulty) {
+ me.data.merc.difficulty = mercInfo.difficulty;
+ }
+ if (merc.charlvl !== me.data.merc.level) {
+ me.data.merc.level = merc.charlvl;
+ }
+ if (merc.rawStrength !== me.data.merc.strength) {
+ me.data.merc.strength = merc.rawStrength;
+ }
+ if (merc.rawDexterity !== me.data.merc.dexterity) {
+ me.data.merc.dexterity = merc.rawDexterity;
+ }
+
+ if (merc.classid !== sdk.mercs.Guard) {
+ try {
+ if (mercInfo.skillName !== me.data.merc.skillName) {
+ me.data.merc.skillName = mercInfo.skillName;
+ me.data.merc.skill = MercData.findByName(me.data.merc.skillName, me.data.merc.act).skill;
+ }
+ } catch (e) {
+ //
+ }
+ }
+
+ // if (merc.classid === sdk.mercs.Guard && !Mercenary.checkMercSkill(me.data.merc.type)) {
+ // // go back, need to make sure this works properly.
+ // // only "go back" if we are past the difficulty we need to be in to hire merc. Ex. In hell but want holy freeze merc
+ // // only if we have enough gold on hand to hire said merc
+ // // return to our orignal difficulty afterwards
+ // }
+ let changed = Misc.recursiveSearch(me.data.merc, _tempMerc);
+
+ if (Object.keys(changed).length > 0) {
+ CharData.updateData("merc", me.data);
+ // mUpdate = true;
+ }
+ }
+
+ // charm check
+ if (!me.data.charms || !Object.keys(me.data.charms).length) {
+ me.data.charms = Check.finalBuild().finalCharms;
+ cUpdate = true;
+ }
+
+ if (!me.data.charmGids || me.data.charmGids.length > 0) {
+ me.data.charmGids = [];
+ cUpdate = true;
+ }
+
+ const finalCharmKeys = Object.keys(me.data.charms);
+ // gids change from game to game so reset our list
+ for (let key of finalCharmKeys) {
+ if (me.data.charms[key].have.length) {
+ me.data.charms[key].have = [];
+ cUpdate = true;
+ }
+ }
+
+ if (!!me.shenk && me.data[currDiffStr].socketUsed === false) {
+ me.data[currDiffStr].socketUsed = true;
+ }
+ }
+
+ let changed = Misc.recursiveSearch(me.data, temp);
+
+ if (cUpdate || mUpdate || Object.keys(changed).length > 0) {
+ CharData.updateData("me", me.data);
+ }
+ },
+
+ // Should this be moved elsewhere? Currently have to include Globals then call this to include rest of overrides
+ // which in doing so would include globals anyway but does this always need to be included first?
+ // really need a centralized way to make sure all files use/have the custom functions and all threads stay updated without having to
+ // scriptBroadcast all the time
+ include: function () {
+ let files = dopen("libs/SoloPlay/Functions/").getFiles();
+ if (!files.length) throw new Error("Failed to find my files");
+ if (!files.includes("Globals.js")) {
+ console.warn("Incorrect Files?", files);
+ // something went wrong?
+ while (!files.includes("Globals.js")) {
+ files = dopen("libs/SoloPlay/Functions/").getFiles();
+ delay(50);
+ }
+ }
+ Array.isArray(files) && files
+ .filter(file => file.endsWith(".js"))
+ .sort(a => a.startsWith("PrototypeOverrides.js") ? 0 : 1) // Dirty fix to load new prototypes first
+ .forEach(function (x) {
+ if (!isIncluded("SoloPlay/Functions/" + x)) {
+ if (!include("SoloPlay/Functions/" + x)) {
+ throw new Error("Failed to include " + "SoloPlay/Functions/" + x);
+ }
+ }
+ });
+ },
+
+ // Storage Settings
+ sortSettings: {
+ 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
+ ],
+ },
+
+ currentBuild: this.currentBuild,
+ finalBuild: this.finalBuild,
+
+ // setter for Developer option to stop a profile once it reaches a certain level
+ stopAtLevel: (function () {
+ if (!Developer.stopAtLevel.enabled) return false;
+ let level = Developer.stopAtLevel.profiles.find(prof => String.isEqual(prof[0], me.profile)) || false;
+ return level ? level[1] : false;
+ })(),
+
+ // pulls respec requirments from final build file
+ finalRespec: function () {
+ let respec = Check.finalBuild().respec() ? me.charlvl : 100;
+
+ if (respec === me.charlvl && me.charlvl < 60) {
+ showConsole();
+ console.log(
+ "ÿc8Kolbot-SoloPlayÿc0: Bot has respecTwo items but is too low a level to respec." + "\n"
+ + "This only happens with user intervention. Remove the items you gave the bot until at least level 60"
+ );
+ respec = 100;
+ }
+
+ return respec;
+ },
+
+ autoBuild: function () {
+ let build = me.currentBuild;
+ if (!build) throw new Error("Failed to include template: " + SetUp._buildTemplate);
+
+ /* AutoStat configuration. */
+ Config.AutoStat.Enabled = true;
+ Config.AutoStat.Save = 0;
+ Config.AutoStat.BlockChance = me.paladin ? 75 : 57;
+ Config.AutoStat.UseBulk = true;
+ Config.AutoStat.Build = JSON.parse(JSON.stringify(build.stats));
+
+ /* AutoSkill configuration. */
+ Config.AutoSkill.Enabled = true;
+ Config.AutoSkill.Save = 0;
+ Config.AutoSkill.Build = JSON.parse(JSON.stringify(build.skills));
+
+ /* AutoBuild configuration. */
+ Config.AutoBuild.Enabled = true;
+ Config.AutoBuild.Verbose = false;
+ Config.AutoBuild.DebugMode = false;
+ Config.AutoBuild.Template = SetUp.currentBuild;
+
+ return true;
+ },
+
+ makeNext: function () {
+ includeIfNotIncluded("SoloPlay/Tools/Tracker.js");
+ let gameObj, printTotalTime = Developer.logPerformance;
+ printTotalTime && (gameObj = Tracker.readObj(Tracker.GTPath));
+
+ // log info
+ myPrint(this.finalBuild + " goal reached. On to the next.");
+ D2Bot.printToConsole(
+ "Kolbot-SoloPlay: " + this.finalBuild + " goal reached"
+ + (printTotalTime ? " (" + (Time.format(gameObj.Total + Time.elapsed(gameObj.LastSave))) + "). " : ". ")
+ + "Making next...",
+ sdk.colors.D2Bot.Gold
+ );
+ D2Bot.setProfile(null, null, require("../Tools/NameGen")());
+ CharData.delete(true);
+ delay(250);
+ D2Bot.restart();
+ },
+
+ belt: function () {
+ let beltSlots = Math.max(1, Storage.BeltSize() - 1);
+ Config.BeltColumn.forEach(function (col, index) {
+ Config.MinColumn[index] = col.toLowerCase() !== "rv" ? beltSlots : 0;
+ });
+ },
+
+ buffers: function () {
+ const isCaster = Check.currentBuild().caster;
+ const beltModifer = 4 - Storage.BeltSize();
+ const mpFactor = isCaster ? 80 : 50;
+ Config.MPBuffer = Math.floor(mpFactor / Math.sqrt(me.mpmax)) + (beltModifer * 2);
+ !me.data.merc.gear.includes(sdk.locale.items.Insight) && (Config.MPBuffer += 2);
+ const hpFactor = isCaster ? 65 : 80;
+ Config.HPBuffer = Math.floor(hpFactor / Math.sqrt(me.hpmax)) + (beltModifer * 2);
+ },
+
+ bowQuiver: function () {
+ NTIP.Runtime.clear();
+ if (CharData.skillData.bow.onSwitch) {
+ if ([sdk.items.type.Bow, sdk.items.type.AmazonBow].includes(CharData.skillData.bow.bowType)) {
+ NTIP.addToRuntime("[type] == bowquiver # # [maxquantity] == 1");
+ } else if (CharData.skillData.bow.bowType === sdk.items.type.Crossbow) {
+ NTIP.addToRuntime("[type] == crossbowquiver # # [maxquantity] == 1");
+ } else if (me.charlvl < 10) {
+ NTIP.addToRuntime("[type] == bowquiver # # [maxquantity] == 1");
+ }
+ }
+ },
+
+ imbueItems: function () {
+ if (SetUp.finalBuild === "Imbuemule") return [];
+ let temp = [];
+ for (let imbueItem of Config.imbueables) {
+ try {
+ if (imbueItem.condition()) {
+ temp.push(
+ "[name] == " + imbueItem.name
+ + " && [quality] >= normal && [quality] <= superior && [flag] != ethereal"
+ + " # [Sockets] == 0 # [maxquantity] == 1"
+ );
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ return temp;
+ },
+
+ config: function () {
+ me.equipped.init();
+ // just initializes the data
+ Check.currentBuild();
+ Check.finalBuild();
+
+ Config.socketables = [];
+ Config.AutoEquip = true;
+
+ if (me.ladder > 0 || Developer.addLadderRW) {
+ // Runewords.ladderOverride = true;
+ Config.LadderOveride = true;
+ }
+
+ // common items
+ NTIP.buildList([
+ "([type] == helm || [type] == circlet) && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Belt
+ "[type] == belt && [quality] >= magic && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ "me.normal && [type] == belt && [quality] >= lowquality && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Boots
+ "[type] == boots && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Armor
+ "[type] == armor && ([quality] >= magic || [flag] == runeword) && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Gloves
+ "[type] == gloves && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Amulet
+ "[type] == amulet && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // Rings
+ "[type] == ring && [quality] >= magic # [itemchargedskill] >= 0 # [tier] == tierscore(item)",
+ // non runeword white items
+ "([type] == armor) && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && [sockets] == 1 # [tier] == tierscore(item)",
+ "([type] == helm || [type] == circlet) && [quality] >= normal && [flag] != ethereal # [itemchargedskill] >= 0 && ([sockets] == 1 || [sockets] == 3) # [tier] == tierscore(item)",
+ ]);
+
+ if (me.expansion) {
+ if (Storage.Stash === undefined) {
+ Storage.Init();
+ }
+ // sometimes it seems hard to find skillers, if we have the room lets try to cube some
+ if (Storage.Stash.UsedSpacePercent() < 60
+ && CharmEquip.grandCharm().keep.length < CharData.charms.get("grand").count().max) {
+ Config.Recipes.push([Recipe.Reroll.Magic, "Grand Charm"]);
+ }
+ // switch bow - only for zon/sorc/pal/necro classes right now
+ if (me.charlvl < 12 && !me.barbarian && !me.assassin && !me.druid) {
+ NTIP.addLine(
+ "([type] == bow || [type] == crossbow) && [quality] >= normal # [itemchargedskill] >= 0 # [secondarytier] == tierscore(item)"
+ );
+ this.bowQuiver();
+ }
+ const expansionExtras = [
+ // Special Charms
+ "[name] == smallcharm && [quality] == unique # [itemallskills] == 1 # [charmtier] == 100000",
+ "[name] == largecharm && [quality] == unique # [itemaddclassskills] == 3 # [charmtier] == 100000",
+ "[name] == grandcharm && [quality] == unique # [itemmagicbonus] >= 30 || [itemgoldbonus] >= 150 # [charmtier] == 100000",
+ // Merc
+ "([type] == circlet || [type] == helm) && ([quality] >= magic || [flag] == runeword) # [itemchargedskill] >= 0 # [Merctier] == mercscore(item)",
+ "[type] == armor && ([quality] >= magic || [flag] == runeword) # [itemchargedskill] >= 0 # [Merctier] == mercscore(item)",
+ // Rogue
+ "me.mercid === 271 && [type] == bow && ([quality] >= magic || [flag] == runeword) # [itemchargedskill] >= 0 # [Merctier] == mercscore(item)",
+ // A2 Guard
+ "me.mercid === 338 && ([type] == polearm || [type] == spear) && ([quality] >= magic || [flag] == runeword) # [itemchargedskill] >= 0 # [Merctier] == mercscore(item)",
+ ];
+ NTIP.buildList(expansionExtras);
+ }
+
+ /* General configuration. */
+ Config.MinGameTime = 400;
+ Config.MaxGameTime = 7200;
+ Config.MiniShopBot = true;
+ Config.PacketShopping = true;
+ Config.TownCheck = true;
+ Config.LogExperience = false;
+ Config.PingQuit = [{ Ping: 600, Duration: 10 }];
+ Config.Silence = true;
+ Config.OpenChests.Enabled = true;
+ Config.LowGold = me.normal ? 25000 : me.nightmare ? 50000 : 100000;
+ Config.PrimarySlot = 0;
+ Config.PacketCasting = 1;
+ Config.WaypointMenu = true;
+ Config.Cubing = !!me.getItem(sdk.items.quest.Cube);
+ Config.MakeRunewords = true;
+
+ /* Chicken configuration. */
+ Config.LifeChicken = me.hardcore ? 45 : 10;
+ Config.ManaChicken = 0;
+ Config.MercChicken = 0;
+ Config.TownHP = me.hardcore ? 0 : 35;
+ Config.TownMP = 0;
+
+ /* Potions configuration. */
+ Config.UseHP = me.hardcore ? 90 : 80;
+ Config.UseRejuvHP = me.hardcore ? 65 : 50;
+ Config.UseMP = me.hardcore ? 75 : 65;
+ Config.UseMercHP = 75;
+
+ /* Belt configuration. */
+ Config.BeltColumn = ["hp", "mp", "mp", "rv"];
+ SetUp.belt();
+
+ /* Gambling configuration. */
+ Config.Gamble = true;
+ Config.GambleGoldStart = 1250000;
+ Config.GambleGoldStop = 750000;
+
+ /* AutoMule configuration. */
+ Config.AutoMule.Trigger = [];
+ Config.AutoMule.Force = [];
+ Config.AutoMule.Exclude = [
+ "[name] >= Elrune && [name] <= Lemrune",
+ ];
+
+ /* Shrine scan configuration. */
+ if (Check.currentBuild().caster) {
+ Config.ScanShrines = [
+ sdk.shrines.Refilling, sdk.shrines.Health,
+ sdk.shrines.Mana, sdk.shrines.Gem,
+ sdk.shrines.Monster, sdk.shrines.HealthExchange,
+ sdk.shrines.ManaExchange, sdk.shrines.Experience,
+ sdk.shrines.Armor, sdk.shrines.ResistFire,
+ sdk.shrines.ResistCold, sdk.shrines.ResistLightning,
+ sdk.shrines.ResistPoison, sdk.shrines.Skill,
+ sdk.shrines.ManaRecharge, sdk.shrines.Stamina
+ ];
+ } else {
+ Config.ScanShrines = [
+ sdk.shrines.Refilling, sdk.shrines.Health,
+ sdk.shrines.Mana, sdk.shrines.Gem,
+ sdk.shrines.Monster, sdk.shrines.HealthExchange,
+ sdk.shrines.ManaExchange, sdk.shrines.Experience,
+ sdk.shrines.Combat, sdk.shrines.Skill,
+ sdk.shrines.Armor, sdk.shrines.ResistFire,
+ sdk.shrines.ResistCold, sdk.shrines.ResistLightning,
+ sdk.shrines.ResistPoison, sdk.shrines.ManaRecharge, sdk.shrines.Stamina
+ ];
+ }
+
+ /* General logging. */
+ Config.ItemInfo = false;
+ Config.LogKeys = false;
+ Config.LogOrgans = false;
+ Config.LogMiddleRunes = true;
+ Config.LogHighRunes = true;
+ Config.ShowCubingInfo = true;
+
+ /* DClone. */
+ Config.StopOnDClone = !!me.expansion;
+ Config.SoJWaitTime = 5; // Time in minutes to wait for another SoJ sale before leaving game. 0 = disabled
+ Config.KillDclone = !!me.expansion;
+ Config.DCloneQuit = false;
+
+ /* Town configuration. */
+ Config.HealHP = 99;
+ Config.HealMP = 99;
+ Config.HealStatus = true;
+ Config.UseMerc = me.expansion;
+ Config.MercWatch = SetUp.mercwatch;
+ Config.StashGold = me.charlvl * 1000;
+ Config.ClearInvOnStart = false;
+
+ /* Inventory buffers and lock configuration. */
+ Config.HPBuffer = 0;
+ Config.MPBuffer = 0;
+ Config.RejuvBuffer = 4;
+ Config.Inventory[0] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+ Config.Inventory[1] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+ Config.Inventory[2] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+ Config.Inventory[3] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
+
+ Config.SkipId.push(sdk.monsters.FireTower);
+
+ /* FastMod configuration. */
+ Config.FCR = 0;
+ Config.FHR = 0;
+ Config.FBR = 0;
+ Config.IAS = 0;
+
+ SetUp.autoBuild();
+ }
};
Object.defineProperties(SetUp, {
- currentBuild: {
- get: function () {
- return myData.me.currentBuild;
- },
- },
- finalBuild: {
- get: function () {
- return myData.me.finalBuild;
- },
- },
- mercwatch: {
- get: function () {
- const myGold = me.gold;
- const cLvl = me.charlvl;
- let lowGold = Math.min(Math.floor(500 + (cLvl * 150 * Math.sqrt(cLvl - 1))), 250000);
- return (SetUp.mercEnabled && (myGold > lowGold) && (myGold > me.mercrevivecost));
- }
- },
+ currentBuild: {
+ get: function () {
+ return me.data.currentBuild;
+ },
+ },
+ finalBuild: {
+ get: function () {
+ return me.data.finalBuild;
+ },
+ },
+ mercwatch: {
+ get: function () {
+ const myGold = me.gold;
+ const cLvl = me.charlvl;
+ let lowGold = Math.min(Math.floor(500 + (cLvl * 150 * Math.sqrt(cLvl - 1))), 250000);
+ return (SetUp.mercEnabled && (myGold > lowGold) && (myGold > me.mercrevivecost));
+ }
+ },
});
-// SoloPlay general gameplay items
-const nipItems = {
- General: [
- "[name] == tomeoftownportal",
- "[name] == tomeofidentify",
- "[name] == gold # [gold] >= me.charlvl * 3 * me.diff",
- "(me.charlvl < 20 || me.gold < 500) && [name] == minorhealingpotion",
- "(me.charlvl < 25 || me.gold < 2000) && [name] == lighthealingpotion",
- "(me.charlvl < 29 || me.gold < 5000) && [name] == healingpotion",
- "[name] == greaterhealingpotion",
- "[name] == superhealingpotion",
- "(me.charlvl < 20 || me.gold < 1000) && [name] == minormanapotion",
- "[name] == lightmanapotion",
- "[name] == manapotion",
- "[name] == greatermanapotion",
- "[name] == supermanapotion",
- "[name] == rejuvenationpotion",
- "[name] == fullrejuvenationpotion",
- "[name] == scrolloftownportal # # [maxquantity] == 20",
- "[name] == scrollofidentify # # [maxquantity] == 20",
- "[name] == key # # [maxquantity] == 12",
- ],
-
- Quest: [
- "[name] == mephisto'ssoulstone",
- "[name] == hellforgehammer",
- "[name] == scrollofinifuss",
- "[name] == keytothecairnstones",
- "[name] == bookofskill",
- "[name] == horadriccube",
- "[name] == shaftofthehoradricstaff",
- "[name] == topofthehoradricstaff",
- "[name] == horadricstaff",
- "[name] == ajadefigurine",
- "[name] == thegoldenbird",
- "[name] == potionoflife",
- "[name] == lamesen'stome",
- "[name] == khalim'seye",
- "[name] == khalim'sheart",
- "[name] == khalim'sbrain",
- "[name] == khalim'sflail",
- "[name] == khalim'swill",
- "[name] == scrollofresistance",
- ],
-};
-
-const addSocketableObj = (classid, socketWith = [], temp = [], useSocketQuest = false, condition = () => {}) => ({
- classid: classid,
- socketWith: socketWith,
- temp: temp,
- useSocketQuest: useSocketQuest,
- condition: condition
-});
-const basicSocketables = {
- caster: [],
- all: [],
-};
-// insight base
-basicSocketables.all.push(addSocketableObj(sdk.items.Bill, [], [], true, (item) =>
- me.nightmare && item.ilvl >= 26 && item.isBaseType && item.ethereal
-));
-// insight base
-basicSocketables.all.push(addSocketableObj(sdk.items.ColossusVoulge, [], [], true, (item) =>
- me.nightmare && item.ilvl >= 26 && item.isBaseType && item.ethereal
-));
-// Crown of Ages
-basicSocketables.caster.push(addSocketableObj(sdk.items.Corona, [sdk.items.runes.Ber, sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
- false, (item) => item.unique
-));
-// Moser's
-basicSocketables.caster.push(addSocketableObj(sdk.items.RoundShield, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Diamond],
- false, (item) => item.unique && !item.ethereal
-));
-// Spirit Forge
-basicSocketables.caster.push(addSocketableObj(sdk.items.LinkedMail, [sdk.items.runes.Shael], [sdk.items.gems.Perfect.Ruby],
- false, (item) => item.unique && !item.ethereal
-));
-// Dijjin Slayer
-basicSocketables.caster.push(addSocketableObj(sdk.items.Ataghan, [sdk.items.runes.Amn], [sdk.items.gems.Perfect.Skull],
- false, (item) => !Check.currentBuild().caster && item.unique && !item.ethereal
-));
-// Bone Hew - for merc
-basicSocketables.caster.push(addSocketableObj(sdk.items.OgreAxe, [sdk.items.runes.Hel, sdk.items.runes.Amn], [sdk.items.gems.Perfect.Skull],
- false, (item) => item.unique
-));
-// spirit base
-basicSocketables.caster.push(addSocketableObj(sdk.items.BroadSword, [], [], true, (item) =>
- me.normal && !Check.haveBase("sword", 4) && !me.checkItem({name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Sword}).have
- && item.ilvl >= 26 && item.isBaseType && !item.ethereal
-));
-// spirit base
-basicSocketables.caster.push(addSocketableObj(sdk.items.CrystalSword, [], [], true, (item) =>
- me.normal && !Check.haveBase("sword", 4) && !me.checkItem({name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Sword}).have
- && item.ilvl >= 26 && item.ilvl <= 40 && item.isBaseType && !item.ethereal
-));
-// Lidless
-basicSocketables.caster.push(addSocketableObj(sdk.items.GrimShield, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Diamond], !me.hell, (item) =>
- item.unique && (item.isInStorage || (item.isEquipped && !item.isOnSwap)) && !item.ethereal
-));
-
// misc
const goToDifficulty = function (diff = undefined, reason = "") {
- try {
- if (diff === undefined) throw new Error("diff is undefined");
-
- let diffString;
- switch (typeof diff) {
- case "string":
- diff = diff.capitalize(true);
- if (!sdk.difficulty.Difficulties.includes(diff)) throw new Error("difficulty doesn't exist" + diff);
- if (sdk.difficulty.Difficulties.indexOf(diff) === me.diff) throw new Error("already in this difficulty" + diff);
- diffString = diff;
-
- break;
- case "number":
- if (diff === me.diff || diff < 0) throw new Error("invalid diff" + diff);
- diffString = sdk.difficulty.nameOf(diff);
-
- break;
- default:
- throw new Error("?");
- }
-
- CharData.updateData("me", "setDifficulty", diffString);
- myPrint("Going to " + diffString + " " + reason, true);
- delay(1000);
- if (CharData.getStats().me.setDifficulty !== diffString) {
- throw new Error("Failed to set difficulty");
- }
- scriptBroadcast("quit");
- } catch (e) {
- console.debug(e.message ? e.message : e);
- }
-
- return false;
+ try {
+ if (diff === undefined) throw new Error("diff is undefined");
+
+ let diffString;
+ switch (typeof diff) {
+ case "string":
+ diff = diff.capitalize(true);
+ if (!sdk.difficulty.Difficulties.includes(diff)) throw new Error("difficulty doesn't exist" + diff);
+ if (sdk.difficulty.Difficulties.indexOf(diff) === me.diff) throw new Error("already in this difficulty" + diff);
+ diffString = diff;
+
+ break;
+ case "number":
+ if (diff === me.diff || diff < 0) throw new Error("invalid diff" + diff);
+ diffString = sdk.difficulty.nameOf(diff);
+
+ break;
+ default:
+ throw new Error("?");
+ }
+
+ CharData.updateData("me", "setDifficulty", diffString);
+ myPrint("Going to " + diffString + " " + reason, true);
+ delay(1000);
+ if (CharData.getStats().setDifficulty !== diffString) {
+ throw new Error("Failed to set difficulty");
+ }
+ scriptBroadcast("quit");
+
+ while (me.ingame) {
+ delay(3);
+ }
+ } catch (e) {
+ console.debug(e.message ? e.message : e);
+ return false;
+ }
+
+ return true;
};
-const buildAutoBuildTempObj = (update = () => {}) => ({
- SkillPoints: [-1],
- StatPoints: [-1, -1, -1, -1, -1],
- Update: update
-});
-
// General Game functions
const Check = {
- lowGold: false,
-
- gold: function () {
- let gold = me.gold;
- let goldLimit = [25000, 50000, 100000][me.diff];
-
- if ((me.normal && !Pather.accessToAct(2)) || gold >= goldLimit) {
- return true;
- }
-
- me.overhead("low gold");
-
- return false;
- },
-
- brokeAf: function (announce = true) {
- let gold = me.gold;
- let lowGold = Math.min(Math.floor(500 + (me.charlvl * 100 * Math.sqrt(me.charlvl - 1))), 250000);
-
- switch (true) {
- case (me.charlvl < 15):
- case (me.normal && !Pather.accessToAct(2)):
- case (gold >= lowGold):
- case (me.charlvl >= 15 && gold > Math.floor(lowGold / 2) && gold > me.getRepairCost()):
- return false;
- }
-
- if (announce) {
- myPrint("very low gold. My Gold: " + gold);
- NTIP.addLine("[name] == gold # [gold] >= 1");
- }
-
- return true;
- },
-
- broken: function () {
- let gold = me.gold;
-
- // Almost broken but not quite
- if (((Item.getEquippedItem(sdk.body.RightArm).durability <= 30 && Item.getEquippedItem(sdk.body.RightArm).durability > 0)
- || (Item.getEquippedItem(sdk.body.LeftArm).durability <= 30 && Item.getEquippedItem(sdk.body.LeftArm).durability > 0)
- && !me.getMerc() && me.charlvl >= 15 && !me.normal && !me.nightmare && gold < 1000)) {
- return 1;
- }
-
- // Broken
- if ((Item.getEquippedItem(sdk.body.RightArm).durability === 0 || Item.getEquippedItem(sdk.body.LeftArm).durability === 0) && me.charlvl >= 15 && !me.normal && gold < 1000) {
- return 2;
- }
-
- return 0;
- },
-
- brokeCheck: function () {
- Town.doChores();
-
- let myGold = me.gold;
- let repairCost = me.getRepairCost();
- let items = (me.getItemsForRepair(100, false) || []);
- let meleeChar = !Check.currentBuild().caster;
- let msg = "";
- let diff = -1;
-
- switch (true) {
- case myGold > repairCost:
- return false;
- case me.normal:
- case !meleeChar && me.nightmare:
- this.lowGold = myGold < repairCost;
- return false;
- case meleeChar && !me.normal:
- // check how broke we are - only for melee chars since casters don't care about weapons
- let wep = items.filter(i => i.isEquipped && i.bodylocation === sdk.body.RightArm).first();
- if (!!wep && meleeChar && wep.durabilityPercent === 0) {
- // we are really broke - go back to normal
- msg = " We are broken - lets get some easy gold in normal.";
- diff = sdk.difficulty.Normal;
- }
-
- break;
- case !meleeChar && me.hell:
- msg = " We are pretty broke, lets run some easy stuff in nightmare for gold";
- diff = sdk.difficulty.Nightmare;
-
- break;
- }
-
- if (diff > -1) {
- console.debug("My gold: " + myGold + ", Repair cost: " + repairCost);
- goToDifficulty(diff, msg + (" My gold: " + myGold + ", Repair cost: " + repairCost));
-
- return true;
- }
-
- return false;
- },
-
- resistance: function () {
- let resPenalty = me.getResPenalty(me.diff + 1);
- let [frRes, lrRes, crRes, prRes] = [(me.realFR - resPenalty), (me.realLR - resPenalty), (me.realCR - resPenalty), (me.realPR - resPenalty)];
-
- return {
- Status: ((frRes > 0) && (lrRes > 0) && (crRes > 0)),
- FR: frRes,
- CR: crRes,
- LR: lrRes,
- PR: prRes,
- };
- },
-
- nextDifficulty: function (announce = true) {
- let diffShift = me.diff;
- let res = this.resistance();
- let lvlReq = !!((me.charlvl >= CharInfo.levelCap) && !["Bumper", "Socketmule"].includes(SetUp.finalBuild) && !this.broken());
-
- if (me.diffCompleted) {
- if (lvlReq) {
- if (res.Status) {
- diffShift = me.diff + 1;
- announce && D2Bot.printToConsole("Kolbot-SoloPlay: next difficulty requirements met. Starting: " + sdk.difficulty.nameOf(diffShift), sdk.colors.D2Bot.Blue);
- } else {
- if (me.charlvl >= CharInfo.levelCap + (!me.normal ? 5 : 2)) {
- diffShift = me.diff + 1;
- announce && D2Bot.printToConsole("Kolbot-SoloPlay: Over leveled. Starting: " + sdk.difficulty.nameOf(diffShift));
- } else {
- announce && myPrint(sdk.difficulty.nameOf(diffShift + 1) + " requirements not met. Negative resistance. FR: " + res.FR + " | CR: " + res.CR + " | LR: " + res.LR);
- return false;
- }
- }
- }
- } else {
- return false;
- }
-
- return sdk.difficulty.nameOf(diffShift);
- },
-
- runes: function () {
- if (me.classic) return false;
- let needRunes = true;
-
- switch (me.diff) {
- case sdk.difficulty.Normal:
- // Have runes or stealth and ancients pledge
- if (me.haveRunes([sdk.items.runes.Tal, sdk.items.runes.Eth]) || me.checkItem({name: sdk.locale.items.Stealth}).have) {
- needRunes = false;
- }
-
- break;
- case sdk.difficulty.Nightmare:
- if ((me.haveRunes([sdk.items.runes.Tal, sdk.items.runes.Thul, sdk.items.runes.Ort, sdk.items.runes.Amn]) && Check.currentBuild().caster)
- || (!me.paladin && me.checkItem({name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Sword}).have)
- || (me.paladin && me.haveAll([{name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Sword}, {name: sdk.locale.items.Spirit, itemtype: sdk.items.type.AuricShields}]))
- || (me.necromancer && me.checkItem({name: sdk.locale.items.White}).have
- && (me.checkItem({name: sdk.locale.items.Rhyme, itemtype: sdk.items.type.VoodooHeads}).have || Item.getEquippedItem(sdk.body.LeftArm).tier > 800))
- || (me.barbarian && (me.checkItem({name: sdk.locale.items.Lawbringer}).have || me.baal))) {
- needRunes = false;
- }
-
- break;
- case sdk.difficulty.Hell:
- if (!me.baal || (me.sorceress && !["Blova", "Lightning"].includes(SetUp.currentBuild))) {
- needRunes = false;
- }
-
- break;
- }
-
- return needRunes;
- },
-
- // todo: need to finish up adding locale string ids to sdk so I can remove this in favor of better me.checkItem prototype
- haveItem: function (type, flag, iName = undefined) {
- let [isClassID, itemCHECK, typeCHECK] = [false, false, false];
-
- flag && typeof flag === "string" && (flag = flag.capitalize(true));
- typeof iName === "string" && (iName = iName.toLowerCase());
-
- let items = me.getItemsEx()
- .filter(function (item) {
- return !item.questItem && (flag === "Runeword" ? item.isRuneword : item.quality === sdk.items.quality[flag]);
- });
-
- switch (typeof type) {
- case "string":
- typeof type === "string" && (type = type.toLowerCase());
- if (type !== "dontcare" && !NTIPAliasType[type] && !NTIPAliasClassID[type]) return false;
- if (type === "dontcare") {
- typeCHECK = true; // we don't care about type
- break;
- }
-
- // check if item is a classid but with hacky fix for items like belt which is a type and classid...sigh
- isClassID = !!NTIPAliasClassID[type] && !NTIPAliasType[type];
- type = isClassID ? NTIPAliasClassID[type] : NTIPAliasType[type];
-
- break;
- case "number":
- if (!Object.values(sdk.items.type).includes(type) && !Object.values(sdk.items).includes(type)) return false;
- // check if item is a classid but with hacky fix for items like belt which is a type and classid...sigh
- isClassID = Object.values(sdk.items).includes(type) && !Object.values(sdk.items.type).includes(type);
-
- break;
- }
-
- // filter out non-matching item types/classids
- if (typeof type === "number") {
- items = items.filter(function (item) {
- return (isClassID ? item.classid === type : item.itemType === type);
- });
- }
-
- for (let i = 0; i < items.length; i++) {
- switch (flag) {
- case "Set":
- case "Unique":
- case "Crafted":
- itemCHECK = !!(items[i].quality === sdk.items.quality[flag]) && (iName ? items[i].fname.toLowerCase().includes(iName) : true);
- break;
- case "Runeword":
- itemCHECK = !!(items[i].isRuneword) && (iName ? items[i].fname.toLowerCase().includes(iName) : true);
- break;
- }
-
- // don't waste time if first condition wasn't met
- if (itemCHECK && typeof type === "number") {
- typeCHECK = isClassID ? items[i].classid === type : items[i].itemType === type;
- }
-
- if (itemCHECK && typeCHECK) {
- return true;
- }
- }
-
- return false;
- },
-
- itemSockables: function (type, quality, iName) {
- quality && typeof quality === "string" && (quality = sdk.items.quality[quality.capitalize(true)]);
- typeof iName === "string" && (iName = iName.toLowerCase());
- let [isClassID, itemCHECK, typeCHECK] = [false, false, false];
-
- switch (typeof type) {
- case "string":
- typeof type === "string" && (type = type.toLowerCase());
- if (!NTIPAliasType[type] && !NTIPAliasClassID[type]) return false;
- isClassID = !!NTIPAliasClassID[type];
- type = isClassID ? NTIPAliasClassID[type] : NTIPAliasType[type];
-
- break;
- case "number":
- if (!Object.values(sdk.items.type).includes(type) && !Object.values(sdk.items).includes(type)) return false;
- isClassID = Object.values(sdk.items).includes(type);
-
- break;
- }
-
- let socketableCHECK = isClassID ? Config.socketables.find(({ classid }) => type === classid) : false;
- let items = me.getItemsEx()
- .filter(function (item) {
- return item.quality === quality && !item.questItem && !item.isRuneword && (isClassID ? item.classid === type : item.itemType === type) && getBaseStat("items", item.classid, "gemsockets") > 0;
- });
-
- for (let i = 0; i < items.length; i++) {
- itemCHECK = !!(items[i].quality === quality) && (iName ? items[i].fname.toLowerCase().includes(iName) : true);
-
- // don't waste time if first condition wasn't met
- itemCHECK && (typeCHECK = isClassID ? items[i].classid === type : items[i].itemType === type);
-
- if (itemCHECK && typeCHECK) {
- if (!socketableCHECK && items[i].getItemsEx().length === 0) {
- return true;
- } else if (socketableCHECK) {
- SoloWants.addToList(items[i]);
-
- return true;
- }
- }
- }
-
- return false;
- },
-
- haveBase: function (type = undefined, sockets = undefined) {
- if (!type || !sockets) return false;
- let isClassID = false;
-
- switch (typeof type) {
- case "string":
- typeof type === "string" && (type = type.toLowerCase());
- if (!NTIPAliasType[type] && !NTIPAliasClassID[type]) return false;
- isClassID = !!NTIPAliasClassID[type];
- type = isClassID ? NTIPAliasClassID[type] : NTIPAliasType[type];
-
- break;
- case "number":
- if (!Object.values(sdk.items.type).includes(type) && !Object.values(sdk.items).includes(type)) return false;
- isClassID = Object.values(sdk.items).includes(type);
-
- break;
- }
-
- let items = me.getItemsEx()
- .filter(item => item.isBaseType && item.isInStorage && (isClassID ? item.classid === type : item.itemType === type));
-
- for (let i = 0; i < items.length; i++) {
- if (items[i].sockets === sockets && (isClassID ? items[i].classid === type : items[i].itemType === type)) {
- return true;
- }
- }
-
- return false;
- },
-
- getMaxValue: function (buildInfo, stat) {
- if (!buildInfo || !buildInfo.stats || stat === undefined) return 0;
- let highest = 0;
- const shorthandStr = [sdk.stats.Strength, "s", "str", "strength"];
- const shorthandDex = [sdk.stats.Dexterity, "d", "dex", "dexterity"];
- const statToCheck = shorthandStr.includes(stat) ? "str" : shorthandDex.includes(stat) ? "dex" : "";
-
- buildInfo.stats.forEach(s => {
- switch (true) {
- case (shorthandStr.includes(s[0]) && statToCheck === "str"):
- case (shorthandDex.includes(s[0]) && statToCheck === "dex"):
- if (typeof s[1] === "number" && s[1] > highest) {
- highest = s[1];
- }
-
- break;
- default:
- break;
- }
- });
-
- return highest;
- },
-
- currentBuild: function () {
- let buildInfo = SetUp.getTemplate();
-
- if (!includeIfNotIncluded(buildInfo.template)) throw new Error("currentBuild(): Failed to include template: " + buildInfo.template);
-
- let final = buildInfo.buildType === SetUp.finalBuild;
-
- return {
- caster: final ? finalBuild.caster : build.caster,
- tabSkills: final ? finalBuild.skillstab : build.skillstab,
- wantedSkills: final ? finalBuild.wantedskills : build.wantedskills,
- usefulSkills: final ? finalBuild.usefulskills : build.usefulskills,
- precastSkills: final ? finalBuild.precastSkills : [],
- usefulStats: final ? (!!finalBuild.usefulStats ? finalBuild.usefulStats : []) : (!!build.usefulStats ? build.usefulStats : []),
- mercDiff: final ? finalBuild.mercDiff : null,
- mercAct: final ? finalBuild.mercAct : null,
- mercAuraWanted: final ? finalBuild.mercAuraWanted : null,
- finalGear: final ? finalBuild.autoEquipTiers : [],
- finalCharms: final ? (finalBuild.charms || {}) : {},
- respec: final ? finalBuild.respec : () => {},
- active: final ? finalBuild.active : build.active,
- };
- },
-
- finalBuild: function () {
- function getBuildTemplate () {
- let build;
- let buildType = SetUp.finalBuild;
-
- if (["Bumper", "Socketmule", "Imbuemule"].includes(buildType)) {
- build = ["Javazon", "Cold", "Bone", "Hammerdin", "Whirlwind", "Wind", "Trapsin"][me.classid] + "Build";
- } else {
- build = buildType + "Build";
- }
-
- return ("SoloPlay/BuildFiles/" + MYCLASSNAME + "/" + MYCLASSNAME + "." + build + ".js").toLowerCase();
- }
-
- let template = getBuildTemplate();
-
- if (!includeIfNotIncluded(template)) {
- let foundError = false;
- let buildType;
-
- // try to see if we can correct the finalBuild
- if (myData.me.finalBuild.match("Build", "gi")) {
- myData.me.finalBuild = myData.me.finalBuild.substring(0, SetUp.finalBuild.length - 5);
- D2Bot.printToConsole("Kolbot-SoloPlay: Info tag contained build which is unecessary. It has been fixed. New InfoTag/finalBuild :: " + SetUp.finalBuild, sdk.colors.D2Bot.Red);
- foundError = true;
- }
-
- if (myData.me.finalBuild.includes(".")) {
- myData.me.finalBuild = myData.me.finalBuild.substring(myData.me.finalBuild.indexOf(".") + 1).capitalize(true);
- D2Bot.printToConsole("Kolbot-SoloPlay: Info tag was incorrect, it contained '.' which is unecessary and means you likely entered something along the lines of Classname.finalBuild. I have attempted to remedy this. If it is still giving you an error please re-read the documentation. New InfoTag/finalBuild :: " + SetUp.finalBuild, sdk.colors.D2Bot.Red);
- foundError = true;
- }
-
- if (myData.me.finalBuild.includes(" ")) {
- myData.me.finalBuild = myData.me.finalBuild.trim().capitalize(true);
- D2Bot.printToConsole("Kolbot-SoloPlay: Info tag was incorrect, it contained a trailing space. I have attempted to remedy this. If it is still giving you an error please re-read the documentation. New InfoTag/finalBuild :: " + SetUp.finalBuild, sdk.colors.D2Bot.Red);
- foundError = true;
- }
-
- if (myData.me.finalBuild.includes("-")) {
- myData.me.finalBuild = myData.me.finalBuild.substring(myData.me.finalBuild.indexOf("-") + 1).capitalize(true);
- D2Bot.printToConsole("Kolbot-SoloPlay: Info tag was incorrect, it contained '-' which is unecessary and means you likely entered something along the lines of Classname-finalBuild. I have attempted to remedy this. If it is still giving you an error please re-read the documentation. New InfoTag/finalBuild :: " + SetUp.finalBuild, sdk.colors.D2Bot.Red);
- foundError = true;
- }
-
- if (foundError) {
- D2Bot.setProfile(null, null, null, null, null, SetUp.finalBuild);
- CharData.updateData("me", "finalBuild", SetUp.finalBuild);
- buildType = myData.me.finalBuild;
- template = ("SoloPlay/BuildFiles/" + sdk.player.class.nameOf(me.classid) + "." + buildType + "Build.js").toLowerCase();
- }
-
- // try-again - if it fails again throw error
- if (!include(template)) {
- console.debug("ÿc8Kolbot-SoloPlayÿc0: Failed to include finalBuild template. Please check that you have actually entered it in correctly. Here is what you currently have: " + SetUp.finalBuild);
- throw new Error("finalBuild(): Failed to include template: " + template);
- }
- }
-
- return {
- caster: finalBuild.caster,
- tabSkills: finalBuild.skillstab,
- wantedSkills: finalBuild.wantedskills,
- usefulSkills: finalBuild.usefulskills,
- precastSkills: finalBuild.precastSkills,
- usefulStats: (!!finalBuild.usefulStats ? finalBuild.usefulStats : []),
- mercDiff: finalBuild.mercDiff,
- mercAct: finalBuild.mercAct,
- mercAuraWanted: finalBuild.mercAuraWanted,
- finalGear: finalBuild.autoEquipTiers,
- finalCharms: (finalBuild.charms || {}),
- maxStr: Check.getMaxValue(finalBuild, "strength"),
- maxDex: Check.getMaxValue(finalBuild, "dexterity"),
- respec: finalBuild.respec,
- active: finalBuild.active,
- };
- },
-
- checkSpecialCase: function () {
- const questCompleted = (id) => !!Misc.checkQuest(id, sdk.quest.states.ReqComplete);
- let goalReached = false, goal = "";
-
- switch (true) {
- case SetUp.finalBuild === "Bumper" && me.charlvl >= 40:
- case (SetUp.finalBuild === "Socketmule" && questCompleted(sdk.quest.id.SiegeOnHarrogath)):
- case (SetUp.finalBuild === "Imbuemule" && questCompleted(sdk.quest.id.ToolsoftheTrade) && me.charlvl >= Developer.imbueStopLevel):
- goal = SetUp.finalBuild;
- goalReached = true;
-
- break;
- case SetUp.stopAtLevel && me.charlvl >= SetUp.stopAtLevel:
- goal = "Level: " + SetUp.stopAtLevel;
- goalReached = true;
-
- break;
- case sdk.difficulty.Difficulties.indexOf(sdk.difficulty.nameOf(me.diff)) < sdk.difficulty.Difficulties.indexOf(myData.me.highestDifficulty):
- // TODO: fill this out, if we go back to normal from hell I want to be able to do whatever it was imbue/socket/respec then return to our orignal difficulty
- // as it is right now if we go back it would take 2 games to get back to hell
- // but this needs a check to ensure that one of the above reasons are why we went back in case we had gone back because low gold in which case we need to stay in the game
- break;
- default:
- break;
- }
-
- if (goalReached) {
- const gameObj = Developer.logPerformance ? Developer.readObj(Tracker.GTPath) : null;
-
- switch (true) {
- case (SetUp.finalBuild === "Bumper" && Developer.fillAccount.bumpers):
- case (SetUp.finalBuild === "Socketmule" && Developer.fillAccount.socketMules):
- SetUp.makeNext();
-
- break;
- default:
- D2Bot.printToConsole("Kolbot-SoloPlay " + goal + " goal reached." + (gameObj ? " (" + (Developer.formatTime(gameObj.Total + Developer.timer(gameObj.LastSave))) + ")" : ""), sdk.colors.D2Bot.Gold);
- Developer.logPerformance && Tracker.update();
- D2Bot.stop();
- }
- }
- },
-
- // TODO: enable this for other items, i.e maybe don't socket tal helm in hell but instead go back and use nightmare so then we can use hell socket on tal armor?
- usePreviousSocketQuest: function () {
- if (me.classic) return;
- if (!Check.resistance().Status) {
- if (me.weaponswitch === 0 && Item.getEquippedItem(sdk.body.LeftArm).fname.includes("Lidless Wall") && !Item.getEquippedItem(sdk.body.LeftArm).socketed) {
- if (!me.normal) {
- if (!myData.normal.socketUsed) goToDifficulty(sdk.difficulty.Normal, " to use socket quest");
- if (me.hell && !myData.nightmare.socketUsed) goToDifficulty(sdk.difficulty.Nightmare, " to use socket quest");
- }
- }
- }
- },
-};
-
-const SoloWants = {
- needList: [],
- validGids: [],
-
- checkItem: function (item) {
- if (!item) return false;
- if (this.validGids.includes(item.gid)) return true;
- let i = 0;
- for (let el of this.needList) {
- if ([sdk.items.type.Jewel, sdk.items.type.Rune].includes(item.itemType) || (item.itemType >= sdk.items.type.Amethyst && item.itemType <= sdk.items.type.Skull)) {
- if (el.needed.includes(item.classid)) {
- this.validGids.push(item.gid);
- this.needList[i].needed.splice(this.needList[i].needed.indexOf(item.classid), 1);
- if (this.needList[i].needed.length === 0) {
- // no more needed items so remove from list
- this.needList.splice(i, 1);
- }
- return true;
- }
- }
- i++; // keep track of index
- }
-
- return false;
- },
-
- keepItem: function (item) {
- if (!item) return false;
- return this.validGids.includes(item.gid);
- },
-
- buildList: function () {
- let myItems = me.getItemsEx()
- .filter(function (item) {
- return !item.isRuneword && !item.questItem && item.quality >= sdk.items.quality.Magic && (item.sockets > 0 || getBaseStat("items", item.classid, "gemsockets") > 0);
- });
- myItems
- .filter(item => item.isEquipped)
- .forEach(item => SoloWants.addToList(item));
- myItems
- .filter(item => item.isInStorage && item.getItemType() && AutoEquip.wanted(item))
- .forEach(item => SoloWants.addToList(item));
-
- return myItems.forEach(item => SoloWants.checkItem(item));
- },
-
- addToList: function (item) {
- if (!item || me.classic || item.isRuneword) return false;
- if (SoloWants.needList.some(check => item.classid === check.classid)) return false;
- let hasWantedItems;
- let list = [];
- let socketedWith = item.getItemsEx();
- let numSockets = item.sockets;
- let curr = Config.socketables.find(({ classid }) => item.classid === classid);
-
- if (curr && curr.socketWith.length > 0) {
- hasWantedItems = socketedWith.some(el => curr.socketWith.includes(el.classid));
- if (hasWantedItems && socketedWith.length === numSockets) {
- return true; // this item is full
- }
-
- if (curr.socketWith.includes(sdk.items.runes.Hel)) {
- let merc = me.getMerc();
- switch (true) {
- case Item.autoEquipCheck(item, true) && me.trueStr >= item.strreq && me.trueDex >= item.dexreq:
- case Item.autoEquipKeepCheckMerc(item) && !!merc && merc.rawStrength >= item.strreq && merc.rawDexterity >= item.dexreq:
- curr.socketWith.splice(curr.socketWith.indexOf(sdk.items.runes.Hel), 1);
- break;
- }
- }
-
- if (curr.socketWith.length > 1 && hasWantedItems) {
- // handle different wanted socketables, if we already have a wanted socketable inserted then remove it from the check list
- socketedWith.forEach(function (socketed) {
- if (curr.socketWith.length > 1 && curr.socketWith.includes(socketed.classid)) {
- curr.socketWith.splice(curr.socketWith.indexOf(socketed.classid), 1);
- }
- });
- }
-
- // add the wanted items to the list
- for (let i = 0; i < numSockets - (hasWantedItems ? socketedWith.length : 0); i++) {
- // handle different wanted socketables
- curr.socketWith.length === numSockets ? list.push(curr.socketWith[i]) : list.push(curr.socketWith[0]);
- }
-
- // currently no sockets but we might use our socket quest on it
- numSockets === 0 && curr.useSocketQuest && list.push(curr.socketWith[0]);
-
- // if temp socketables are used for this item and its not already socketed with wanted items add the temp items too
- if (!hasWantedItems && !!curr.temp && !!curr.temp.length > 0) {
- for (let i = 0; i < numSockets - socketedWith.length; i++) {
- list.push(curr.temp[0]);
- }
- // Make sure we keep a hel rune so we can unsocket temp socketables if needed
- if (!SoloWants.needList.some(check => sdk.items.runes.Hel === check.classid)) {
- let hel = me.getItemsEx(sdk.items.runes.Hel, sdk.items.mode.inStorage);
- // we don't have any hel runes and its not already in our needList
- if ((!hel || hel.length === 0)) {
- SoloWants.needList.push({classid: sdk.items.runes.Hel, needed: [sdk.items.runes.Hel]});
- } else if (!hel.some(check => SoloWants.validGids.includes(check.gid))) {
- SoloWants.needList.push({classid: sdk.items.runes.Hel, needed: [sdk.items.runes.Hel]});
- }
- }
- }
- } else {
- let itemtype = item.getItemType();
- if (!itemtype) return false;
- let gemType = ["Helmet", "Armor"].includes(itemtype) ? "Ruby" : itemtype === "Shield" ? "Diamond" : itemtype === "Weapon" && !Check.currentBuild().caster ? "Skull" : "";
- let runeType;
-
- // Tir rune in normal, Io rune otherwise and Shael's if assassin TODO: use jewels too
- !gemType && (runeType = me.normal ? "Tir" : me.assassin ? "Shael" : "Io");
-
- hasWantedItems = socketedWith.some(el => gemType ? el.itemType === sdk.items.type[gemType] : el.classid === sdk.items.runes[runeType]);
- if (hasWantedItems && socketedWith.length === numSockets) {
- return true; // this item is full
- }
-
- for (let i = 0; i < numSockets - socketedWith.length; i++) {
- list.push(gemType ? sdk.items.gems.Perfect[gemType] : sdk.items.runes[runeType]);
- }
- }
-
- // add to our needList so we pick the items
- return list.length > 0 ? this.needList.push({classid: item.classid, needed: list}) : false;
- },
-
- update: function (item) {
- if (!item) return false;
- if (this.validGids.includes(item.gid)) return true; // already in the list
- let i = 0;
- for (let el of this.needList) {
- if (!me.getItem(el.classid)) {
- // We no longer have the item we wanted socketables for
- this.needList.splice(i, 1);
- continue;
- }
- if ([sdk.items.type.Jewel, sdk.items.type.Rune].includes(item.itemType) || (item.itemType >= sdk.items.type.Amethyst && item.itemType <= sdk.items.type.Skull)) {
- if (el.needed.includes(item.classid)) {
- this.validGids.push(item.gid);
- this.needList[i].needed.splice(this.needList[i].needed.indexOf(item.classid), 1);
- if (this.needList[i].needed.length === 0) {
- // no more needed items so remove from list
- this.needList.splice(i, 1);
- }
- return true;
- }
- }
- i++; // keep track of index
- }
-
- return false;
- },
-
- ensureList: function () {
- let i = 0;
- for (let el of this.needList) {
- if (!me.getItem(el.classid)) {
- // We no longer have the item we wanted socketables for
- this.needList.splice(i, 1);
- continue;
- }
- i++; // keep track of index
- }
- },
-
- // Cube ingredients
- checkSubrecipes: function () {
- for (let el of this.needList) {
- for (let i = 0; i < el.needed.length; i++) {
- switch (true) {
- case [
- sdk.items.gems.Perfect.Ruby, sdk.items.gems.Perfect.Sapphire, sdk.items.gems.Perfect.Topaz, sdk.items.gems.Perfect.Emerald,
- sdk.items.gems.Perfect.Amethyst, sdk.items.gems.Perfect.Diamond, sdk.items.gems.Perfect.Skull].includes(el.needed[i]):
- if (Cubing.subRecipes.indexOf(el.needed[i]) === -1) {
- Cubing.subRecipes.push(el.needed[i]);
- Cubing.recipes.push({
- Ingredients: [el.needed[i] - 1, el.needed[i] - 1, el.needed[i] - 1],
- Index: 0,
- AlwaysEnabled: true,
- MainRecipe: "Crafting"
- });
- }
-
- break;
- case el.needed[i] >= sdk.items.runes.El && el.needed[i] <= sdk.items.runes.Ort:
- if (Cubing.subRecipes.indexOf(el.needed[i]) === -1) {
- Cubing.subRecipes.push(el.needed[i]);
- Cubing.recipes.push({
- Ingredients: [el.needed[i] - 1, el.needed[i] - 1, el.needed[i] - 1],
- Index: Recipe.Rune,
- AlwaysEnabled: true,
- MainRecipe: "Crafting"
- });
- }
-
- break;
- // case el.needed[i] >= sdk.items.runes.Thul && el.needed[i] <= sdk.items.runes.Lem:
- // // gems repeat so should be able to math this out chipped (TASRED) -> repeat flawed (TASRED)
- // if (Cubing.subRecipes.indexOf(el.needed[i]) === -1) {
- // Cubing.subRecipes.push(el.needed[i]);
- // Cubing.recipes.push({
- // Ingredients: [el.needed[i] - 1, el.needed[i] - 1, el.needed[i] - 1],
- // Index: Recipe.Rune,
- // AlwaysEnabled: true,
- // MainRecipe: "Crafting"
- // });
- // }
-
- // break;
- // case el.needed[i] >= sdk.items.runes.Mal && el.needed[i] <= sdk.items.runes.Zod:
- // // gems repeat so should be able to math this out Base (TASRED) -> repeat Flawless (TASRE) (stops at Emerald)
- // if (Cubing.subRecipes.indexOf(el.needed[i]) === -1) {
- // Cubing.subRecipes.push(el.needed[i]);
- // Cubing.recipes.push({
- // Ingredients: [el.needed[i] - 1, el.needed[i] - 1],
- // Index: Recipe.Rune,
- // AlwaysEnabled: true,
- // MainRecipe: "Crafting"
- // });
- // }
-
- // break;
- }
- }
- }
-
- return true;
- },
+ lowGold: false,
+
+ gold: function () {
+ let gold = me.gold;
+ let goldLimit = [25000, 50000, 100000][me.diff];
+
+ if ((me.normal && !me.accessToAct(2)) || gold >= goldLimit) {
+ return true;
+ }
+
+ me.overhead("low gold");
+
+ return false;
+ },
+
+ brokeAf: function (announce = true) {
+ let gold = me.gold;
+ let lowGold = Math.min(Math.floor(500 + (me.charlvl * 100 * Math.sqrt(me.charlvl - 1))), 250000);
+
+ switch (true) {
+ case (me.charlvl < 15):
+ case (me.normal && !me.accessToAct(2)):
+ case (gold >= lowGold):
+ case (me.charlvl >= 15 && gold > Math.floor(lowGold / 2) && gold > me.getRepairCost()):
+ return false;
+ }
+
+ if (announce) {
+ myPrint("very low gold. My Gold: " + gold);
+ NTIP.addLine("[name] == gold # [gold] >= 1");
+ }
+
+ return true;
+ },
+
+ broken: function () {
+ const gold = me.gold;
+ const rightArm = me.equipped.get(sdk.body.RightArm);
+ const leftArm = me.equipped.get(sdk.body.LeftArm);
+
+ // Almost broken but not quite
+ if (((rightArm.durability <= 30 && rightArm.durability > 0)
+ || (leftArm.durability <= 30 && leftArm.durability > 0)
+ && !me.getMerc() && me.charlvl >= 15 && !me.normal && !me.nightmare && gold < 1000)) {
+ return 1;
+ }
+
+ // Broken
+ if ((rightArm.durability === 0 || leftArm.durability === 0)
+ && me.charlvl >= 15 && !me.normal && gold < 1000) {
+ return 2;
+ }
+
+ return 0;
+ },
+
+ brokeCheck: function () {
+ Town.doChores();
+
+ let myGold = me.gold;
+ let repairCost = me.getRepairCost();
+ let items = (me.getItemsForRepair(100, false) || []);
+ let meleeChar = !Check.currentBuild().caster;
+ let msg = "";
+ let diff = -1;
+
+ switch (true) {
+ case myGold > repairCost:
+ return false;
+ case me.normal:
+ case !meleeChar && me.nightmare:
+ Check.lowGold = myGold < repairCost;
+ return false;
+ case meleeChar && !me.normal:
+ // check how broke we are - only for melee chars since casters don't care about weapons
+ let wep = me.equipped.get(sdk.body.RightArm);
+ if (!!wep && meleeChar && wep.durabilityPercent === 0) {
+ // we are really broke - go back to normal
+ msg = " We are broken - lets get some easy gold in normal.";
+ diff = sdk.difficulty.Normal;
+ }
+
+ break;
+ case !meleeChar && me.hell:
+ msg = " We are pretty broke, lets run some easy stuff in nightmare for gold";
+ diff = sdk.difficulty.Nightmare;
+
+ break;
+ }
+
+ if (diff > -1) {
+ console.debug("My gold: " + myGold + ", Repair cost: " + repairCost);
+ goToDifficulty(diff, msg + (" My gold: " + myGold + ", Repair cost: " + repairCost));
+
+ return true;
+ }
+
+ return false;
+ },
+
+ resistance: function () {
+ let resPenalty = me.getResPenalty(me.diff + 1);
+ let [frRes, lrRes, crRes, prRes] = [
+ (me.realFR - resPenalty),
+ (me.realLR - resPenalty),
+ (me.realCR - resPenalty),
+ (me.realPR - resPenalty)
+ ];
+
+ return {
+ Status: ((frRes > 0) && (lrRes > 0) && (crRes > 0)),
+ FR: frRes,
+ CR: crRes,
+ LR: lrRes,
+ PR: prRes,
+ };
+ },
+
+ nextDifficulty: function (announce = true) {
+ let currDiff = me.diff;
+ if (currDiff === sdk.difficulty.Hell) return false;
+ if (["Bumper", "Socketmule"].includes(SetUp.finalBuild)) return false;
+ if (me.charlvl < CharInfo.levelCap) return false;
+ if (!me.diffCompleted) return false;
+ let nextDiff = null;
+ let res = this.resistance();
+ let lvlReq = !!(!this.broken());
+ let [str, color] = ["", sdk.colors.D2Bot.Black];
+
+ if (lvlReq) {
+ if (res.Status) {
+ nextDiff = currDiff + 1;
+ [str, color] = ["next difficulty requirements met. Starting: " + sdk.difficulty.nameOf(nextDiff), sdk.colors.D2Bot.Blue];
+ } else {
+ if (me.charlvl >= CharInfo.levelCap + (!me.normal ? 5 : 2)) {
+ nextDiff = currDiff + 1;
+ str = "Over leveled. Starting: " + sdk.difficulty.nameOf(nextDiff);
+ } else {
+ announce && myPrint(
+ sdk.difficulty.nameOf(currDiff + 1)
+ + " requirements not met. Negative resistance. FR: " + res.FR + " | CR: " + res.CR + " | LR: " + res.LR
+ );
+ }
+ }
+ }
+
+ if (!nextDiff) return false;
+ if (announce && str) {
+ D2Bot.printToConsole("Kolbot-SoloPlay: " + str, color);
+ }
+
+ return sdk.difficulty.nameOf(nextDiff);
+ },
+
+ runes: function () {
+ if (me.classic) return false;
+ let needRunes = true;
+
+ switch (me.diff) {
+ case sdk.difficulty.Normal:
+ // Have runes or stealth and ancients pledge
+ if (me.haveRunes([sdk.items.runes.Tal, sdk.items.runes.Eth])
+ || me.checkItem({ name: sdk.locale.items.Stealth }).have) {
+ needRunes = false;
+ }
+
+ break;
+ case sdk.difficulty.Nightmare:
+ if ((me.haveRunes([sdk.items.runes.Tal, sdk.items.runes.Thul, sdk.items.runes.Ort, sdk.items.runes.Amn])
+ && Check.currentBuild().caster)
+ || (!me.paladin && me.checkItem({ name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Sword }).have)
+ || (me.paladin && me.haveAll([
+ {
+ name: sdk.locale.items.Spirit,
+ itemtype: sdk.items.type.Sword
+ },
+ {
+ name: sdk.locale.items.Spirit,
+ itemtype: sdk.items.type.AuricShields
+ }
+ ]))
+ || (me.necromancer && me.checkItem({ name: sdk.locale.items.White }).have
+ && (
+ me.checkItem({ name: sdk.locale.items.Rhyme, itemtype: sdk.items.type.VoodooHeads }).have
+ || me.equipped.get(sdk.body.LeftArm).tier > 800
+ ))
+ || (me.barbarian && (me.checkItem({ name: sdk.locale.items.Lawbringer }).have || me.baal))) {
+ needRunes = false;
+ }
+
+ break;
+ case sdk.difficulty.Hell:
+ if (!me.baal || (me.sorceress && !["Blova", "Lightning"].includes(SetUp.currentBuild))) {
+ needRunes = false;
+ }
+
+ break;
+ }
+
+ return needRunes;
+ },
+
+ /**
+ * @deprecated Use me.checkItem() instead
+ * @param {number | string} type
+ * @param {string} [flag]
+ * @param {string} [iName]
+ * @returns
+ */
+ haveItem: function (type, flag, iName) {
+ let [isClassID, itemCHECK, typeCHECK] = [false, false, false];
+
+ flag && typeof flag === "string" && (flag = flag.capitalize(true));
+ typeof iName === "string" && (iName = iName.toLowerCase());
+
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return !item.questItem && (flag === "Runeword" ? item.isRuneword : item.quality === sdk.items.quality[flag]);
+ });
+
+ switch (typeof type) {
+ case "string":
+ typeof type === "string" && (type = type.toLowerCase());
+ if (type !== "dontcare" && !NTIPAliasType[type] && !NTIPAliasClassID[type]) return false;
+ if (type === "dontcare") {
+ typeCHECK = true; // we don't care about type
+ break;
+ }
+
+ // check if item is a classid but with hacky fix for items like belt which is a type and classid...sigh
+ isClassID = !!NTIPAliasClassID[type] && !NTIPAliasType[type];
+ type = isClassID ? NTIPAliasClassID[type] : NTIPAliasType[type];
+
+ break;
+ case "number":
+ if (!Object.values(sdk.items.type).includes(type) && !Object.values(sdk.items).includes(type)) return false;
+ // check if item is a classid but with hacky fix for items like belt which is a type and classid...sigh
+ isClassID = Object.values(sdk.items).includes(type) && !Object.values(sdk.items.type).includes(type);
+
+ break;
+ }
+
+ // filter out non-matching item types/classids
+ if (typeof type === "number") {
+ items = items.filter(function (item) {
+ return (isClassID ? item.classid === type : item.itemType === type);
+ });
+ }
+
+ const quality = (flag === "Set" || flag === "Unique" || flag === "Crafted")
+ ? sdk.items.quality[flag]
+ : undefined;
+
+ for (let item of items) {
+ switch (flag) {
+ case "Set":
+ case "Unique":
+ case "Crafted":
+ itemCHECK = !!(item.quality === quality) && (iName ? item.fname.toLowerCase().includes(iName) : true);
+ break;
+ case "Runeword":
+ itemCHECK = !!(item.isRuneword) && (iName ? item.fname.toLowerCase().includes(iName) : true);
+ break;
+ }
+
+ // don't waste time if first condition wasn't met
+ if (itemCHECK && typeof type === "number") {
+ typeCHECK = isClassID ? item.classid === type : item.itemType === type;
+ }
+
+ if (itemCHECK && typeCHECK) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ itemSockables: function (type, quality, iName) {
+ quality && typeof quality === "string" && (quality = sdk.items.quality[quality.capitalize(true)]);
+ typeof iName === "string" && (iName = iName.toLowerCase());
+ let [isClassID, itemCHECK, typeCHECK] = [false, false, false];
+
+ switch (typeof type) {
+ case "string":
+ typeof type === "string" && (type = type.toLowerCase());
+ if (!NTIPAliasType[type] && !NTIPAliasClassID[type]) return false;
+ isClassID = !!NTIPAliasClassID[type];
+ type = isClassID ? NTIPAliasClassID[type] : NTIPAliasType[type];
+
+ break;
+ case "number":
+ if (!Object.values(sdk.items.type).includes(type) && !Object.values(sdk.items).includes(type)) return false;
+ isClassID = Object.values(sdk.items).includes(type);
+
+ break;
+ }
+
+ let socketableCHECK = isClassID ? Config.socketables.find(({ classid }) => type === classid) : false;
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item.quality === quality && !item.questItem && !item.isRuneword
+ && (isClassID ? item.classid === type : item.itemType === type)
+ && getBaseStat("items", item.classid, "gemsockets") > 0;
+ });
+
+ for (let item of items) {
+ itemCHECK = !!(item.quality === quality) && (iName ? item.fname.toLowerCase().includes(iName) : true);
+
+ // don't waste time if first condition wasn't met
+ itemCHECK && (typeCHECK = isClassID ? item.classid === type : item.itemType === type);
+
+ if (itemCHECK && typeCHECK) {
+ if (!socketableCHECK && item.getItemsEx().length === 0) {
+ return true;
+ } else if (socketableCHECK) {
+ SoloWants.addToList(item);
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ getMaxValue: function (buildInfo, stat) {
+ if (!buildInfo || !buildInfo.stats || stat === undefined) return 0;
+ let highest = 0;
+ const shorthandStr = [sdk.stats.Strength, "s", "str", "strength"];
+ const shorthandDex = [sdk.stats.Dexterity, "d", "dex", "dexterity"];
+ const statToCheck = shorthandStr.includes(stat) ? "str" : shorthandDex.includes(stat) ? "dex" : "";
+
+ buildInfo.stats.forEach(function (s) {
+ switch (true) {
+ case (shorthandStr.includes(s[0]) && statToCheck === "str"):
+ case (shorthandDex.includes(s[0]) && statToCheck === "dex"):
+ if (typeof s[1] === "number" && s[1] > highest) {
+ highest = s[1];
+ }
+
+ break;
+ default:
+ break;
+ }
+ });
+
+ return highest;
+ },
+
+ // repetitive code - FIX THIS
+ currentBuild: function () {
+ let build = me.currentBuild;
+
+ if (!build) throw new Error("currentBuild(): Failed to include template: " + SetUp._buildTemplate);
+
+ return {
+ caster: build.caster,
+ tabSkills: build.skillstab,
+ wantedSkills: build.wantedskills,
+ usefulSkills: build.usefulskills,
+ precastSkills: build.hasOwnProperty("precastSkills") ? build.precastSkills : [],
+ usefulStats: build.hasOwnProperty("usefulStats") ? build.usefulStats : [],
+ wantedMerc: build.hasOwnProperty("wantedMerc") ? build.wantedMerc : null,
+ finalCharms: build.hasOwnProperty("charms") ? (build.charms || {}) : {},
+ maxStr: Check.getMaxValue(build, "strength"),
+ maxDex: Check.getMaxValue(build, "dexterity"),
+ respec: build.hasOwnProperty("respec") ? build.respec : () => {},
+ active: build.active,
+ };
+ },
+
+ // repetitive code - FIX THIS
+ finalBuild: function () {
+ let finalBuild = me.finalBuild;
+
+ if (!finalBuild) throw new Error("finalBuild(): Failed to include template: " + SetUp._buildTemplate);
+
+ return {
+ caster: finalBuild.caster,
+ tabSkills: finalBuild.skillstab,
+ wantedSkills: finalBuild.wantedskills,
+ usefulSkills: finalBuild.usefulskills,
+ precastSkills: finalBuild.precastSkills,
+ usefulStats: (!!finalBuild.usefulStats ? finalBuild.usefulStats : []),
+ wantedMerc: finalBuild.wantedMerc,
+ finalCharms: (finalBuild.charms || {}),
+ maxStr: Check.getMaxValue(finalBuild, "strength"),
+ maxDex: Check.getMaxValue(finalBuild, "dexterity"),
+ respec: finalBuild.respec,
+ active: finalBuild.active,
+ };
+ },
+
+ checkSpecialCase: function () {
+ const questCompleted = (id) => !!Misc.checkQuest(id, sdk.quest.states.ReqComplete);
+ let goalReached = false, goal = "";
+
+ switch (true) {
+ case SetUp.finalBuild === "Bumper" && me.charlvl >= 40:
+ case (SetUp.finalBuild === "Socketmule" && questCompleted(sdk.quest.id.SiegeOnHarrogath)):
+ case (SetUp.finalBuild === "Imbuemule" && questCompleted(sdk.quest.id.ToolsoftheTrade) && me.charlvl >= Developer.imbueStopLevel):
+ goal = SetUp.finalBuild;
+ goalReached = true;
+
+ break;
+ case SetUp.stopAtLevel && me.charlvl >= SetUp.stopAtLevel:
+ goal = "Level: " + SetUp.stopAtLevel;
+ goalReached = true;
+
+ break;
+ case sdk.difficulty.Difficulties.indexOf(sdk.difficulty.nameOf(me.diff)) < sdk.difficulty.Difficulties.indexOf(me.data.highestDifficulty):
+ // TODO: fill this out, if we go back to normal from hell I want to be able to do whatever it was imbue/socket/respec then return to our orignal difficulty
+ // as it is right now if we go back it would take 2 games to get back to hell
+ // but this needs a check to ensure that one of the above reasons are why we went back in case we had gone back because low gold in which case we need to stay in the game
+ break;
+ default:
+ break;
+ }
+
+ if (goalReached) {
+ const gameObj = Developer.logPerformance ? Tracker.readObj(Tracker.GTPath) : null;
+
+ switch (true) {
+ case (SetUp.finalBuild === "Bumper" && Developer.fillAccount.bumpers):
+ case (SetUp.finalBuild === "Socketmule" && Developer.fillAccount.socketMules):
+ case (SetUp.finalBuild === "Imbuemule" && Developer.fillAccount.imbueMule):
+ SetUp.makeNext();
+
+ break;
+ default:
+ D2Bot.printToConsole("Kolbot-SoloPlay " + goal + " goal reached." + (gameObj ? " (" + (Time.format(gameObj.Total + Time.elapsed(gameObj.LastSave))) + ")" : ""), sdk.colors.D2Bot.Gold);
+ Developer.logPerformance && Tracker.update();
+ D2Bot.stop();
+ }
+ }
+ },
+
+ // TODO: enable this for other items, i.e maybe don't socket tal helm in hell but instead go back and use nightmare so then we can use hell socket on tal armor?
+ usePreviousSocketQuest: function () {
+ if (me.classic) return;
+ if (!Check.resistance().Status) {
+ if (me.weaponswitch === 0
+ && me.equipped.get(sdk.body.LeftArm).fname.includes("Lidless Wall")
+ && !me.equipped.get(sdk.body.LeftArm).socketed) {
+ if (!me.normal) {
+ if (!me.data.normal.socketUsed) goToDifficulty(sdk.difficulty.Normal, " to use socket quest");
+ if (me.hell && !me.data.nightmare.socketUsed) goToDifficulty(sdk.difficulty.Nightmare, " to use socket quest");
+ }
+ }
+ }
+ },
};
diff --git a/libs/SoloPlay/Functions/ItemOverrides.js b/libs/SoloPlay/Functions/ItemOverrides.js
index abb0bdc2..d3ad40b0 100644
--- a/libs/SoloPlay/Functions/ItemOverrides.js
+++ b/libs/SoloPlay/Functions/ItemOverrides.js
@@ -6,160 +6,104 @@
*
*/
-includeIfNotIncluded("common/Misc.js");
-includeIfNotIncluded("common/Item.js");
-includeIfNotIncluded("SoloPlay/Functions/PrototypeOverrides.js");
+includeIfNotIncluded("core/Item.js");
includeIfNotIncluded("SoloPlay/Functions/ItemPrototypes.js");
Item.weaponTypes = [
- 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.Orb, sdk.items.type.AmazonBow,
- sdk.items.type.AmazonSpear, sdk.items.type.AmazonJavelin, sdk.items.type.MissilePotion
+ 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.Orb, sdk.items.type.AmazonBow,
+ sdk.items.type.AmazonSpear, sdk.items.type.AmazonJavelin, sdk.items.type.MissilePotion
];
Item.shieldTypes = [
- sdk.items.type.Shield, sdk.items.type.AuricShields, sdk.items.type.VoodooHeads, sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver
+ sdk.items.type.Shield, sdk.items.type.AuricShields,
+ sdk.items.type.VoodooHeads, sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver
];
Item.helmTypes = [
- sdk.items.type.Helm, sdk.items.type.PrimalHelm, sdk.items.type.Circlet, sdk.items.type.Pelt
+ sdk.items.type.Helm, sdk.items.type.PrimalHelm, sdk.items.type.Circlet, sdk.items.type.Pelt
];
-/**
- * @param {ItemUnit} item
- * @param {boolean} [skipSameItem]
- */
-Item.getQuantityOwned = function (item = undefined, skipSameItem = false) {
- if (!item) return 0;
-
- return me.getItemsEx()
- .filter(check =>
- check.itemType === item.itemType
- && (!skipSameItem || check.gid !== item.gid)
- && check.classid === item.classid
- && check.quality === item.quality
- && check.sockets === item.sockets
- && check.isInStorage
- ).length;
-};
-
/**
* @param {ItemUnit} item
*/
Item.hasDependancy = function (item) {
- switch (item.itemType) {
- case sdk.items.type.Bow:
- case sdk.items.type.AmazonBow:
- return sdk.items.Arrows;
- case sdk.items.type.Crossbow:
- return sdk.items.Bolts;
- default:
- return false;
- }
+ switch (item.itemType) {
+ case sdk.items.type.Bow:
+ case sdk.items.type.AmazonBow:
+ return sdk.items.Arrows;
+ case sdk.items.type.Crossbow:
+ return sdk.items.Bolts;
+ default:
+ return false;
+ }
};
/**
* @param {ItemUnit} item
*/
Item.identify = function (item) {
- if (item.identified) return true;
- let idTool = me.getIdTool();
-
- if (idTool) {
- item.isInStash && Town.openStash();
- return Town.identifyItem(item, idTool);
- }
- return false;
+ if (item.identified) return true;
+ let idTool = me.getIdTool();
+
+ if (idTool) {
+ item.isInStash && Town.openStash();
+ return Packet.identifyItem(item, idTool);
+ }
+ return false;
};
/**
* @param {ItemUnit} item
*/
Item.getBodyLoc = function (item) {
- let bodyLoc = (() => {
- switch (true) {
- case Item.shieldTypes.includes(item.itemType):
- return sdk.body.LeftArm;
- case item.itemType === sdk.items.type.Armor:
- return sdk.body.Armor;
- case item.itemType === sdk.items.type.Ring:
- return [sdk.body.RingRight, sdk.body.RingLeft];
- case item.itemType === sdk.items.type.Amulet:
- return sdk.body.Neck;
- case item.itemType === sdk.items.type.Boots:
- return sdk.body.Feet;
- case item.itemType === sdk.items.type.Gloves:
- return sdk.body.Gloves;
- case item.itemType === sdk.items.type.Belt:
- return sdk.body.Belt;
- case Item.helmTypes.includes(item.itemType):
- return sdk.body.Head;
- case Item.weaponTypes.includes(item.itemType):
- return me.barbarian && item.twoHanded && !item.strictlyTwoHanded
- ? [sdk.body.RightArm, sdk.body.LeftArm]
- : sdk.body.RightArm;
- case [sdk.items.type.HandtoHand, sdk.items.type.AssassinClaw].includes(item.itemType):
- return !Check.currentBuild().caster && me.assassin ? [sdk.body.RightArm, sdk.body.LeftArm] : sdk.body.RightArm;
- default:
- return false;
- }
- })();
-
- return Array.isArray(bodyLoc) ? bodyLoc : [bodyLoc];
-};
-
-// todo: clean this up
-Item.getEquippedItem = function (bodyLoc = -1) {
- let item = me.getItemsEx().filter((item) => item.isEquipped && item.bodylocation === bodyLoc).first();
-
- if (item) {
- return {
- classid: item.classid,
- name: item.name,
- fname: item.fname,
- prefixnum: item.prefixnum,
- itemType: item.itemType,
- quality: item.quality,
- tier: NTIP.GetTier(item),
- tierScore: tierscore(item, bodyLoc),
- secondarytier: NTIP.GetSecondaryTier(item),
- str: item.getStatEx(sdk.stats.Strength),
- dex: item.getStatEx(sdk.stats.Dexterity),
- durability: item.durabilityPercent,
- sockets: item.sockets,
- socketed: item.getItemsEx().length > 0,
- isRuneword: item.runeword,
- twoHanded: item.twoHanded,
- finalItem: NTIP.GetTier(item) >= NTIP.MAX_TIER,
- };
- }
-
- // Don't have anything equipped in there
- return {
- classid: -1,
- name: "none",
- fname: "none",
- prefixnum: -1,
- itemType: -1,
- quality: -1,
- tier: -1,
- tierScore: -1,
- secondarytier: -1,
- str: 0,
- dex: 0,
- durability: 0,
- sockets: 0,
- socketed: false,
- isRuneword: false,
- twoHanded: false,
- };
+ if (!item || item.isInsertable) return [];
+ if (Item.shieldTypes.includes(item.itemType)) return [sdk.body.LeftArm];
+ if (Item.helmTypes.includes(item.itemType)) return [sdk.body.Head];
+ if (Item.weaponTypes.includes(item.itemType)) {
+ return me.barbarian && (!item.twoHanded || (item.twoHanded && !item.strictlyTwoHanded))
+ ? [sdk.body.RightArm, sdk.body.LeftArm]
+ : [sdk.body.RightArm];
+ }
+
+ switch (item.itemType) {
+ case sdk.items.type.Armor:
+ return [sdk.body.Armor];
+ case sdk.items.type.Ring:
+ return [sdk.body.RingRight, sdk.body.RingLeft];
+ case sdk.items.type.Amulet:
+ return [sdk.body.Neck];
+ case sdk.items.type.Boots:
+ return [sdk.body.Feet];
+ case sdk.items.type.Gloves:
+ return [sdk.body.Gloves];
+ case sdk.items.type.Belt:
+ return [sdk.body.Belt];
+ case sdk.items.type.AssassinClaw:
+ case sdk.items.type.HandtoHand:
+ return !Check.currentBuild().caster && me.assassin
+ ? [sdk.body.RightArm, sdk.body.LeftArm] : [sdk.body.RightArm];
+ }
+
+ return [];
};
/**
* @param {ItemUnit} item
*/
Item.canEquip = function (item) {
- if (!item || item.type !== sdk.unittype.Item || !item.identified) return false;
- return me.charlvl >= item.getStat(sdk.stats.LevelReq) && me.trueStr >= item.strreq && me.trueDex >= item.dexreq;
+ if (!item || item.type !== sdk.unittype.Item || !item.identified) return false;
+ return (
+ me.charlvl >= item.lvlreq
+ && me.trueStr >= item.strreq
+ && me.trueDex >= item.dexreq
+ && me.classid === item.charclass
+ );
};
/**
@@ -167,171 +111,236 @@ Item.canEquip = function (item) {
* @param {boolean} basicCheck
*/
Item.autoEquipCheck = function (item, basicCheck = false) {
- 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) {
- let equippedItem = this.getEquippedItem(bodyLoc[i]);
-
- // rings are special
- // first check if its a final item - can't use tierscore value as it doesn't count the bloated value
- if (item.isInStorage && item.itemType === sdk.items.type.Ring
- && ((tier < NTIP.MAX_TIER && !equippedItem.finalItem) || (equippedItem.finalItem && tier >= NTIP.MAX_TIER))) {
- // have to pass in the specific location
- tier = tierscore(item, bodyLoc[i]);
-
- if (tier > equippedItem.tierScore) {
- return true;
- }
- } else if (tier > equippedItem.tier && (!basicCheck ? this.canEquip(item) || !item.identified : true)) {
- if (item.twoHanded && !me.barbarian) {
- if (tier < this.getEquippedItem(sdk.body.RightArm).tier + this.getEquippedItem(sdk.body.LeftArm).tier) return false;
- }
-
- if (!me.barbarian && bodyLoc[i] === sdk.body.LeftArm && this.getEquippedItem(bodyLoc[i]).tier === -1) {
- if (this.getEquippedItem(sdk.body.RightArm).twoHanded && tier < this.getEquippedItem(sdk.body.RightArm).tier) return false;
- }
-
- return true;
- }
- }
- }
-
- return false;
+ if (!Config.AutoEquip) return true;
+
+ let tier = NTIP.GetTier(item);
+ if (tier <= 0) return false;
+ let bodyLoc = this.getBodyLoc(item);
+
+ for (let loc of bodyLoc) {
+ const equippedItem = me.equipped.get(loc);
+
+ // rings are special
+ if (item.isInStorage && item.itemType === sdk.items.type.Ring) {
+ // have to pass in the specific location
+ tier = tierscore(item, 1, loc);
+
+ if (tier > equippedItem.tierScore) {
+ return true;
+ }
+ } else if (tier > equippedItem.tier && (basicCheck ? true : this.canEquip(item) || !item.identified)) {
+ if (Item.canEquip(item)) {
+ if (item.twoHanded && !me.barbarian) {
+ if (tier < me.equipped.get(sdk.body.RightArm).tier + me.equipped.get(sdk.body.LeftArm).tier) {
+ return false;
+ }
+ }
+
+ if (!me.barbarian && loc === sdk.body.LeftArm && me.equipped.get(loc).tier === -1) {
+ if (me.equipped.get(sdk.body.RightArm).twoHanded
+ && tier < me.equipped.get(sdk.body.RightArm).tier) {
+ return false;
+ }
+ }
+
+ return true;
+ } else {
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ const checkForBetterItem = function (item) {
+ let betterItem = me.getItemsEx()
+ .filter(function (el) {
+ return el.isInStorage
+ && el.gid !== item.gid
+ && el.identified
+ && Item.getBodyLoc(el).includes(loc);
+ })
+ .sort(function (a, b) {
+ return NTIP.GetTier(b) - NTIP.GetTier(a);
+ })
+ .find(el => NTIP.GetTier(el) > tier);
+ return !!betterItem;
+ };
+ // keep wanted final gear items
+ if (NTIP.CheckItem(item, NTIP.FinalGear) === Pickit.Result.WANTED) {
+ // don't horde items we can't equip
+ return !checkForBetterItem(item);
+ }
+
+ let [lvlReq, strReq, dexReq] = [item.lvlreq, item.strreq, item.dexreq];
+
+ // todo - bit hacky, better way would be to track what stats are going to be allocated next
+ if ((lvlReq - me.charlvl > 5) || (strReq - me.trueStr > 10) || (dexReq - me.trueDex > 10)) {
+ return false;
+ }
+
+ // if we can't equip it, but it's a good item, keep it as long as we have space for it
+ // lets double check that this is the highest tied'd item of this type in our storage
+ let betterItem = checkForBetterItem(item);
+ if (!betterItem) return true;
+
+ return Storage.Stash.CanFit(item) && Storage.Stash.UsedSpacePercent() < 65;
+ }
+ }
+ }
+
+ return false;
};
/**
* @param {string} task
*/
Item.autoEquip = function (task = "") {
- if (!Config.AutoEquip) return true;
- task = task + "AutoEquip";
- console.log("ÿc8Kolbot-SoloPlayÿc0: Entering " + task);
-
- const noStash = (!me.inTown || task !== "AutoEquip");
- let tick = getTickCount();
- let items = me.getItemsEx()
- .filter(function (item) {
- if (!item.isInStorage) return false;
- if (noStash && !item.isInInventory) return false;
- let tier = NTIP.GetTier(item);
- return (item.identified ? tier > 0 : tier !== 0);
- });
- // couldn't find my items
- if (!items.length) return false;
-
- me.switchWeapons(sdk.player.slot.Main);
-
- const sortEq = (a, b) => {
- if (Item.canEquip(a)) return -1;
- if (Item.canEquip(b)) return 1;
- return 0;
- };
-
- /**
- * @param {ItemUnit} item
- * @param {number} bodyLoc
- * @param {number} tier
- */
- const runEquip = (item, bodyLoc, tier) => {
- let gid = item.gid;
- let prettyName = item.prettyPrint;
- console.debug(prettyName + " tier: " + tier);
-
- if (this.equip(item, bodyLoc)) {
- console.log("ÿc9" + task + "ÿc0 :: Equipped: " + prettyName + " Tier: " + tier);
- // item that can have sockets
- if (item.getItemType()) {
- SoloWants.addToList(item);
- SoloWants.ensureList();
- }
- Developer.debugging.autoEquip && Misc.logItem(task, me.getItem(-1, -1, gid));
- Developer.logEquipped && MuleLogger.logEquippedItems();
- } else if (!noStash && item.lvlreq > me.charlvl && !item.isInStash) {
- if (Storage.Stash.CanFit(item)) {
- console.log("ÿc9" + task + "ÿc0 :: Item level is to high, attempting to stash for now as its better than what I currently have: " + prettyName + " Tier: " + tier);
- Storage.Stash.MoveTo(item);
- }
- } else if (me.getItem(-1, -1, gid)) {
- // Make sure we didn't lose it during roll back
- return false;
- }
-
- return true;
- };
-
- // stash'd unid check
- let unids = items.filter(item => !item.identified && item.isInStash);
- if (unids.length && Town.fillTome(sdk.items.TomeofIdentify, true)) {
- unids.forEach(item => Item.identify(item));
- }
-
- // ring check - sometimes a higher tier ring ends up on the wrong finger causing a rollback loop
- if (this.getEquippedItem(sdk.body.RingLeft).tierScore > this.getEquippedItem(sdk.body.RingRight).tierScore) {
- console.log("ÿc9" + task + "ÿc0 :: Swapping rings, higher tier ring is on the wrong finger");
- clickItemAndWait(sdk.clicktypes.click.item.Left, sdk.body.RingLeft);
- delay(200);
- me.itemoncursor && clickItemAndWait(sdk.clicktypes.click.item.Left, sdk.body.RingRight);
- delay(200);
- me.itemoncursor && clickItemAndWait(sdk.clicktypes.click.item.Left, sdk.body.RingLeft);
- }
-
- !getUIFlag(sdk.uiflags.Shop) && me.cancel();
-
- while (items.length > 0) {
- items.sort(sortEq);
- const item = items.shift();
- let tier = NTIP.GetTier(item);
- let bodyLoc = this.getBodyLoc(item);
-
- if (tier > 0 && bodyLoc) {
- for (let j = 0; j < bodyLoc.length; j += 1) {
- const equippedItem = this.getEquippedItem(bodyLoc[j]);
- // rings are special
- if (item.isInStorage && item.itemType === sdk.items.type.Ring && (tier < NTIP.MAX_TIER || (equippedItem.finalItem && tier >= NTIP.MAX_TIER))) {
- Item.identify(item);
- // have to pass in the specific location
- tier = tierscore(item, bodyLoc[j]);
-
- if (tier > equippedItem.tierScore) {
- if (!runEquip(item, bodyLoc[j], tier)) {
- continue;
- }
- }
- } else {
- if (item.isInStorage && tier > equippedItem.tier && equippedItem.classid !== sdk.items.quest.KhalimsWill) {
- Item.identify(item);
-
- if (item.twoHanded && !me.barbarian) {
- if (tier < this.getEquippedItem(sdk.body.RightArm).tier + this.getEquippedItem(sdk.body.LeftArm).tier) {
- continue;
- }
- console.log("ÿc9" + task + "ÿc0 :: TwoHandedWep better than sum tier of currently equipped main + shield hand : " + item.fname + " Tier: " + tier);
- }
-
- if (!me.barbarian && bodyLoc[j] === sdk.body.LeftArm && equippedItem.tier === -1 && this.getEquippedItem(sdk.body.RightArm).twoHanded) {
- if (tier < this.getEquippedItem(sdk.body.RightArm).tier) {
- continue;
- }
- console.log("ÿc9" + task + "ÿc0 :: TwoHandedWep not as good as what we want to equip on our shield hand : " + item.fname + " Tier: " + tier);
- }
-
- if (!runEquip(item, bodyLoc[j], tier)) {
- continue;
- }
-
- break;
- }
- }
- }
- }
- }
-
- console.log("ÿc8Kolbot-SoloPlayÿc0: Exiting ÿc9" + task + "ÿc0. Time elapsed: " + Developer.formatTime(getTickCount() - tick));
- return true;
+ if (!Config.AutoEquip) return true;
+ task = task + "AutoEquip";
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Entering " + task);
+
+ const noStash = (!me.inTown || task !== "AutoEquip");
+ let tick = getTickCount();
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ if (!item.isInStorage) return false;
+ if (noStash && !item.isInInventory) return false;
+ let tier = NTIP.GetTier(item);
+ return (item.identified ? tier > 0 : tier !== 0);
+ });
+ // couldn't find my items
+ if (!items.length) return false;
+
+ me.switchWeapons(sdk.player.slot.Main);
+
+ const sortEq = function (a, b) {
+ let [prioA, prioB] = [Item.canEquip(a), Item.canEquip(b)];
+ if (prioA && prioB) return NTIP.GetTier(b) - NTIP.GetTier(a);
+ if (prioA) return -1;
+ if (prioB) return 1;
+ return 0;
+ };
+
+ /**
+ * @param {ItemUnit} item
+ * @param {number} bodyLoc
+ * @param {number} tier
+ */
+ const runEquip = function (item, bodyLoc, tier) {
+ let gid = item.gid;
+ let prettyName = item.prettyPrint;
+ console.debug(prettyName + " tier: " + tier);
+
+ if (Item.equip(item, bodyLoc)) {
+ console.log(
+ "ÿc9" + task + "ÿc0 :: \n"
+ + "ÿc9 - Equippedÿc0: " + prettyName + "\n"
+ + "ÿc9 - Tierÿc0: " + tier
+ );
+ // item that can have sockets
+ if (item.getItemType()) {
+ SoloWants.addToList(item);
+ SoloWants.ensureList();
+ }
+ Developer.debugging.autoEquip && Item.logItem(task, me.getItem(-1, -1, gid));
+ Developer.logEquipped && MuleLogger.logEquippedItems();
+ me.equipped.set(bodyLoc, item);
+ } else if (!noStash && item.lvlreq > me.charlvl && !item.isInStash) {
+ if (Storage.Stash.CanFit(item)) {
+ console.log(
+ "ÿc9" + task + "ÿc0 :: \n"
+ + "- " + prettyName + " Item req is too high (" + item.lvlreq + ") for my level (" + me.charlvl + ") \n"
+ + "- Stash for now as its better than what I currently have. Tier: " + tier
+ );
+ Storage.Stash.MoveTo(item);
+ }
+ } else if (me.getItem(-1, -1, gid)) {
+ // Make sure we didn't lose it during roll back
+ return false;
+ }
+
+ return true;
+ };
+
+ // stash'd unid check
+ let unids = items
+ .filter(function (item) {
+ return !item.identified && item.isInStash;
+ });
+ if (unids.length && NPCAction.fillTome(sdk.items.TomeofIdentify, true)) {
+ unids.forEach(function (item) {
+ Item.identify(item);
+ });
+ }
+
+ // ring check - sometimes a higher tier ring ends up on the wrong finger causing a rollback loop
+ if (me.equipped.get(sdk.body.RingLeft).tierScore > me.equipped.get(sdk.body.RingRight).tierScore) {
+ console.log("ÿc9" + task + "ÿc0 :: Swapping rings, higher tier ring is on the wrong finger");
+ clickItemAndWait(sdk.clicktypes.click.item.Left, sdk.body.RingLeft);
+ delay(200);
+ me.itemoncursor && clickItemAndWait(sdk.clicktypes.click.item.Left, sdk.body.RingRight);
+ delay(200);
+ me.itemoncursor && clickItemAndWait(sdk.clicktypes.click.item.Left, sdk.body.RingLeft);
+ me.equipped.init();
+ }
+
+ !getUIFlag(sdk.uiflags.Shop) && me.cancel();
+
+ while (items.length > 0) {
+ items.sort(sortEq);
+ const item = items.shift();
+ if (!item.isInStorage) continue;
+ let tier = NTIP.GetTier(item);
+ if (tier <= 0) continue;
+ /** @type {Array} */
+ const bodyLoc = this.getBodyLoc(item);
+
+ for (let loc of bodyLoc) {
+ const equippedItem = me.equipped.get(loc);
+ if (equippedItem.classid === sdk.items.quest.KhalimsWill) continue;
+ if (!item.identified && !Item.identify(item)) continue;
+ // rings are special
+ if (item.itemType === sdk.items.type.Ring) {
+ // have to pass in the specific location
+ tier = tierscore(item, 1, loc);
+
+ if (tier > equippedItem.tierScore) {
+ if (!runEquip(item, loc, tier)) {
+ continue;
+ }
+
+ break;
+ }
+ } else {
+ if (tier > equippedItem.tier) {
+ console.debug("EquippedItem :: " + equippedItem.prettyPrint + " |ÿc0 Tier: " + equippedItem.tier);
+
+ if (item.twoHanded && !me.barbarian) {
+ if (tier < me.equipped.get(sdk.body.RightArm).tier + me.equipped.get(sdk.body.LeftArm).tier) {
+ continue;
+ }
+ console.log("ÿc9" + task + "ÿc0 :: TwoHandedWep better than sum tier of currently equipped main + shield hand : " + item.fname + " Tier: " + tier);
+ }
+
+ if (!me.barbarian && loc === sdk.body.LeftArm
+ && equippedItem.tier === -1
+ && me.equipped.get(sdk.body.RightArm).twoHanded) {
+ if (tier < me.equipped.get(sdk.body.RightArm).tier) {
+ continue;
+ }
+ console.log("ÿc9" + task + "ÿc0 :: TwoHandedWep not as good as what we want to equip on our shield hand : " + item.fname + " Tier: " + tier);
+ }
+
+ if (!runEquip(item, loc, tier)) {
+ continue;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Exiting ÿc9" + task + "ÿc0. Time elapsed: " + Time.format(getTickCount() - tick));
+ return true;
};
/**
@@ -339,1167 +348,675 @@ Item.autoEquip = function (task = "") {
* @param {number} bodyLoc
*/
Item.equip = function (item, bodyLoc) {
- // can't equip - @todo handle if it's one of our final items and we can equip it given the stats of our other items
- if (!this.canEquip(item)) return false;
-
- // Already equipped in the right slot
- if (item.mode === sdk.items.mode.Equipped && item.bodylocation === bodyLoc) return true;
- // failed to open stash
- if (item.isInStash && !Town.openStash()) return false;
- // failed to open cube
- if (item.isInCube && !Cubing.openCube()) return false;
-
- let rolledBack = false;
- // todo: sometimes rings get bugged with the higher tier ring ending up on the wrong finger, if this happens swap them
-
- 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) {
- // rollback check
- let justEquipped = this.getEquippedItem(bodyLoc);
- let checkScore = 0;
- switch (cursorItem.itemType) {
- case sdk.items.type.Ring:
- checkScore = tierscore(cursorItem, bodyLoc);
- if (checkScore > justEquipped.tierScore && !justEquipped.finalItem) {
- console.debug("ROLLING BACK TO OLD ITEM BECAUSE IT WAS BETTER");
- console.debug("OldItem: " + checkScore + " Just Equipped Item: " + this.getEquippedItem(bodyLoc).tierScore);
- clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc);
- cursorItem = Game.getCursorUnit();
- rolledBack = true;
- }
-
- break;
- default:
- checkScore = NTIP.GetTier(cursorItem);
- if (checkScore > justEquipped.tier && !item.questItem && !justEquipped.isRuneword/*Wierd bug with runewords that it'll fail to get correct item desc so don't attempt rollback*/) {
- console.debug("ROLLING BACK TO OLD ITEM BECAUSE IT WAS BETTER");
- console.debug("OldItem: " + checkScore + " Just Equipped Item: " + this.getEquippedItem(bodyLoc).tier);
- clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc);
- cursorItem = Game.getCursorUnit();
- rolledBack = true;
- }
-
- break;
- }
-
- if (cursorItem && !cursorItem.shouldKeep()) {
- cursorItem.drop();
- }
- }
- }
-
- return rolledBack ? false : true;
- }
- }
- }
-
- return false;
+ // can't equip - @todo handle if it's one of our final items and we can equip it given the stats of our other items
+ if (!this.canEquip(item)) return false;
+
+ // Already equipped in the right slot
+ if (item.isEquipped && item.bodylocation === bodyLoc) return true;
+ // failed to open stash
+ if (item.isInStash && !Town.openStash()) return false;
+ // failed to open cube
+ if (item.isInCube && !Cubing.openCube()) return false;
+
+ let currentEquipped = me.getItemsEx()
+ .filter(function (item) {
+ return item.isEquipped && item.bodylocation === bodyLoc;
+ })
+ .first();
+ if (currentEquipped && !item.questItem) {
+ if (NTIP.GetTier(currentEquipped) > NTIP.GetTier(item)) {
+ console.debug(
+ "ÿc9Equipÿc0 ::\n"
+ + "ÿc2: Tried to equip lower tier'd item. " + item.prettyPrint + "\n"
+ );
+ console.debug(me.equipped.get(bodyLoc));
+ return false;
+ }
+ console.debug(
+ "ÿc9Equipÿc0 ::\n"
+ + "ÿc9 - Equippingÿc0: " + item.prettyPrint + "\n"
+ + "ÿc9 - Tierÿc0: " + NTIP.GetTier(item) + "\n"
+ + "ÿc9 - Currently Equippedÿc0: " + currentEquipped.prettyPrint + "\n"
+ + "ÿc9 - Tierÿc0: " + NTIP.GetTier(currentEquipped) + "\n"
+ + "ÿc9 - BodyLocÿc0: " + bodyLoc
+ );
+ }
+ let rolledBack = false;
+ // todo: sometimes rings get bugged with the higher tier ring ending up on the wrong finger, if this happens swap them
+
+ 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) {
+ // rollback check
+ let justEquipped = me.equipped.get(bodyLoc);
+ let checkScore = 0;
+ switch (cursorItem.itemType) {
+ case sdk.items.type.Ring:
+ checkScore = tierscore(cursorItem, 1, bodyLoc);
+ if (checkScore > justEquipped.tierScore) {
+ console.debug("ROLLING BACK TO OLD ITEM BECAUSE IT WAS BETTER");
+ console.debug("OldItem: " + checkScore + " Just Equipped Item: " + me.equipped.get(bodyLoc).tierScore);
+ clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc);
+ cursorItem = Game.getCursorUnit();
+ rolledBack = true;
+ }
+
+ break;
+ default:
+ checkScore = NTIP.GetTier(cursorItem);
+ if (checkScore > justEquipped.tier
+ && !item.questItem
+ /*Wierd bug with runewords that it'll fail to get correct item desc so don't attempt rollback*/
+ && !justEquipped.isRuneword) {
+ console.debug("ROLLING BACK TO OLD ITEM BECAUSE IT WAS BETTER");
+ console.debug("OldItem: " + checkScore + " Just Equipped Item: " + me.equipped.get(bodyLoc).tier);
+ clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc);
+ cursorItem = Game.getCursorUnit();
+ rolledBack = true;
+ }
+
+ break;
+ }
+
+ if (cursorItem && !cursorItem.shouldKeep()) {
+ console.debug("ÿc9Equipÿc0 :: Dropping " + cursorItem.prettyPrint);
+ cursorItem.drop();
+ }
+ }
+ }
+
+ return rolledBack ? false : true;
+ }
+ }
+ }
+
+ return false;
};
Item.removeItem = function (bodyLoc = -1, item = undefined) {
- let removable = item && typeof item === "object"
- ? item
- : me.getEquippedItem(bodyLoc);
- !me.inTown && Town.goToTown();
- !getUIFlag(sdk.uiflags.Stash) && Town.openStash();
-
- if (removable) {
- removable.isOnSwap && me.weaponswitch !== 1 && me.switchWeapons(1);
- removable.toCursor();
- let cursorItem = Game.getCursorUnit();
-
- if (cursorItem) {
- // only keep wanted items
- if ([Pickit.Result.WANTED, Pickit.Result.SOLOWANTS].includes(Pickit.checkItem(cursorItem).result) || AutoEquip.wanted(cursorItem)) {
- if (Storage.Inventory.CanFit(cursorItem)) {
- Storage.Inventory.MoveTo(cursorItem);
- } else if (Storage.Stash.CanFit(cursorItem)) {
- Storage.Stash.MoveTo(cursorItem);
- } else if (Storage.Cube.CanFit(cursorItem)) {
- Storage.Cube.MoveTo(cursorItem);
- }
- } else {
- D2Bot.printToConsole("Dropped " + cursorItem.prettyPrint + " during un-equip process", sdk.colors.D2Bot.Red);
- cursorItem.drop();
- }
- }
-
- me.weaponswitch === 1 && me.switchWeapons(0);
-
- return true;
- }
-
- return false;
+ let removable = item && typeof item === "object"
+ ? item
+ : me.getEquippedItem(bodyLoc);
+ !me.inTown && Town.goToTown();
+ !getUIFlag(sdk.uiflags.Stash) && Town.openStash();
+
+ if (removable) {
+ removable.isOnSwap && me.weaponswitch !== 1 && me.switchWeapons(1);
+ removable.toCursor();
+ let cursorItem = Game.getCursorUnit();
+
+ if (cursorItem) {
+ // only keep wanted items
+ if ([Pickit.Result.WANTED, Pickit.Result.SOLOWANTS].includes(Pickit.checkItem(cursorItem).result)
+ || AutoEquip.wanted(cursorItem)) {
+ if (Storage.Inventory.CanFit(cursorItem)) {
+ Storage.Inventory.MoveTo(cursorItem);
+ } else if (Storage.Stash.CanFit(cursorItem)) {
+ Storage.Stash.MoveTo(cursorItem);
+ } else if (Storage.Cube.CanFit(cursorItem)) {
+ Storage.Cube.MoveTo(cursorItem);
+ }
+ } else {
+ D2Bot.printToConsole("Dropped " + cursorItem.prettyPrint + " during un-equip process", sdk.colors.D2Bot.Red);
+ cursorItem.drop();
+ }
+ }
+
+ me.weaponswitch === 1 && me.switchWeapons(0);
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * @param {ItemUnit} item
+ */
+Item.hasSecondaryTier = function (item) {
+ return Config.AutoEquip && me.expansion && NTIP.GetSecondaryTier(item) > 0;
};
-Item.hasSecondaryTier = (item) => Config.AutoEquip && me.expansion && NTIP.GetSecondaryTier(item) > 0;
-
-Item.getBodyLocSecondary = function (item) {
- let bodyLoc = (() => {
- switch (true) {
- case Item.shieldTypes.includes(item.itemType):
- return sdk.body.LeftArmSecondary;
- case Item.weaponTypes.includes(item.itemType):
- return me.barbarian && item.twoHanded && !item.strictlyTwoHanded
- ? [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary]
- : sdk.body.RightArmSecondary;
- case [sdk.items.type.HandtoHand, sdk.items.type.AssassinClaw].includes(item.itemType):
- return !Check.currentBuild().caster && me.assassin ? [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary] : sdk.body.RightArmSecondary;
- default:
- return false;
- }
- })();
-
- return Array.isArray(bodyLoc) ? bodyLoc : [bodyLoc];
+/**
+ * @param {ItemUnit} item
+ */
+Item.getSecondaryBodyLoc = function (item) {
+ if (Item.shieldTypes.includes(item.itemType)) return [sdk.body.LeftArmSecondary];
+ if ([sdk.items.type.HandtoHand, sdk.items.type.AssassinClaw].includes(item.itemType)) {
+ return !Check.currentBuild().caster && me.assassin
+ ? [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary]
+ : [sdk.body.RightArmSecondary];
+ }
+ if (Item.weaponTypes.includes(item.itemType)) {
+ return me.barbarian && (!item.twoHanded || (item.twoHanded && !item.strictlyTwoHanded))
+ ? [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary]
+ : [sdk.body.RightArmSecondary];
+ }
+ return [];
};
+/**
+ * @param {ItemUnit} item
+ * @param {11 | 12} bodyLoc
+ */
Item.secondaryEquip = function (item, bodyLoc) {
- if (!this.canEquip(item) && me.expansion) 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;
- let equipped = false;
-
- me.switchWeapons(1); // Switch weapons
-
- try {
- for (let i = 0; i < 3; i += 1) {
- if (item.toCursor()) {
- clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc - 7);
-
- if (item.bodylocation === bodyLoc - 7) {
- equipped = true;
- [sdk.items.Arrows, sdk.items.Bolts].includes(item.classid) && CharData.skillData.bowData.setArrowInfo(item);
- [sdk.items.type.Bow, sdk.items.type.AmazonBow, sdk.items.type.Crossbow].includes(item.itemType) && CharData.skillData.bowData.setBowInfo(item);
- if (getCursorType() === 3) {
- let cursorItem = Game.getCursorUnit();
- !!cursorItem && !cursorItem.shouldKeep() && cursorItem.drop();
- }
-
- if (Item.hasDependancy(item) && me.needRepair() && me.inTown) {
- Town.repair(true);
- }
-
- return true;
- }
- }
- }
- } finally {
- // Switch back to primary
- me.weaponswitch !== 0 && me.switchWeapons(0);
- }
-
- return equipped;
+ if (!this.canEquip(item) && me.expansion) 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;
+ let equipped = false;
+
+ me.switchWeapons(1); // Switch weapons
+
+ try {
+ for (let i = 0; i < 3; i += 1) {
+ if (item.toCursor()) {
+ clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc - 7);
+
+ if (item.bodylocation === bodyLoc - 7) {
+ equipped = true;
+ [sdk.items.Arrows, sdk.items.Bolts].includes(item.classid) && CharData.skillData.bow.setArrowInfo(item);
+ if ([sdk.items.type.Bow, sdk.items.type.AmazonBow, sdk.items.type.Crossbow].includes(item.itemType)) {
+ CharData.skillData.bow.setBowInfo(item);
+ }
+
+ if (getCursorType() === 3) {
+ let cursorItem = Game.getCursorUnit();
+ !!cursorItem && !cursorItem.shouldKeep() && cursorItem.drop();
+ }
+
+ return true;
+ }
+ }
+ }
+ } finally {
+ me.switchToPrimary();
+ }
+
+ return equipped;
};
+/**
+ * @param {ItemUnit} item
+ */
Item.autoEquipCheckSecondary = function (item) {
- if (!Config.AutoEquip) return true;
- if (me.classic) return false;
+ if (!Config.AutoEquip) return true;
+ if (me.classic) return false;
- let tier = NTIP.GetSecondaryTier(item);
- let bodyLoc = Item.getBodyLocSecondary(item);
+ let tier = NTIP.GetSecondaryTier(item);
+ if (tier <= 0) return false;
+ let bodyLoc = Item.getSecondaryBodyLoc(item);
- for (let i = 0; tier > 0 && i < bodyLoc.length; i += 1) {
- if (tier > Item.getEquippedItem(bodyLoc[i]).secondarytier && (Item.canEquip(item) || !item.identified)) {
- return true;
- }
- }
+ for (let loc of bodyLoc) {
+ if (tier > me.equipped.get(loc).secondaryTier
+ && (Item.canEquip(item) || !item.identified)) {
+ return true;
+ }
+ }
- return false;
+ return false;
};
+/**
+ * @param {string} task
+ */
Item.autoEquipSecondary = function (task = "") {
- if (!Config.AutoEquip || me.classic) return true;
-
- task = task + "Secondary AutoEquip";
- console.log("ÿc8Kolbot-SoloPlayÿc0: Entering " + task);
-
- const noStash = (!me.inTown || task !== "Secondary AutoEquip");
- let tick = getTickCount();
- let items = me.getItemsEx()
- .filter(function (item) {
- if (!item.isInStorage) return false;
- if (noStash && !item.isInInventory) return false;
- let tier = NTIP.GetSecondaryTier(item);
- return (item.identified ? tier > 0 : tier !== 0);
- });
-
- if (!items) return false;
-
- const sortEq = (a, b) => {
- if (Item.canEquip(a)) return -1;
- if (Item.canEquip(b)) return 1;
- return 0;
- };
-
- me.cancel();
-
- while (items.length > 0) {
- items.sort(sortEq);
- const item = items.shift();
- const tier = NTIP.GetSecondaryTier(item);
- let bodyLoc = this.getBodyLocSecondary(item);
-
- if (tier > 0 && bodyLoc) {
- for (let j = 0; j < bodyLoc.length; j += 1) {
- const equippedItem = this.getEquippedItem(bodyLoc[j]);
- if (item.isInStorage && tier > equippedItem.secondarytier && equippedItem.classid !== sdk.items.quest.KhalimsWill) {
- Item.identify(item);
-
- let gid = item.gid;
- let prettyName = item.prettyPrint;
- console.debug(prettyName + " tier: " + tier);
-
- if (this.secondaryEquip(item, bodyLoc[j])) {
- console.log("ÿc9SecondaryEquipÿc0 :: Equipped: " + prettyName + " SecondaryTier: " + tier);
- Developer.debugging.autoEquip && Misc.logItem("Equipped switch", me.getItem(-1, -1, gid));
- Developer.logEquipped && MuleLogger.logEquippedItems();
- }
-
- break;
- }
- }
- }
- }
-
- console.log("ÿc8Kolbot-SoloPlayÿc0: Exiting secondary auto equip. Time elapsed: " + Developer.formatTime(getTickCount() - tick));
- return true;
+ if (!Config.AutoEquip || me.classic) return true;
+
+ task = task + "Secondary AutoEquip";
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Entering " + task);
+
+ const noStash = (!me.inTown || task !== "Secondary AutoEquip");
+ let tick = getTickCount();
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ if (!item.isInStorage) return false;
+ if (noStash && !item.isInInventory) return false;
+ let tier = NTIP.GetSecondaryTier(item);
+ return (item.identified ? tier > 0 : tier !== 0);
+ });
+
+ if (!items) return false;
+
+ const sortEq = function (a, b) {
+ if (Item.canEquip(a)) return -1;
+ if (Item.canEquip(b)) return 1;
+ return 0;
+ };
+
+ me.cancel();
+
+ while (items.length > 0) {
+ items.sort(sortEq);
+ const item = items.shift();
+ if (!item.isInStorage) continue;
+ const tier = NTIP.GetSecondaryTier(item);
+ if (tier <= 0) continue;
+ let bodyLoc = Item.getSecondaryBodyLoc(item);
+
+ for (let loc of bodyLoc) {
+ const equippedItem = me.equipped.get(loc);
+ // should never happen - but just in case
+ if (equippedItem.classid === sdk.items.quest.KhalimsWill) continue;
+ if (tier > equippedItem.secondaryTier) {
+ Item.identify(item);
+
+ let gid = item.gid;
+ let prettyName = item.prettyPrint;
+ console.debug(prettyName + " tier: " + tier);
+
+ if (this.secondaryEquip(item, loc)) {
+ console.log("ÿc9SecondaryEquipÿc0 :: Equipped: " + prettyName + " SecondaryTier: " + tier);
+ Developer.debugging.autoEquip && Item.logItem("Equipped switch", me.getItem(-1, -1, gid));
+ Developer.logEquipped && MuleLogger.logEquippedItems();
+ me.equipped.set(loc, item);
+ }
+
+ break;
+ }
+ }
+ }
+
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Exiting secondary auto equip. Time elapsed: " + Time.format(getTickCount() - tick));
+ return true;
};
-// AUTO EQUIP MERC - modified from dzik's
+/**
+ * @param {ItemUnit} item
+ */
Item.hasMercTier = (item) => Config.AutoEquip && me.expansion && NTIP.GetMercTier(item) > 0;
-// need to re-work using char data so we can shop/keep items if merc is dead *but* we have enough to revive him and buy the item and enough space
+/**
+ * @param {ItemUnit} item
+ * @param {number} bodyLoc
+ * @todo re-work using char data so we can shop/keep items if merc is dead *but* we have enough to revive him and buy the item and enough space
+ */
Item.canEquipMerc = function (item, bodyLoc) {
- if (item.type !== sdk.unittype.Item || me.classic) return false;
- let mercenary = me.getMercEx();
+ if (item.type !== sdk.unittype.Item || me.classic) return false;
+ let mercenary = me.getMercEx();
- // dont have merc or he is dead or unidentifed item
- if (!mercenary || !item.identified) return false;
- let curr = Item.getEquippedItemMerc(bodyLoc);
+ // dont have merc or he is dead or unidentifed item
+ if (!mercenary || !item.identified) return false;
+ let curr = Item.getMercEquipped(bodyLoc);
- // Higher requirements
- if (item.getStat(sdk.stats.LevelReq) > mercenary.getStat(sdk.stats.Level)
- || item.dexreq > mercenary.getStat(sdk.stats.Dexterity) - curr.dex
- || item.strreq > mercenary.getStat(sdk.stats.Strength) - curr.str) {
- return false;
- }
+ // Higher requirements
+ if (item.lvlreq > mercenary.getStat(sdk.stats.Level)
+ || item.dexreq > mercenary.getStat(sdk.stats.Dexterity) - curr.dex
+ || item.strreq > mercenary.getStat(sdk.stats.Strength) - curr.str) {
+ return false;
+ }
- return true;
+ return true;
};
+/**
+ * @param {ItemUnit} item
+ * @param {number} bodyLoc
+ */
Item.equipMerc = function (item, bodyLoc) {
- let mercenary = me.getMercEx();
-
- // dont have merc or he is dead or higher requirements
- if (!mercenary || !Item.canEquipMerc(item, bodyLoc)) 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()) {
- if (clickItem(sdk.clicktypes.click.item.Mercenary, bodyLoc)) {
- delay(500 + me.ping * 2);
- Developer.debugging.autoEquip && Misc.logItem("Merc Equipped", mercenary.getItem(item.classid));
- }
-
- let check = mercenary.getItem(item.classid);
-
- if (check && check.bodylocation === bodyLoc) {
- if (check.runeword) {
- // just track runewords for now
- myData.merc.gear.push(check.prefixnum);
- CharData.updateData("merc", myData);
- }
-
- if (getCursorType() === 3) {
- let cursorItem = Game.getCursorUnit();
- !!cursorItem && !cursorItem.shouldKeep() && cursorItem.drop();
- }
-
- Developer.logEquipped && MuleLogger.logEquippedItems();
-
- return true;
- }
- }
- }
-
- return false;
-};
-
-Item.getEquippedItemMerc = function (bodyLoc = -1) {
- let mercenary = me.getMercEx();
-
- if (mercenary) {
- let item = mercenary.getItemsEx().filter((item) => item.isEquipped && item.bodylocation === bodyLoc).first();
-
- if (item) {
- return {
- classid: item.classid,
- prefixnum: item.prefixnum,
- tier: NTIP.GetMercTier(item),
- name: item.fname,
- str: item.getStatEx(sdk.stats.Strength),
- dex: item.getStatEx(sdk.stats.Dexterity)
- };
- }
- }
-
- // Don't have anything equipped in there
- return {
- classid: -1,
- prefixnum: -1,
- tier: -1,
- name: "none",
- str: 0,
- dex: 0
- };
+ let mercenary = me.getMercEx();
+
+ // dont have merc or he is dead or higher requirements
+ if (!mercenary || !Item.canEquipMerc(item, bodyLoc)) 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()) {
+ if (clickItem(sdk.clicktypes.click.item.Mercenary, bodyLoc)) {
+ delay(500 + me.ping * 2);
+ Developer.debugging.autoEquip && Item.logItem("Merc Equipped", mercenary.getItem(item.classid));
+ }
+
+ let check = mercenary.getItem(item.classid);
+
+ if (check && check.bodylocation === bodyLoc) {
+ if (check.runeword) {
+ // just track runewords for now
+ me.data.merc.gear.push(check.prefixnum);
+ CharData.updateData("merc", me.data);
+ }
+
+ if (getCursorType() === 3) {
+ let cursorItem = Game.getCursorUnit();
+ !!cursorItem && !cursorItem.shouldKeep() && cursorItem.drop();
+ }
+
+ Developer.logEquipped && MuleLogger.logEquippedItems();
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+Item.getMercEquipped = function (bodyLoc = -1) {
+ let mercenary = me.getMercEx();
+
+ if (mercenary) {
+ let item = mercenary.getItemsEx()
+ .filter(function (item) {
+ return item.isEquipped && item.bodylocation === bodyLoc;
+ })
+ .first();
+
+ if (item) {
+ return {
+ classid: item.classid,
+ prefixnum: item.prefixnum,
+ tier: NTIP.GetMercTier(item),
+ name: item.fname,
+ str: item.getStatEx(sdk.stats.Strength),
+ dex: item.getStatEx(sdk.stats.Dexterity)
+ };
+ }
+ }
+
+ // Don't have anything equipped in there
+ return {
+ classid: -1,
+ prefixnum: -1,
+ tier: -1,
+ name: "none",
+ str: 0,
+ dex: 0
+ };
};
+/**
+ * @param {ItemUnit} item
+ * @returns {number[]}
+ */
Item.getBodyLocMerc = function (item) {
- let mercenary = me.getMercEx();
-
- // dont have merc or he is dead
- if (!mercenary) return false;
-
- let bodyLoc = (() => {
- switch (item.itemType) {
- case sdk.items.type.Shield:
- return (mercenary.classid === sdk.mercs.IronWolf ? sdk.body.LeftArm : []);
- case sdk.items.type.Armor:
- return sdk.body.Armor;
- case sdk.items.type.Helm:
- case sdk.items.type.Circlet:
- return sdk.body.Head;
- case sdk.items.type.PrimalHelm:
- return (mercenary.classid === sdk.mercs.A5Barb ? sdk.body.Head : []);
- case sdk.items.type.Bow:
- return (mercenary.classid === sdk.mercs.Rogue ? sdk.body.RightArm : []);
- case sdk.items.type.Spear:
- case sdk.items.type.Polearm:
- return (mercenary.classid === sdk.mercs.Guard ? sdk.body.RightArm : []);
- case sdk.items.type.Sword:
- return ([sdk.mercs.IronWolf, sdk.mercs.A5Barb].includes(mercenary.classid) ? sdk.body.RightArm : []);
- default:
- return false;
- }
- })();
-
- return Array.isArray(bodyLoc) ? bodyLoc : [bodyLoc];
+ let _mercId = me.data.merc.classid;
+
+ switch (item.itemType) {
+ case sdk.items.type.Shield:
+ return (_mercId === sdk.mercs.IronWolf
+ ? [sdk.body.LeftArm]
+ : []);
+ case sdk.items.type.Armor:
+ return [sdk.body.Armor];
+ case sdk.items.type.Helm:
+ case sdk.items.type.Circlet:
+ return [sdk.body.Head];
+ case sdk.items.type.PrimalHelm:
+ return (_mercId === sdk.mercs.A5Barb
+ ? [sdk.body.Head]
+ : []);
+ case sdk.items.type.Bow:
+ return (_mercId === sdk.mercs.Rogue
+ ? [sdk.body.RightArm]
+ : []);
+ case sdk.items.type.Spear:
+ case sdk.items.type.Polearm:
+ return (_mercId === sdk.mercs.Guard
+ ? [sdk.body.RightArm]
+ : []);
+ case sdk.items.type.Sword:
+ return ([sdk.mercs.IronWolf, sdk.mercs.A5Barb].includes(_mercId)
+ ? sdk.body.RightArm
+ : []);
+ }
+ return [];
};
-Item.autoEquipCheckMerc = function (item) {
- if (!Config.AutoEquip) return true;
- if (Config.AutoEquip && !me.getMercEx()) return false;
-
- let tier = NTIP.GetMercTier(item), bodyLoc = Item.getBodyLocMerc(item);
-
- if (tier > 0 && bodyLoc) {
- for (let i = 0; i < bodyLoc.length; i += 1) {
- let oldTier = Item.getEquippedItemMerc(bodyLoc[i]).tier; // Low tier items shouldn't be kept if they can't be equipped
-
- if (tier > oldTier && (Item.canEquipMerc(item) || !item.identified)) {
- return true;
- }
- }
- }
-
- return false;
-};
-
-Item.autoEquipKeepCheckMerc = function (item) {
- if (!Config.AutoEquip) return true;
- if (Config.AutoEquip && !me.getMercEx()) return false;
-
- let tier = NTIP.GetMercTier(item), bodyLoc = Item.getBodyLocMerc(item);
-
- if (tier > 0 && bodyLoc) {
- for (let i = 0; i < bodyLoc.length; i += 1) {
- let oldTier = Item.getEquippedItemMerc(bodyLoc[i]).tier; // Low tier items shouldn't be kept if they can't be equipped
-
- if (tier > oldTier) {
- return true;
- }
- }
- }
-
- return false;
-};
-
-Item.autoEquipMerc = function () {
- if (!Config.AutoEquip || !me.getMercEx()) return true;
-
- let items = me.getItemsEx()
- .filter(function (item) {
- if (!item.isInStorage) return false;
- let tier = NTIP.GetMercTier(item);
- return (item.identified ? tier > 0 : tier !== 0);
- });
-
- if (!items.length) return false;
-
- function sortEq (a, b) {
- if (Item.canEquipMerc(a) && Item.canEquipMerc(b)) {
- return NTIP.GetMercTier(b) - NTIP.GetMercTier(a);
- }
-
- if (Item.canEquipMerc(a)) return -1;
- if (Item.canEquipMerc(b)) return 1;
-
- return 0;
- }
-
- me.cancel();
-
- while (items.length > 0) {
- items.sort(sortEq);
- const item = items.shift();
- const tier = NTIP.GetMercTier(item);
- const bodyLoc = Item.getBodyLocMerc(item);
- const name = item.name;
-
- if (tier > 0 && bodyLoc) {
- for (let j = 0; j < bodyLoc.length; j += 1) {
- if ([sdk.storage.Inventory, sdk.storage.Stash].includes(item.location) && tier > Item.getEquippedItemMerc(bodyLoc[j]).tier) {
- Item.identify(item);
-
- console.log("Merc " + name);
- this.equipMerc(item, bodyLoc[j]) && console.log("ÿc9MercEquipÿc0 :: Equipped: " + name + " MercTier: " + tier);
-
- let cursorItem = Game.getCursorUnit();
-
- if (cursorItem) {
- cursorItem.drop();
- Developer.debugging.autoEquip && Misc.logItem("Merc Dropped", cursorItem);
- }
-
- break;
- }
- }
- }
- }
-
- return true;
-};
-
-Item.removeItemsMerc = function () {
- let mercenary = me.getMercEx();
- if (!mercenary) return true;
- // Sort items so we try to keep the highest tier'd items in case space in our invo is limited
- let items = mercenary.getItemsEx().sort((a, b) => NTIP.GetMercTier(b) - NTIP.GetMercTier(a));
-
- if (items) {
- for (let i = 0; i < items.length; i++) {
- clickItem(sdk.clicktypes.click.item.Mercenary, items[i].bodylocation);
- delay(500 + me.ping * 2);
-
- let cursorItem = Game.getCursorUnit();
-
- if (cursorItem) {
- if (Storage.Inventory.CanFit(cursorItem)) {
- Storage.Inventory.MoveTo(cursorItem);
- } else {
- cursorItem.drop();
- }
- }
- }
- }
-
- return !!mercenary.getItem();
-};
-
-// Charm Autoequip - TODO: clean this section up...sigh
-// goals
-/*
-* need to be able to define what types of charms we want while leveling, and upgrade based on that
-* need to be able to define what types of charms we want for final build, upgrade to that
-* need to be able to handle different invoquantity values of final charms vs leveling charms
-* need to be abel to handle final charms and leveling charms being the same type, in situation where we have enough of a final charm so compare it as a noraml leveling charm
-* need to differentiate bewtween cubing charm or pickit wanted charm vs autoequip charm
-* example:
-* Imagine we are an auradin and we have 9 small charms in our inventory, Seven 5allres/20life and Two random life charms. Our build tells us we should keep 6 of the 5/20s
-* so we should keep those. That leaves us with One 5/20 and Two random life charms, we should then compare the tier values and keep the highest of the two then sell or drop the third.
-* As it is now, what happens is we don't compare the 7th 5/20 and we add that to the sell list while keeping the 2 lower charms. If we directly add it to the backup then the invoquantity
-* gets read from the finalBuild file so instead of only keeping two it says we should keep 6.
-*/
-Item.hasCharmTier = (item) => me.expansion && Config.AutoEquip && NTIP.GetCharmTier(item) > 0;
-Item.isFinalCharm = (item) => myData.me.charmGids.includes(item.gid);
-
/**
- * Iterate over charm checklist, pickit result 0 and 4 get sold
- * Otherwise if its not in the stash already and not a final charm try and stash it. I don't remember why I checked if it wasn't a final charm
- * @param {ItemUnit[]} checkList
- * @param {boolean} verbose
+ * @param {ItemUnit} item
+ * @param {boolean} basicCheck
*/
-const spliceCharmCheckList = function (checkList = [], verbose = false) {
- for (let i = 0; i < checkList.length; i++) {
- const currCharm = checkList[i];
- if (!currCharm || [Pickit.Result.UNWANTED, Pickit.Result.TRASH].includes(Pickit.checkItem(currCharm).result)) continue;
- if (!currCharm.isInStash && !myData.me.charmGids.includes(currCharm.gid)) {
- if (!Storage.Stash.MoveTo(currCharm)) {
- verbose && Misc.itemLogger("Dropped", currCharm);
- currCharm.drop();
- } else {
- if (verbose) {
- Cubing.checkItem(currCharm) ? Misc.logItem("Stashed Cubing Ingredient", currCharm) : Misc.logItem("Stashed", currCharm);
- }
- }
- }
-
- checkList.splice(i, 1);
- i -= 1;
- }
-};
-
-const spliceCharmKeepList = function (keep = [], sell = [], verbose = false) {
- if (!keep.length) return;
- const id = keep[0].classid;
- const cInfo = (() => {
- switch (id) {
- case sdk.items.SmallCharm:
- return CharData.charmData.small.getCountInfo();
- case sdk.items.LargeCharm:
- return CharData.charmData.large.getCountInfo();
- case sdk.items.GrandCharm:
- return CharData.charmData.grand.getCountInfo();
- default:
- return { max: 0 };
- }
- })();
-
- // sort through kept charms
- if (keep.length > cInfo.max) {
- keep.sort((a, b) => NTIP.GetCharmTier(b) - NTIP.GetCharmTier(a));
-
- // everything after the cap (need a better method for this in the instances where the max cap is less then leveling wanted cap)
- for (let i = cInfo.max; i < keep.length; i++) {
- if (!!keep[i].classid && !Item.autoEquipCharmCheck(keep[i])) {
- sell.push(keep[i]);
- verbose && console.log("ÿc8Kolbot-SoloPlayÿc0: CharmEquip Add " + keep[i].fname + " to checkList");
- keep.splice(i, 1);
- i -= 1;
- }
- }
- }
-};
+Item.autoEquipCheckMerc = function (item, basicCheck = false) {
+ if (!Config.AutoEquip) return true;
+ if (Config.AutoEquip && !me.getMercEx()) return false;
-Item.autoEquipSC = function () {
- let verbose = Developer.debugging.smallCharm;
- // build list of our charms
- let items = me.getItemsEx()
- .filter((charm) => charm.isInStorage && charm.classid === sdk.items.SmallCharm && charm.magic);
+ let tier = NTIP.GetMercTier(item);
+ if (tier <= 0) return false;
+ let bodyLoc = Item.getBodyLocMerc(item);
- if (!items.length) {
- verbose && console.debug("No charms found");
- return { keep: [], sell: [] };
- }
+ for (let loc of bodyLoc) {
+ let oldTier = Item.getMercEquipped(loc).tier;
- let charms = Item.autoEquipCharmSort(items, verbose);
- spliceCharmKeepList(charms.keep, charms.checkList, verbose);
+ if (tier > oldTier) {
+ if (Item.canEquipMerc(item) || !item.identified) {
+ return true;
+ } else if (basicCheck) {
+ // keep wanted final gear items
+ if (NTIP.CheckItem(item, NTIP.FinalGear) === Pickit.Result.WANTED) {
+ return true;
+ }
- verbose && console.log("Small Charm checklist length: " + charms.checkList.length);
- spliceCharmCheckList(charms.checkList, verbose);
+ return false;
+ }
+ }
+ }
- return { keep: charms.keep, sell: charms.checkList };
+ return false;
};
-Item.autoEquipLC = function () {
- let verbose = Developer.debugging.largeCharm;
- let items = me.getItemsEx()
- .filter((charm) => charm.isInStorage && charm.classid === sdk.items.LargeCharm && charm.magic);
-
- if (!items.length) {
- verbose && console.debug("No charms found");
- return { keep: [], sell: [] };
- }
-
- let charms = Item.autoEquipCharmSort(items, verbose);
- spliceCharmKeepList(charms.keep, charms.checkList, verbose);
-
- verbose && console.log("Large charm checklist length: " + charms.checkList.length);
- spliceCharmCheckList(charms.checkList, verbose);
-
- return { keep: charms.keep, sell: charms.checkList };
-};
-
-/**
- * @memberof Item
- * @returns { { keep: ItemUnit[], sell: ItemUnit[] } }
- */
-Item.autoEquipGC = function () {
- let verbose = Developer.debugging.largeCharm;
- let items = me.getItemsEx()
- .filter((charm) => charm.isInStorage && charm.classid === sdk.items.GrandCharm && charm.magic);
-
- if (!items.length) {
- verbose && console.debug("No charms found");
- return { keep: [], sell: [] };
- }
-
- let charms = Item.autoEquipCharmSort(items, verbose);
- spliceCharmKeepList(charms.keep, verbose);
-
- verbose && console.log("Grand charm checklist length: " + charms.checkList.length);
- spliceCharmKeepList(charms.keep, charms.checkList, verbose);
-
- return { keep: charms.keep, sell: charms.checkList };
+Item.autoEquipMerc = function () {
+ if (!Config.AutoEquip || !me.getMercEx()) return true;
+
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ if (!item.isInStorage) return false;
+ let tier = NTIP.GetMercTier(item);
+ return (item.identified ? tier > 0 : tier !== 0);
+ });
+ if (!items.length) return false;
+
+ function sortEq (a, b) {
+ let [prioA, prioB] = [Item.canEquipMerc(a), Item.canEquipMerc(b)];
+ if (prioA && prioB) return NTIP.GetMercTier(b) - NTIP.GetMercTier(a);
+ if (prioA) return -1;
+ if (prioB) return 1;
+ return 0;
+ }
+
+ me.cancel();
+
+ while (items.length > 0) {
+ items.sort(sortEq);
+ const item = items.shift();
+ const tier = NTIP.GetMercTier(item);
+ if (tier <= 0) continue;
+ const bodyLoc = Item.getBodyLocMerc(item);
+ const name = item.name;
+
+ for (let loc of bodyLoc) {
+ if (item.isInStorage && tier > Item.getMercEquipped(loc).tier) {
+ Item.identify(item);
+
+ console.log("Merc " + name);
+ this.equipMerc(item, loc) && console.log("ÿc9MercEquipÿc0 :: Equipped: " + name + " MercTier: " + tier);
+
+ let cursorItem = Game.getCursorUnit();
+
+ if (cursorItem) {
+ cursorItem.drop();
+ Developer.debugging.autoEquip && Item.logItem("Merc Dropped", cursorItem);
+ }
+
+ break;
+ }
+ }
+ }
+
+ return true;
};
-Item.autoEquipCharmSort = function (items = [], verbose = false) {
- let charms = {
- skillerTypeA: [],
- skillerTypeB: [],
- skillerTypeC: [],
- resist: [],
- life: [],
- magicfind: [],
- damage: [],
- elemental: [],
- backup: [],
- keep: [],
- checkList: []
- };
-
- if (!items.length) {
- verbose && console.log("No charms found");
- return charms;
- }
-
- const addToCheckList = (item) => charms.checkList.indexOf(item) === -1 && charms.checkList.push(item);
- const addToBackUp = (item) => charms.backup.indexOf(item) === -1 && charms.backup.push(item);
-
- const sortCharms = (arr = [], verbose = false, backUpCheck = true) => {
- let invoquantity = NTIP.getInvoQuantity(arr[0]);
- (invoquantity === undefined || invoquantity === -1) && (invoquantity = 2);
- let charmType = Item.getCharmType(arr[0]);
- verbose && console.log("Amount of " + charmType + " Charms: " + arr.length + " invoquantity: " + invoquantity);
- arr.length > 1 && arr.sort((a, b) => NTIP.GetCharmTier(b) - NTIP.GetCharmTier(a));
-
- if (arr.length > invoquantity) {
- verbose && arr.forEach((el, index) => console.log(charmType + "[" + index + "] = " + NTIP.GetCharmTier(el)));
-
- for (let i = invoquantity; i < arr.length; i++) {
- backUpCheck ? addToBackUp(arr[i]) : addToCheckList(arr[i]);
-
- arr.splice(i, 1);
- i -= 1;
- }
- }
- };
-
- verbose && console.log("Amount of items: " + items.length);
- items.length > 1 && items.sort((a, b) => NTIP.GetCharmTier(b) - NTIP.GetCharmTier(a));
-
- const finalCharmInfo = Check.finalBuild().finalCharms;
- const finalCharmKeys = Object.keys(finalCharmInfo);
-
- let found = false;
-
- while (items.length > 0) {
- let gid = items[0].gid;
- let item = items.shift();
-
- if (!item.identified) {
- let idTool = me.getIdTool();
-
- if (idTool) {
- item.isInStash && Town.openStash();
- Town.identifyItem(item, idTool);
-
- } else if (item.isInStash && (getUIFlag(sdk.uiflags.Stash) || Town.openStash())) {
- Storage.Inventory.MoveTo(item);
- Town.identify();
- }
-
- if (!Game.getItem(-1, -1, gid)) {
- verbose && console.log("Sold charm during Town.identify()");
- items.shift();
-
- continue;
- }
- }
-
- if (myData.me.charmGids.includes(item.gid)) {
- charms.keep.push(item);
-
- continue;
- }
-
- let next = false;
-
- for (let i = 0; i < finalCharmKeys.length; i++) {
- let cKey = finalCharmKeys[i];
- try {
- if (!!myData.me.charms[cKey] && myData.me.charms[cKey].have.indexOf(item.gid) === -1
- && myData.me.charms[cKey].have.length < myData.me.charms[cKey].max) {
- if (finalCharmInfo[cKey].stats(item)) {
- console.debug(item.fname);
- myData.me.charmGids.push(item.gid);
- myData.me.charms[cKey].have.push(item.gid);
- charms.keep.push(item);
- found = true;
- next = true;
-
- break;
- }
- }
- } catch (e) {
- console.error(e);
- }
- }
-
- if (next) {
- continue;
- }
-
- if (NTIP.GetCharmTier(item) <= 0) {
- verbose && console.log("No tier. Adding to checkList: " + item.fname);
- addToCheckList(item);
- } else if (!NTIP.hasStats(item) && NTIP.GetCharmTier(item) > 0) {
- verbose && console.log("Multiple Misc charm: " + item.fname);
- charms.backup.push(item);
- } else {
- let charmType = Item.getCharmType(item);
- switch (charmType) {
- case "skillerTypeA":
- case "skillerTypeB":
- case "skillerTypeC":
- case "resist":
- case "life":
- case "magicfind":
- case "damage":
- case "elemental":
- charms[charmType].push(item);
- verbose && console.log(charmType + ": " + item.fname);
-
- break;
- default:
- addToCheckList(item);
- verbose && console.log("Failed all checks. Adding to checkList: " + item.fname);
-
- break;
- }
- }
- }
-
- if (found) {
- updateMyData();
- }
-
- if (!charms.skillerTypeA.length && !charms.skillerTypeB.length && !charms.skillerTypeC.length
- && !charms.damage.length && !charms.resist.length && !charms.elemental.length && !charms.life.length && !charms.backup.length) {
- verbose && console.log("No Charms");
- return charms;
- }
-
- charms.skillerTypeA.length > 0 && sortCharms(charms.skillerTypeA, verbose);
- charms.skillerTypeB.length > 0 && sortCharms(charms.skillerTypeB, verbose);
- charms.skillerTypeC.length > 0 && sortCharms(charms.skillerTypeC, verbose);
- charms.resist.length > 0 && sortCharms(charms.resist, verbose);
- charms.life.length > 0 && sortCharms(charms.life, verbose);
- charms.magicfind.length > 0 && sortCharms(charms.magicfind, verbose);
- charms.damage.length > 0 && sortCharms(charms.damage, verbose);
- charms.elemental.length > 0 && sortCharms(charms.elemental, verbose);
-
- // If stats are unspecifed, this will filter charms and keep highest based on invoquantity. If no invoquantity defined it will keep two of that type
- charms.backup.length > 0 && sortCharms(charms.backup, verbose, false);
- charms.keep = charms.keep.concat(charms.skillerTypeA, charms.skillerTypeB, charms.skillerTypeC, charms.resist, charms.life, charms.magicfind, charms.damage, charms.elemental, charms.backup);
- verbose && charms.checkList.forEach((el, index) => console.log("checkList[" + index + "] = " + NTIP.GetCharmTier(el) + " " + el.fname));
-
- return charms;
+Item.removeItemsMerc = function () {
+ let mercenary = me.getMercEx();
+ if (!mercenary) return true;
+ // Sort items so we try to keep the highest tier'd items in case space in our invo is limited
+ mercenary.getItemsEx()
+ .sort(function (a, b) {
+ return NTIP.GetMercTier(b) - NTIP.GetMercTier(a);
+ })
+ .forEach(function (item) {
+ clickItem(sdk.clicktypes.click.item.Mercenary, item.bodylocation);
+ delay(500 + me.ping * 2);
+
+ let cursorItem = Game.getCursorUnit();
+
+ if (cursorItem) {
+ if (Storage.Inventory.CanFit(cursorItem)) {
+ Storage.Inventory.MoveTo(cursorItem);
+ } else {
+ cursorItem.drop();
+ }
+ }
+ });
+ return mercenary.getItemsEx().length === 0;
};
/**
- * @param {ItemUnit} item
+ * Log kept item stats in the manager.
+ * @param {string} action
+ * @param {ItemUnit} unit
+ * @param {string} keptLine
+ * @param {boolean} force
*/
-Item.autoEquipCharmCheck = function (item = undefined) {
- if (!item || NTIP.GetCharmTier(item) <= 0 || !item.isCharm) return false;
- // Annhilus, Hellfire Torch, Gheeds - Handled by a different function so return true to keep
- if (item.isCharm && item.unique) return true;
- // is one of our final charms
- if (myData.me.charmGids.includes(item.gid)) return true;
-
- let lowestCharm;
- let items = me.getItemsEx()
- .filter(charm => charm.classid === item.classid && charm.isInStorage && charm.magic && NTIP.GetCharmTier(charm) > 0);
- if (!items.length) return true;
-
- let quantityCap = NTIP.getInvoQuantity(item);
- let charms = Item.autoEquipCharmSort(items);
- let charmType = Item.getCharmType(item);
- let cInfo, newList = [];
-
- switch (item.classid) {
- case sdk.items.SmallCharm:
- cInfo = CharData.charmData.small.getCountInfo();
-
- if (cInfo.curr && (cInfo.curr / cInfo.max) * 100 >= 75) {
- // chop off past our cap
- newList = charms.keep
- .sort((a, b) => NTIP.GetCharmTier(b) - NTIP.GetCharmTier(a))
- .slice(0, cInfo.max);
- // check if it made the cut
- if (!newList.find(i => i.gid === item.gid)) return false;
- lowestCharm = newList.last();
- return !!(NTIP.GetCharmTier(item) >= NTIP.GetCharmTier(lowestCharm) || item.gid === lowestCharm.gid);
- }
-
- break;
- case sdk.items.LargeCharm:
- cInfo = CharData.charmData.large.getCountInfo();
-
- if (cInfo.curr && (cInfo.curr / cInfo.max) * 100 >= 75) {
- // chop off past our cap
- newList = charms.keep
- .sort((a, b) => NTIP.GetCharmTier(b) - NTIP.GetCharmTier(a))
- .slice(0, cInfo.max);
- // check if it made the cut
- if (!newList.find(i => i.gid === item.gid)) return false;
- lowestCharm = newList.last();
- return !!(NTIP.GetCharmTier(item) >= NTIP.GetCharmTier(lowestCharm) || item.gid === lowestCharm.gid);
- }
-
- break;
- case sdk.items.GrandCharm:
- cInfo = CharData.charmData.grand.getCountInfo();
-
- if (cInfo.curr && (cInfo.curr / cInfo.max) * 100 >= 50) {
- // chop off past our cap
- newList = charms.keep
- .sort((a, b) => NTIP.GetCharmTier(b) - NTIP.GetCharmTier(a))
- .slice(0, cInfo.max);
- // check if it made the cut
- if (!newList.find(i => i.gid === item.gid)) return false;
- lowestCharm = newList.last();
- return !!(NTIP.GetCharmTier(item) >= NTIP.GetCharmTier(lowestCharm) || item.gid === lowestCharm.gid);
- }
-
- break;
- }
-
- switch (charmType) {
- case "skillerTypeA":
- case "skillerTypeB":
- case "skillerTypeC":
- case "resist":
- case "life":
- case "magicfind":
- case "damage":
- case "elemental":
- lowestCharm = charms[charmType].last();
- if ((charms[charmType].findIndex(c => c.gid === lowestCharm.gid) + 1) > quantityCap) return false;
-
- break;
- default:
- lowestCharm = charms.backup.last();
- if ((charms.backup.findIndex(c => c.gid === lowestCharm.gid) + 1) > quantityCap) return false;
-
- break;
- }
-
- return !!lowestCharm ? !!(NTIP.GetCharmTier(item) >= NTIP.GetCharmTier(lowestCharm) || item.gid === lowestCharm.gid) : true;
-};
-
-Item.initCharms = function () {
- // No charms in classic
- if (me.classic) return;
- let myCharms = me.getItemsEx().filter(item => item.isInStorage && item.isCharm && item.magic);
- let changed = false;
-
- const finalCharmKeys = Object.keys(myData.me.charms);
- const check = function (list = [], charms = []) {
- for (let i = 0; i < list.length; i++) {
- if (!charms.some(c => c.gid === list[i])) {
- console.log("A charm was removed from our final list - updated it");
- myData.me.charmGids.remove(list[i]);
- list.splice(i, 1);
- i--;
- changed = true;
- }
- }
- };
-
- for (let i = 0; i < finalCharmKeys.length; i++) {
- let cKey = finalCharmKeys[i];
- switch (myData.me.charms[cKey].classid) {
- case sdk.items.SmallCharm:
- check(myData.me.charms[cKey].have, myCharms);
-
- break;
- case sdk.items.LargeCharm:
- check(myData.me.charms[cKey].have, myCharms);
-
- break;
- case sdk.items.GrandCharm:
- check(myData.me.charms[cKey].have, myCharms);
-
- break;
- }
- }
-
- changed && updateMyData();
-};
-
-Item.autoEquipCharms = function () {
- // No charms in classic
- if (me.classic) return;
-
- console.log("ÿc8Kolbot-SoloPlayÿc0: Entering charm auto equip");
- let tick = getTickCount();
- let totalKeep = [], totalSell = [];
- let GCs = Item.autoEquipGC();
- let LCs = Item.autoEquipLC();
- let SCs = Item.autoEquipSC();
- let specialCharms = me.getItemsEx()
- .filter((charm) => charm.isCharm && charm.unique);
- let verbose = !!(Developer.debugging.smallCharm || Developer.debugging.largeCharm || Developer.debugging.grandCharm);
-
- if (verbose) {
- console.log("Grand Charms Keep: " + GCs.keep.length + ", Sell: " + GCs.sell.length);
- console.log("Large Charms Keep: " + LCs.keep.length + ", Sell: " + LCs.sell.length);
- console.log("Small Charms Keep: " + SCs.keep.length + ", Sell: " + SCs.sell.length);
- }
-
- totalKeep = totalKeep.concat(SCs.keep, LCs.keep, GCs.keep, specialCharms);
- for (let i = 0; i < totalKeep.length; i++) {
- if (!Item.autoEquipCharmCheck(totalKeep[i])) {
- totalSell.push(totalKeep[i]);
- totalKeep.splice(i, 1);
- i--;
- }
- }
- totalSell = totalSell
- .concat(SCs.sell, LCs.sell, GCs.sell)
- .filter((charm) => NTIP.CheckItem(charm, NTIP_CheckListNoTier) === Pickit.Result.UNWANTED);
- totalKeep.length > 0 && console.log("ÿc8Kolbot-SoloPlayÿc0: Total Charms Kept: " + totalKeep.length);
-
- if (totalSell.length > 0) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Total Charms Sell: " + totalSell.length);
-
- for (let i = 0; i < totalSell.length; i++) {
- totalSell[i].isInStash && !getUIFlag(sdk.uiflags.Stash) && Town.openStash();
- if (totalSell[i].isInStash && (!totalSell[i].sellable || !Storage.Inventory.MoveTo(totalSell[i]))) {
- totalSell[i].drop();
- totalSell.splice(i, 1);
- i -= 1;
- }
- }
-
- Town.initNPC("Shop", "clearInventory");
-
- if (getUIFlag(sdk.uiflags.Shop) || (Config.PacketShopping && getInteractedNPC() && getInteractedNPC().itemcount > 0)) {
- for (let i = 0; i < totalSell.length; i++) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Sell old charm " + totalSell[i].name);
- verbose && Misc.itemLogger("Sold", totalSell[i]);
- verbose && Misc.logItem("CharmEquip Sold", totalSell[i]);
- totalSell[i].sell();
- }
- }
- }
-
- if (totalKeep.length > 0) {
- for (let i = 0; i < totalKeep.length; i++) {
- if (totalKeep[i].isInStash && !Cubing.checkItem(totalKeep[i])) {
- !getUIFlag(sdk.uiflags.Stash) && Town.openStash() && delay(300 + me.ping);
- if (Storage.Inventory.CanFit(totalKeep[i]) && Storage.Inventory.MoveTo(totalKeep[i])) {
- verbose && Misc.logItem("CharmEquip Equipped", totalKeep[i]);
- }
- }
- }
- }
-
- me.cancelUIFlags();
-
- console.log("ÿc8Kolbot-SoloPlayÿc0: Exiting charm auto equip. Time elapsed: " + Developer.formatTime(getTickCount() - tick));
-};
-
-// Write charm equip version that checks by item prefix/suffix using a switch case with the various prefixes and suffixes to sort them
-// improve this, not sure how but still needs work
-// maybe do this by prefix/suffix number instead?
-Item.getCharmType = function (charm = undefined) {
- if (!charm || !charm.isCharm) return false;
- if (charm.unique) return "unique";
- if (!NTIP.hasStats(charm) && NTIP.GetCharmTier(charm) > 0) return "misc";
-
- let charmType = "";
- const skillerStats = me.getSkillTabs();
-
- if (charm.getStat(sdk.stats.AddSkillTab, skillerStats[0])) {
- charmType = "skillerTypeA";
- } else if (charm.getStat(sdk.stats.AddSkillTab, skillerStats[1])) {
- charmType = "skillerTypeB";
- } else if (charm.getStat(sdk.stats.AddSkillTab, skillerStats[2])) {
- charmType = "skillerTypeC";
- }
-
- switch (charm.prefix) {
- case "Shimmering":
- case "Azure":
- case "Lapis":
- case "Cobalt":
- case "Sapphire":
- case "Crimson":
- case "Russet":
- case "Garnet":
- case "Ruby":
- case "Tangerine":
- case "Ocher":
- case "Coral":
- case "Amber":
- case "Beryl":
- case "Viridian":
- case "Jade":
- case "Emerald":
- charmType = "resist";
- break;
- }
-
- if (!charmType || charmType === "") {
- switch (charm.suffix) {
- case "of Fortune":
- case "of Good Luck":
- charmType = "magicfind";
- break;
- case "of Life":
- case "of Substinence": // Odd issue, seems to be misspelled wherever item.suffix pulls info from
- case "of Vita":
- charmType = "life";
- break;
- }
- }
-
- if (!charmType || charmType === "") {
- switch (charm.prefix) {
- case "Red":
- case "Sanguinary":
- case "Bloody":
- case "Jagged":
- case "Forked":
- case "Serrated":
- case "Bronze":
- case "Iron":
- case "Steel":
- case "Fine":
- case "Sharp":
- charmType = "damage";
- break;
- case "Snowy":
- case "Shivering":
- case "Boreal":
- case "Hibernal":
- case "Ember":
- case "Smoldering":
- case "Smoking":
- case "Flaming":
- case "Static":
- case "Glowing":
- case "Arcing":
- case "Shocking":
- case "Septic":
- case "Foul":
- case "Toxic":
- case "Pestilant":
- charmType = "elemental";
- break;
- }
- }
-
- if (!charmType || charmType === "") {
- switch (charm.suffix) {
- case "of Craftmanship":
- case "of Quality":
- case "of Maiming":
- charmType = "damage";
- break;
- case "of Strength":
- case "of Dexterity":
- charmType = "stats";
- break;
- case "of Blight":
- case "of Venom":
- case "of Pestilence":
- case "of Anthrax":
- case "of Frost":
- case "of Icicle":
- case "of Glacier":
- case "of Winter":
- case "of Flame":
- case "of Burning":
- case "of Incineration":
- case "of Shock":
- case "of Lightning":
- case "of Thunder":
- case "of Storms":
- charmType = "elemental";
- break;
- }
- }
-
- if (!charmType || charmType === "") {
- switch (charm.prefix) {
- case "Stout":
- case "Burly":
- case "Stalwart":
- charmType = "misc";
- break;
- case "Rugged":
- charmType = "misc";
- break;
- case "Lizard's":
- case "Snake's":
- case "Serpent's":
- charmType = "mana";
- break;
- }
- }
-
- if (!charmType || charmType === "") {
- switch (charm.suffix) {
- case "of Balance":
- case "of Greed":
- case "of Inertia":
- charmType = "misc";
- break;
- }
- }
-
- return charmType;
+Item.logItem = function (action, unit, keptLine, force) {
+ if (!this.useItemLog || unit === undefined || !unit || !unit.fname) return false;
+
+ const keys = ["pk1", "pk2", "pk3"];
+ const organs = ["dhn", "bey", "mbr"];
+ const lowRunes = ["r01", "r02", "r03", "r04", "r05", "r06", "r07", "r08", "r09", "r10", "r11", "r12", "r13", "r14"];
+ const middleRunes = ["r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23"];
+ const highRunes = ["r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31", "r32", "r33"];
+ const lowGems = [
+ "gcv", "gcy", "gcb", "gcg", "gcr",
+ "gcw", "skc", "gfv", "gfy", "gfb",
+ "gfg", "gfr", "gfw", "skf", "gsv",
+ "gsy", "gsb", "gsg", "gsr", "gsw", "sku"
+ ];
+ const highGems = ["gzv", "gly", "glb", "glg", "glr", "glw", "skl", "gpv", "gpy", "gpb", "gpg", "gpr", "gpw", "skz"];
+ if (!Config.LogKeys && keys.includes(unit.code)) return false;
+ if (!Config.LogOrgans && organs.includes(unit.code)) return false;
+ if (!Config.LogLowRunes && lowRunes.includes(unit.code)) return false;
+ if (!Config.LogMiddleRunes && middleRunes.includes(unit.code)) return false;
+ if (!Config.LogHighRunes && highRunes.includes(unit.code)) return false;
+ if (!Config.LogLowGems && lowGems.includes(unit.code)) return false;
+ if (!Config.LogHighGems && highGems.includes(unit.code)) return false;
+
+ for (let skipCode of Config.SkipLogging) {
+ if (skipCode === unit.classid || skipCode === 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));
+ }
+
+ const mercCheck = action.match("Merc");
+ const hasTier = AutoEquip.hasTier(unit);
+ const charmCheck = (unit.isCharm && CharmEquip.check(unit));
+ const nTResult = NTIP.CheckItem(unit, NTIP.CheckList) === 1;
+
+ if (!action.match("kept", "i") && !action.match("Shopped") && hasTier) {
+ if (!mercCheck) {
+ if (unit.isCharm) {
+ NTIP.GetCharmTier(unit) > 0 && (desc += ("\n\\xffc0Autoequip charm tier: " + NTIP.GetCharmTier(unit)));
+ } else {
+ let [mainTier, secTier] = [NTIP.GetTier(unit), NTIP.GetSecondaryTier(unit)];
+ mainTier > 0 && (desc += ("\n\\xffc0Autoequip tier: " + mainTier));
+ secTier > 0 && (desc += ("\n\\xffc0Autoequip Secondary tier: " + secTier));
+ }
+ } else {
+ desc += ("\n\\xffc0Autoequip merc tier: " + NTIP.GetMercTier(unit));
+ }
+ }
+
+ // should stop logging items unless we wish to see them or it's part of normal pickit
+ if (!nTResult && !force) {
+ switch (true) {
+ case (unit.questItem || unit.isBaseType):
+ case (!unit.isCharm && hasTier && !Developer.debugging.autoEquip):
+ case (charmCheck && !Developer.debugging.smallCharm && unit.classid === sdk.items.SmallCharm):
+ case (charmCheck && !Developer.debugging.largeCharm && unit.classid === sdk.items.LargeCharm):
+ case (charmCheck && !Developer.debugging.grandCharm && unit.classid === sdk.items.GrandCharm):
+ return true;
+ default:
+ break;
+ }
+ }
+
+ 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());
+ }
+
+ if (keptLine) {
+ keptLine.includes("[") && (keptLine = keptLine.split("[")[0].trim());
+ desc += ("\n\\xffc0Line: " + keptLine);
+ }
+ desc += "$" + (unit.getFlag(sdk.items.flags.Ethereal) ? ":eth" : "");
+ const formattedDate = new Date().dateStamp().replace(/\//g, "-");
+
+ const itemObj = {
+ title: formattedDate + " " + action.replace(/ÿc[0-9!"+<:;.*]|\/|\\/g, "").trim() + " " + name,
+ description: desc,
+ image: code,
+ textColor: unit.quality,
+ itemColor: color,
+ header: "",
+ sockets: this.getItemSockets(unit)
+ };
+
+ D2Bot.printToItemLog(itemObj);
+
+ return true;
};
const AutoEquip = {
- /**
- * @param {ItemUnit} item
- */
- hasTier: function (item) {
- if (me.classic) return Item.hasTier(item);
- if (item.isCharm) {
- return Item.hasCharmTier(item);
- }
- return Item.hasMercTier(item) || Item.hasTier(item) || Item.hasSecondaryTier(item);
- },
-
- /**
- * @param {ItemUnit} item
- */
- wanted: function (item) {
- if (me.classic) return Item.autoEquipCheck(item, true);
- if (item.isCharm) {
- return Item.autoEquipCharmCheck(item);
- }
- return Item.autoEquipKeepCheckMerc(item) || Item.autoEquipCheck(item, true) || Item.autoEquipCheckSecondary(item);
- },
-
- runAutoEquip: function () {
- Item.autoEquip();
- Item.autoEquipSecondary();
- Item.autoEquipCharms();
- }
+ /**
+ * @param {ItemUnit} item
+ */
+ hasTier: function (item) {
+ if (me.classic) return Item.hasTier(item);
+ if (item.isCharm) {
+ return CharmEquip.hasCharmTier(item);
+ }
+ return Item.hasMercTier(item) || Item.hasTier(item) || Item.hasSecondaryTier(item);
+ },
+
+ /**
+ * @param {ItemUnit} item
+ */
+ wanted: function (item) {
+ if (me.classic) return Item.autoEquipCheck(item, true);
+ if (item.isCharm) {
+ return CharmEquip.check(item);
+ }
+ return Item.autoEquipCheckMerc(item, true) || Item.autoEquipCheck(item, true) || Item.autoEquipCheckSecondary(item);
+ },
+
+ run: function () {
+ console.time("AutoEquip");
+ Item.autoEquip();
+ Item.autoEquipSecondary();
+ CharmEquip.run();
+ console.timeEnd("AutoEquip");
+ }
};
diff --git a/libs/SoloPlay/Functions/ItemPrototypes.js b/libs/SoloPlay/Functions/ItemPrototypes.js
index 7be26fc6..af5e83ba 100644
--- a/libs/SoloPlay/Functions/ItemPrototypes.js
+++ b/libs/SoloPlay/Functions/ItemPrototypes.js
@@ -3,346 +3,223 @@
* @author theBGuy
* @desc Item related prototypes
* @notes Unused, for now at least can't guarantee only units are attempting to use the prototypes
-* me.getItem(sdk.items.runes.Ber).getQuantityOwned() throws an error if we don't have a ber rune
*
*/
includeIfNotIncluded("SoloPlay/Functions/PrototypeOverrides.js");
-Unit.prototype.getQuantityOwned = function (skipSameItem = false) {
- if (this === undefined || this.type !== sdk.unittype.Item) return 0;
-
- return me.getItemsEx()
- .filter(check =>
- check.itemType === this.itemType
- && (!skipSameItem || check.gid !== this.gid)
- && check.classid === this.classid
- && check.quality === this.quality
- && check.sockets === this.sockets
- && check.isInStorage
- ).length;
-};
-
+/** @this {ItemUnit} */
Unit.prototype.hasTier = function () {
- if (this === undefined || this.type !== sdk.unittype.Item) return false;
- return Config.AutoEquip && NTIP.GetTier(this) > 0;
+ if (this.type !== sdk.unittype.Item) return false;
+ return Config.AutoEquip && NTIP.GetTier(this) > 0;
};
+/** @this {ItemUnit} */
Unit.prototype.hasSecondaryTier = function () {
- if (this === undefined || this.type !== sdk.unittype.Item) return false;
- return Config.AutoEquip && NTIP.GetSecondaryTier(this) > 0 && me.expansion;
+ if (this.type !== sdk.unittype.Item) return false;
+ return Config.AutoEquip && NTIP.GetSecondaryTier(this) > 0 && me.expansion;
};
+/** @this {ItemUnit} */
Unit.prototype.hasMercTier = function () {
- if (this === undefined || this.type !== sdk.unittype.Item) return false;
- return Config.AutoEquip && NTIP.GetMercTier(this) > 0 && me.expansion;
-};
-
-Unit.prototype.bodyLocation = function () {
- if (this === undefined || this.type !== sdk.unittype.Item) return [];
-
- let bodyLoc = (() => {
- switch (true) {
- case Item.shieldTypes.includes(this.itemType):
- return sdk.body.LeftArm;
- case this.itemType === sdk.items.type.Armor:
- return sdk.body.Armor;
- case this.itemType === sdk.items.type.Ring:
- return [sdk.body.RingRight, sdk.body.RingLeft];
- case this.itemType === sdk.items.type.Amulet:
- return sdk.body.Neck;
- case this.itemType === sdk.items.type.Boots:
- return sdk.body.Feet;
- case this.itemType === sdk.items.type.Gloves:
- return sdk.body.Gloves;
- case this.itemType === sdk.items.type.Belt:
- return sdk.body.Belt;
- case Item.helmTypes.includes(this.itemType):
- return sdk.body.Head;
- case Item.weaponTypes.includes(this.itemType):
- return me.barbarian ? [sdk.body.RightArm, sdk.body.LeftArm] : sdk.body.RightArm;
- case [sdk.items.type.HandtoHand, sdk.items.type.AssassinClaw].includes(this.itemType):
- return !Check.currentBuild().caster && me.assassin ? [sdk.body.RightArm, sdk.body.LeftArm] : sdk.body.RightArm;
- default:
- return false;
- }
- })();
-
- return Array.isArray(bodyLoc) ? bodyLoc : [bodyLoc];
-};
-
-Unit.prototype.secondaryBodyLocation = function () {
- if (this === undefined || this.type !== sdk.unittype.Item) return [];
-
- let bodyLoc = (() => {
- switch (true) {
- case Item.shieldTypes.includes(this.itemType):
- return sdk.body.LeftArmSecondary;
- case Item.weaponTypes.includes(this.itemType):
- return me.barbarian ? [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary] : sdk.body.RightArmSecondary;
- case [sdk.items.type.HandtoHand, sdk.items.type.AssassinClaw].includes(this.itemType):
- return !Check.currentBuild().caster && me.assassin ? [sdk.body.RightArmSecondary, sdk.body.LeftArmSecondary] : sdk.body.RightArmSecondary;
- default:
- return false;
- }
- })();
-
- return Array.isArray(bodyLoc) ? bodyLoc : [bodyLoc];
-};
-
-Unit.prototype.mercBodyLocation = function () {
- if (this === undefined || this.type !== sdk.unittype.Item) return [];
-
- let mercenary = me.getMercEx();
- if (!mercenary) return [];
-
- let bodyLoc = (() => {
- switch (this.itemType) {
- case sdk.items.type.Shield:
- return (mercenary.classid === sdk.mercs.IronWolf ? sdk.body.LeftArm : []);
- case sdk.items.type.Armor:
- return sdk.body.Armor;
- case sdk.items.type.Helm:
- case sdk.items.type.Circlet:
- return sdk.body.Head;
- case sdk.items.type.PrimalHelm:
- return (mercenary.classid === sdk.mercs.A5Barb ? sdk.body.Head : []);
- case sdk.items.type.Bow:
- return (mercenary.classid === sdk.mercs.Rogue ? sdk.body.RightArm : []);
- case sdk.items.type.Spear:
- case sdk.items.type.Polearm:
- return (mercenary.classid === sdk.mercs.Guard ? sdk.body.RightArm : []);
- case sdk.items.type.Sword:
- return ([sdk.mercs.IronWolf, sdk.mercs.A5Barb].includes(mercenary.classid) ? sdk.body.RightArm : []);
- default:
- return false;
- }
- })();
-
- return Array.isArray(bodyLoc) ? bodyLoc : [bodyLoc];
+ if (this.type !== sdk.unittype.Item) return false;
+ return Config.AutoEquip && NTIP.GetMercTier(this) > 0 && me.expansion;
};
+/** @this {ItemUnit} */
Unit.prototype.canEquip = function () {
- if (this === undefined || this.type !== sdk.unittype.Item || !this.identified) return false;
- return me.charlvl >= this.getStat(sdk.stats.LevelReq) && me.trueStr >= this.strreq && me.trueDex >= this.dexreq;
+ if (this.type !== sdk.unittype.Item || !this.identified) return false;
+ return me.charlvl >= this.lvlreq
+ && me.trueStr >= this.strreq
+ && me.trueDex >= this.dexreq;
};
+/** @this {ItemUnit} */
+Unit.prototype.bodyLocation = function () {
+ if (this.type !== sdk.unittype.Item || this.isInsertable) return [];
+ if (Item.shieldTypes.includes(this.itemType)) return [sdk.body.LeftArm];
+ if (Item.helmTypes.includes(this.itemType)) return [sdk.body.Head];
+ if (Item.weaponTypes.includes(this.itemType)) {
+ return me.barbarian && this.twoHanded && !this.strictlyTwoHanded
+ ? [sdk.body.RightArm, sdk.body.LeftArm]
+ : [sdk.body.RightArm];
+ }
+
+ switch (this.itemType) {
+ case sdk.items.type.Armor:
+ return [sdk.body.Armor];
+ case sdk.items.type.Ring:
+ return [sdk.body.RingRight, sdk.body.RingLeft];
+ case sdk.items.type.Amulet:
+ return [sdk.body.Neck];
+ case sdk.items.type.Boots:
+ return [sdk.body.Feet];
+ case sdk.items.type.Gloves:
+ return [sdk.body.Gloves];
+ case sdk.items.type.Belt:
+ return [sdk.body.Belt];
+ case sdk.items.type.AssassinClaw:
+ case sdk.items.type.HandtoHand:
+ return !Check.currentBuild().caster && me.assassin
+ ? [sdk.body.RightArm, sdk.body.LeftArm] : [sdk.body.RightArm];
+ }
+
+ return [];
+};
+
+/** @this {ItemUnit} */
Unit.prototype.canEquipMerc = function (bodyLoc = -1) {
- if (this === undefined || this.type !== sdk.unittype.Item || !this.identified || me.classic) return false;
- let mercenary = me.getMercEx();
-
- // dont have merc or he is dead
- if (!mercenary) return false;
- let curr = Item.getEquippedItemMerc(bodyLoc);
-
- // Higher requirements
- if (this.getStat(sdk.stats.LevelReq) > mercenary.getStat(sdk.stats.Level)
- || this.dexreq > mercenary.getStat(sdk.stats.Dexterity) - curr.dex
- || this.strreq > mercenary.getStat(sdk.stats.Strength) - curr.str) {
- return false;
- }
-
- return true;
-};
-
-Unit.prototype.autoEquipCheck = function (checkCanEquip = true) {
- if (!Config.AutoEquip) return true;
- if (this === undefined || this.type !== sdk.unittype.Item) return false;
-
- let tier = NTIP.GetTier(this);
- let bodyLoc = this.bodyLocation();
+ if (this.type !== sdk.unittype.Item || !this.identified || me.classic) return false;
+ let mercenary = me.getMercEx();
- if (tier > 0 && bodyLoc.length) {
- for (let i = 0; i < bodyLoc.length; i += 1) {
- if (tier > Item.getEquippedItem(bodyLoc[i]).tier
- && ((checkCanEquip ? this.canEquip() : true) || !this.identified)) {
- if (this.twoHanded && !me.barbarian) {
- if (tier < Item.getEquippedItem(sdk.body.RightArm).tier + Item.getEquippedItem(sdk.body.LeftArm).tier) return false;
- }
+ // dont have merc or he is dead
+ if (!mercenary) return false;
+ let curr = Item.getMercEquipped(bodyLoc);
- if (!me.barbarian && bodyLoc[i] === 5 && Item.getEquippedItem(bodyLoc[i]).tier === -1) {
- if (Item.getEquippedItem(sdk.body.RightArm).twoHanded && tier < Item.getEquippedItem(sdk.body.RightArm).tier) return false;
- }
+ // Higher requirements
+ if (this.getStat(sdk.stats.LevelReq) > mercenary.getStat(sdk.stats.Level)
+ || this.dexreq > mercenary.getStat(sdk.stats.Dexterity) - curr.dex
+ || this.strreq > mercenary.getStat(sdk.stats.Strength) - curr.str) {
+ return false;
+ }
- return true;
- }
- }
- }
-
- return false;
-};
-
-Unit.prototype.autoEquipCheckSecondary = function (checkCanEquip = true) {
- if (!Config.AutoEquip) return true;
- if (this === undefined || this.type !== sdk.unittype.Item || me.classic) return false;
-
- let tier = NTIP.GetSecondaryTier(this);
- let bodyLoc = this.secondaryBodyLocation();
-
- if (tier > 0 && bodyLoc.length) {
- for (let i = 0; i < bodyLoc.length; i += 1) {
- if (tier > Item.getEquippedItem(bodyLoc[i]).secondarytier
- && ((checkCanEquip ? this.canEquip() : true) || !this.identified)) {
- if (this.twoHanded && !me.barbarian) {
- if (tier < Item.getEquippedItem(sdk.body.RightArmSecondary).secondarytier + Item.getEquippedItem(sdk.body.LeftArmSecondary).secondarytier) return false;
- }
-
- if (!me.barbarian && bodyLoc[i] === 12 && Item.getEquippedItem(bodyLoc[i]).secondarytier === -1) {
- if (Item.getEquippedItem(sdk.body.RightArmSecondary).twoHanded && tier < Item.getEquippedItem(sdk.body.RightArmSecondary).secondarytier) return false;
- }
-
- return true;
- }
- }
- }
-
- return false;
-};
-
-Unit.prototype.autoEquipCheckMerc = function (checkCanEquip = true) {
- if (!Config.AutoEquip) return true;
- if (this === undefined || this.type !== sdk.unittype.Item || me.classic || !me.getMercEx()) return false;
-
- let tier = NTIP.GetMercTier(this);
- let bodyLoc = this.mercBodyLocation();
-
- if (tier > 0 && bodyLoc.length) {
- for (let i = 0; i < bodyLoc.length; i += 1) {
- let oldTier = Item.getEquippedItemMerc(bodyLoc[i]).tier;
-
- if (tier > oldTier && ((checkCanEquip ? this.canEquipMerc() : true) || !this.identified)) {
- return true;
- }
- }
- }
-
- return false;
+ return true;
};
+/** @this {ItemUnit} */
Unit.prototype.shouldKeep = function () {
- if (this === undefined || this.type !== sdk.unittype.Item) return false;
-
- if (Pickit.checkItem(this).result === 1
- // only keep wanted items or cubing items (in rare cases where weapon being used is also a cubing wanted item)
- || (this.unique && Pickit.checkItem(this).result === 2)
- // or keep if item is worth selling
- || (this.getItemCost(sdk.items.cost.ToSell) / (this.sizex * this.sizey) >= (!me.inTown && me.charlvl < 12 ? 5 : me.normal ? 50 : me.nightmare ? 500 : 1000))) {
- if ((Storage.Inventory.CanFit(this) && Storage.Inventory.MoveTo(this))) {
- !AutoEquip.wanted(this) && NTIP.CheckItem(this, NTIP_CheckListNoTier) === Pickit.Result.UNWANTED && Town.sell.push(this);
- return true;
- }
- }
-
- return false;
-};
-
+ if (this === undefined || this.type !== sdk.unittype.Item) return false;
+
+ if (Pickit.checkItem(this).result === 1
+ // only keep wanted items or cubing items (in rare cases where weapon being used is also a cubing wanted item)
+ || (this.unique && Pickit.checkItem(this).result === 2)
+ // or keep if item is worth selling
+ || (
+ (this.getItemCost(sdk.items.cost.ToSell) / (this.sizex * this.sizey))
+ >= (!me.inTown && me.charlvl < 12 ? 5 : me.normal ? 50 : me.nightmare ? 500 : 1000)
+ )) {
+ if ((Storage.Inventory.CanFit(this) && Storage.Inventory.MoveTo(this))) {
+ if (!AutoEquip.wanted(this) && NTIP.CheckItem(this, NTIP.CheckList) === Pickit.Result.UNWANTED) {
+ Town.sell.push(this);
+ }
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/** @this {ItemUnit} */
Unit.prototype.equipItem = function (bodyLoc = -1) {
- // can't equip
- if (this === undefined || this.type !== sdk.unittype.Item || !this.canEquip()) return false;
- bodyLoc === -1 && (bodyLoc = this.bodyLocation().first());
- // Already equipped in the right slot
- if (this.mode === sdk.items.mode.Equipped && this.bodylocation === bodyLoc) return true;
- // failed to open stash
- if (this.isInStash && !Town.openStash()) return false;
- // failed to open cube
- if (this.isInCube && !Cubing.openCube()) return false;
-
- let rolledBack = false;
- // todo: sometimes rings get bugged with the higher tier ring ending up on the wrong finger, if this happens swap them
-
- for (let i = 0; i < 3; i += 1) {
- if (this.toCursor()) {
- clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc);
-
- if (this.bodylocation === bodyLoc) {
- if (getCursorType() === 3) {
- let cursorItem = Game.getCursorUnit();
-
- if (cursorItem) {
- // rollback check
- let justEquipped = Item.getEquippedItem(bodyLoc);
- if (NTIP.GetTier(cursorItem) > justEquipped.tier && !this.questItem && !justEquipped.isRuneword/*Wierd bug with runewords that it'll fail to get correct item desc so don't attempt rollback*/) {
- console.debug("ROLLING BACK TO OLD ITEM BECAUSE IT WAS BETTER");
- console.debug("OldItem: " + NTIP.GetTier(cursorItem) + " Just Equipped Item: " + Item.getEquippedItem(bodyLoc).tier);
- clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc);
- cursorItem = Game.getCursorUnit();
- rolledBack = true;
- }
-
- !this.shouldKeep() && this.drop();
- }
- }
-
- return rolledBack ? false : true;
- }
- }
- }
-
- return false;
+ // can't equip
+ if (this === undefined || this.type !== sdk.unittype.Item || !this.canEquip()) return false;
+ bodyLoc === -1 && (bodyLoc = this.bodyLocation().first());
+ // Already equipped in the right slot
+ if (this.mode === sdk.items.mode.Equipped && this.bodylocation === bodyLoc) return true;
+ // failed to open stash
+ if (this.isInStash && !Town.openStash()) return false;
+ // failed to open cube
+ if (this.isInCube && !Cubing.openCube()) return false;
+
+ let rolledBack = false;
+ // todo: sometimes rings get bugged with the higher tier ring ending up on the wrong finger, if this happens swap them
+
+ for (let i = 0; i < 3; i += 1) {
+ if (this.toCursor()) {
+ clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc);
+
+ if (this.bodylocation === bodyLoc) {
+ if (getCursorType() === 3) {
+ let cursorItem = Game.getCursorUnit();
+
+ if (cursorItem) {
+ // rollback check
+ let justEquipped = me.equipped.get(bodyLoc);
+ if (NTIP.GetTier(cursorItem) > justEquipped.tier
+ && !this.questItem
+ /*Wierd bug with runewords that it'll fail to get correct item desc so don't attempt rollback*/
+ && !justEquipped.isRuneword) {
+ console.debug("ROLLING BACK TO OLD ITEM BECAUSE IT WAS BETTER");
+ console.debug("OldItem: " + NTIP.GetTier(cursorItem) + " Just Equipped Item: " + me.equipped.get(bodyLoc).tier);
+ clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc);
+ cursorItem = Game.getCursorUnit();
+ rolledBack = true;
+ }
+
+ !this.shouldKeep() && this.drop();
+ }
+ }
+
+ return rolledBack ? false : true;
+ }
+ }
+ }
+
+ return false;
};
Unit.prototype.secondaryEquip = function (bodyLoc = -1) {
- // can't equip
- if (this === undefined || this.type !== sdk.unittype.Item || (!this.canEquip() && me.expansion)) return false;
- bodyLoc === -1 && (bodyLoc = this.secondaryBodyLocation().first());
- // Already equipped in the right slot
- if (this.mode === sdk.items.mode.Equipped && this.bodylocation === bodyLoc) return true;
- // failed to open stash
- if (this.isInStash && !Town.openStash()) return false;
- // failed to open cube
- if (this.isInCube && !Cubing.openCube()) return false;
-
- me.switchWeapons(1);
-
- for (let i = 0; i < 3; i += 1) {
- if (this.toCursor()) {
- clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc - 7);
-
- if (this.bodylocation === bodyLoc - 7) {
- if (getCursorType() === 3) {
- let cursorItem = Game.getCursorUnit();
- !!cursorItem && !this.shouldKeep() && this.drop();
- }
-
- me.switchWeapons(0);
- return true;
- }
- }
- }
-
- me.weaponswitch !== 0 && me.switchWeapons(0);
-
- return false;
+ // can't equip
+ if (this === undefined || this.type !== sdk.unittype.Item || (!this.canEquip() && me.expansion)) return false;
+ bodyLoc === -1 && (bodyLoc = this.secondaryBodyLocation().first());
+ // Already equipped in the right slot
+ if (this.mode === sdk.items.mode.Equipped && this.bodylocation === bodyLoc) return true;
+ // failed to open stash
+ if (this.isInStash && !Town.openStash()) return false;
+ // failed to open cube
+ if (this.isInCube && !Cubing.openCube()) return false;
+
+ me.switchWeapons(1);
+
+ for (let i = 0; i < 3; i += 1) {
+ if (this.toCursor()) {
+ clickItemAndWait(sdk.clicktypes.click.item.Left, bodyLoc - 7);
+
+ if (this.bodylocation === bodyLoc - 7) {
+ if (getCursorType() === 3) {
+ let cursorItem = Game.getCursorUnit();
+ !!cursorItem && !this.shouldKeep() && this.drop();
+ }
+
+ me.switchWeapons(0);
+ return true;
+ }
+ }
+ }
+
+ me.weaponswitch !== 0 && me.switchWeapons(0);
+
+ return false;
};
Unit.prototype.equipMerc = function (bodyLoc = -1) {
- // can't equip
- if (this === undefined || this.type !== sdk.unittype.Item || !this.canEquipMerc()) return false;
- bodyLoc === -1 && (bodyLoc = this.mercBodyLocation().first());
- // Already equipped in the right slot
- if (this.mode === sdk.items.mode.Equipped && this.bodylocation === bodyLoc) return true;
- // failed to open stash
- if (this.isInStash && !Town.openStash()) return false;
- // failed to open cube
- if (this.isInCube && !Cubing.openCube()) return false;
-
- for (let i = 0; i < 3; i += 1) {
- if (this.toCursor()) {
- clickItemAndWait(sdk.clicktypes.click.item.Mercenary, bodyLoc);
-
- if (this.bodylocation === bodyLoc) {
- if (getCursorType() === 3) {
- let cursorItem = Game.getCursorUnit();
- !!cursorItem && !this.shouldKeep() && this.drop();
- }
-
- Developer.debugging.autoEquip && Misc.logItem("Merc Equipped", me.getMerc().getItem(this.classid));
- Developer.logEquipped && MuleLogger.logEquippedItems();
-
- return true;
- }
- }
- }
-
- return false;
+ // can't equip
+ if (this === undefined || this.type !== sdk.unittype.Item || !this.canEquipMerc()) return false;
+ bodyLoc === -1 && (bodyLoc = this.mercBodyLocation().first());
+ // Already equipped in the right slot
+ if (this.mode === sdk.items.mode.Equipped && this.bodylocation === bodyLoc) return true;
+ // failed to open stash
+ if (this.isInStash && !Town.openStash()) return false;
+ // failed to open cube
+ if (this.isInCube && !Cubing.openCube()) return false;
+
+ for (let i = 0; i < 3; i += 1) {
+ if (this.toCursor()) {
+ clickItemAndWait(sdk.clicktypes.click.item.Mercenary, bodyLoc);
+
+ if (this.bodylocation === bodyLoc) {
+ if (getCursorType() === 3) {
+ let cursorItem = Game.getCursorUnit();
+ !!cursorItem && !this.shouldKeep() && this.drop();
+ }
+
+ Developer.debugging.autoEquip && Item.logItem("Merc Equipped", me.getMerc().getItem(this.classid));
+ Developer.logEquipped && MuleLogger.logEquippedItems();
+
+ return true;
+ }
+ }
+ }
+
+ return false;
};
diff --git a/libs/SoloPlay/Functions/ItemUtilities.js b/libs/SoloPlay/Functions/ItemUtilities.js
index fe357970..5dde6d3a 100644
--- a/libs/SoloPlay/Functions/ItemUtilities.js
+++ b/libs/SoloPlay/Functions/ItemUtilities.js
@@ -5,490 +5,593 @@
*
*/
-includeIfNotIncluded("common/Item.js");
-
-// TODO: clean this up (sigh) - 8/10/22 - update refactored alot, still think more can be done though
-const baseSkillsScore = (item, buildInfo) => {
- buildInfo === undefined && (buildInfo = Check.currentBuild());
- let generalScore = 0;
- let selectedWeights = [30, 20];
- let selectedSkills = [buildInfo.wantedSkills, buildInfo.usefulSkills];
- generalScore += item.getStatEx(sdk.stats.AddClassSkills, me.classid) * 200; // + class skills
- generalScore += item.getStatEx(sdk.stats.AddSkillTab, buildInfo.tabSkills) * 100; // + TAB skills - todo handle array of tab skills
-
- for (let i = 0; i < selectedWeights.length; i++) {
- for (let j = 0; j < selectedSkills.length; j++) {
- for (let k = 0; k < selectedSkills[j].length; k++) {
- generalScore += item.getStatEx(107, selectedSkills[j][k]) * selectedWeights[i];
- }
- }
- }
- return generalScore;
-};
-
-Item.betterBaseThanWearing = function (base = undefined, verbose = Developer.debugging.baseCheck) {
- if (!base || !base.isBaseType) return false;
-
- let name = "";
- let itemsResists, baseResists, itemsTotalDmg, baseDmg, itemsDefense, baseDefense;
- let baseSkillsTier, equippedSkillsTier;
- let result = false, preSocketCheck = false;
- let bodyLoc = Item.getBodyLoc(base);
-
- const checkNoSockets = (item) => [
- sdk.locale.items.AncientsPledge, sdk.locale.items.Exile, sdk.locale.items.Lore, sdk.locale.items.White, sdk.locale.items.Rhyme
- ].includes(item.prefixnum) || (item.prefixnum === sdk.locale.items.Spirit && item.getItemType() === "Shield");
- const getRes = (item) => item.getStatEx(sdk.stats.FireResist) + item.getStatEx(sdk.stats.LightResist) + item.getStatEx(sdk.stats.ColdResist) + item.getStatEx(sdk.stats.PoisonResist);
- const getDmg = (item) => item.getStatEx(sdk.stats.MinDamage) + item.getStatEx(sdk.stats.MaxDamage);
- const getRealDmg = (item, maxED = 0, minOffset = 0, maxOffset = 0) => {
- let ED = item.getStatEx(sdk.stats.EnhancedDamage);
- ED > maxED && (ED = maxED);
- let itemsMinDmg = Math.ceil(((item.getStatEx(sdk.stats.MinDamage) - minOffset) / ((ED + 100) / 100)));
- let itemsMaxDmg = Math.ceil(((item.getStatEx(sdk.stats.MaxDamage) - maxOffset) / ((ED + 100) / 100)));
- return (itemsMinDmg + itemsMaxDmg);
- };
- const getDef = (item) => item.getStatEx(sdk.stats.Defense);
- const getRealDef = (item, maxEDef) => {
- let ED = item.getStatEx(sdk.stats.ArmorPercent);
- ED > maxEDef && (ED = maxEDef);
- return (Math.ceil((item.getStatEx(sdk.stats.Defense) / ((ED + 100) / 100))));
- };
- const resCheck = (baseResists, itemsResists) => {
- verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(" + name + ") BaseResists: " + baseResists + " EquippedItem: " + itemsResists);
- return (baseResists > itemsResists);
- };
- const defCheck = (itemsDefense, baseDefense) => {
- verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(" + name + ") BaseDefense: " + baseDefense + " EquippedItem: " + itemsDefense);
- return (baseDefense > itemsDefense);
- };
- const dmgCheck = (itemsTotalDmg, baseDmg) => {
- verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(" + name + ") BaseDamage: " + baseDmg + " EquippedItem: " + itemsTotalDmg);
- return (baseDmg > itemsTotalDmg);
- };
-
- // @todo - betterThanMercUsing check for now just keep merc items
- if ([sdk.items.type.Polearm, sdk.items.type.Spear].includes(base.itemType) || ([sdk.items.type.Armor].includes(base.itemType) && base.ethereal)) return true;
- // Can't use so its worse then what we already have
- if ((Check.finalBuild().maxStr < base.strreq || Check.finalBuild().maxDex < base.dexreq)) {
- console.log("ÿc9BetterThanWearingCheckÿc0 :: " + base.name + " has to high stat requirments strReq: " + base.strreq + " dexReq " + base.dexreq);
- return false;
- }
- // don't toss pb base crescent moon/HoJ/Grief
- if (base.classid === sdk.items.PhaseBlade && [3, 4, 5].includes(base.sockets)) return true;
-
- let items = me.getItemsEx().filter(i => i.isEquipped && bodyLoc.includes(i.bodylocation));
-
- for (let i = 0; i < bodyLoc.length; i++) {
- let equippedItem = items.find(item => item.bodylocation === bodyLoc[i]);
- if (!equippedItem || !equippedItem.runeword || NTIP.GetTier(equippedItem) >= NTIP.MAX_TIER) {
- if (i === 0 && bodyLoc.length > 1) continue;
- return true;
- }
- name = getLocaleString(equippedItem.prefixnum);
-
- preSocketCheck = checkNoSockets(equippedItem);
- if (base.sockets === 0 && !preSocketCheck) return true;
-
- if (base.sockets === equippedItem.sockets || preSocketCheck) {
- switch (equippedItem.prefixnum) {
- case sdk.locale.items.AncientsPledge:
- if (me.paladin) {
- [itemsResists, baseResists] = [(getRes(equippedItem) - 187), getRes(base)];
- if (baseResists !== itemsResists && resCheck(baseResists, itemsResists)) return true;
- } else {
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 50), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
- }
-
- break;
- case sdk.locale.items.Black:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 120), getDmg(base)];
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.CrescentMoon:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 220), getDmg(base)];
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.Exile:
- [itemsResists, baseResists] = [getRes(equippedItem), getRes(base)];
- if (baseResists !== itemsResists && resCheck(baseResists, itemsResists)) return true;
-
- break;
- case sdk.locale.items.Honor:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 160, 9, 9), getDmg(base)];
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.KingsGrace:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 100), getDmg(base)];
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.Lawbringer:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem), getDmg(base)];
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.Lore:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem), getDef(base)];
-
- if (me.barbarian || me.druid) {
- // (PrimalHelms and Pelts)
- [equippedSkillsTier, baseSkillsTier] = [baseSkillsScore(equippedItem), baseSkillsScore(base)];
-
- verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(Lore) EquippedSkillsTier: " + equippedSkillsTier + " BaseSkillsTier: " + baseSkillsTier);
- if (equippedSkillsTier !== baseSkillsTier) {
- // Might need to add some type of std deviation, having the skills is probably better but maybe not if in hell with a 50 defense helm
- return (baseSkillsTier > equippedSkillsTier);
- } else if (baseDefense !== itemsDefense) {
- verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(Lore) BaseDefense: " + baseDefense + " EquippedItem: " + itemsDefense);
- return (baseDefense > itemsDefense);
- }
- } else {
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
- }
-
- break;
- case sdk.locale.items.Malice:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 33, 9), getDmg(base)];
-
- if (me.paladin) {
- // Paladin TODO: See if its worth it to calculate the added damage skills would add
- [equippedSkillsTier, baseSkillsTier] = [baseSkillsScore(equippedItem), baseSkillsScore(base)];
- }
-
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.Rhyme:
- if (me.necromancer) {
- [equippedSkillsTier, baseSkillsTier] = [baseSkillsScore(equippedItem), baseSkillsScore(base)];
-
- if (equippedSkillsTier !== baseSkillsTier) {
- verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(Rhyme) EquippedSkillsTier: " + equippedSkillsTier + " BaseSkillsTier: " + baseSkillsTier);
- // Might need to add some type of std deviation, having the skills is probably better but maybe not if in hell with a 50 defense shield
- if (baseSkillsTier > equippedSkillsTier) return true;
- } else if (equippedSkillsTier === baseSkillsTier) {
- [itemsDefense, baseDefense] = [getRealDef(equippedItem), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
- }
- } else if (me.paladin) {
- [itemsResists, baseResists] = [(getRes(equippedItem) - 100), getRes(base)];
- if (baseResists !== itemsResists && resCheck(baseResists, itemsResists)) return true;
- }
-
- break;
- case sdk.locale.items.Rift:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem), getDmg(base)];
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.Spirit:
- if (!me.paladin || bodyLoc[i] !== sdk.body.LeftArm || base.getItemType() !== "Shield") return true;
-
- [itemsResists, baseResists] = [(getRes(equippedItem) - 115), getRes(base)];
- if (baseResists !== itemsResists && resCheck(baseResists, itemsResists)) return true;
-
- break;
- case sdk.locale.items.Steel:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 20, 3, 3), getDmg(base)];
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.Strength:
- [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 35, 9, 9), getDmg(base)];
- if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
-
- break;
- case sdk.locale.items.White:
- if (me.necromancer) {
- [equippedSkillsTier, baseSkillsTier] = [(baseSkillsScore(equippedItem) - 550), baseSkillsScore(base)];
-
- if (equippedSkillsTier !== baseSkillsTier) {
- verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(White) EquippedSkillsTier: " + equippedSkillsTier + " BaseSkillsTier: " + baseSkillsTier);
- if (baseSkillsTier > equippedSkillsTier) return true;
- }
- }
-
- break;
- case sdk.locale.items.Stone:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 290), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- case sdk.locale.items.Smoke:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 75), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- case sdk.locale.items.Prudence:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 170), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- case sdk.locale.items.Gloom:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 260), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- case sdk.locale.items.Fortitude:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 200), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- case sdk.locale.items.Enlightenment:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 30), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- case sdk.locale.items.Duress:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 200), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- case sdk.locale.items.ChainsofHonor:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem, 70), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- case sdk.locale.items.Bramble:
- case sdk.locale.items.Bone:
- case sdk.locale.items.Dragon:
- case sdk.locale.items.Enigma:
- case sdk.locale.items.Lionheart:
- case sdk.locale.items.Myth:
- case sdk.locale.items.Peace:
- case sdk.locale.items.Principle:
- case sdk.locale.items.Rain:
- case sdk.locale.items.Stealth:
- case sdk.locale.items.Treachery:
- case sdk.locale.items.Wealth:
- [itemsDefense, baseDefense] = [getRealDef(equippedItem), getDef(base)];
- if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
-
- break;
- default:
- // Runeword base isn't in the list, keep the base
- return true;
- }
- }
- }
-
- return result;
-};
-
-Item.betterThanStashed = function (base, verbose) {
- if (!base || base.quality > sdk.items.quality.Superior || base.isRuneword) return false;
- if (base.sockets === 0 && getBaseStat("items", base.classid, "gemsockets") <= 1) return false;
- if (base.sockets === 1) return false;
- verbose === undefined && (verbose = Developer.debugging.baseCheck);
-
- const defenseScore = (item) => ({
- def: item.getStatEx(sdk.stats.Defense),
- eDef: item.getStatEx(sdk.stats.ArmorPercent)
- });
-
- const dmgScore = (item) => ({
- dmg: Math.round((item.getStatEx(sdk.stats.MinDamagePercent) + item.getStatEx(sdk.stats.MaxDamagePercent)) / 2),
- twoHandDmg: Math.round((item.getStatEx(sdk.stats.SecondaryMinDamage) + item.getStatEx(sdk.stats.SecondaryMaxDamage)) / 2),
- eDmg: item.getStatEx(sdk.stats.EnhancedDamage)
- });
-
- const generalScore = (item) => {
- const buildInfo = Check.currentBuild();
- let generalScore = baseSkillsScore(item, buildInfo);
-
- if (generalScore === 0) {
- // should take into account other skills that would be helpful for the current build
- me.paladin && (generalScore += item.getStatEx(sdk.stats.FireResist) * 2);
- generalScore += item.getStatEx(sdk.stats.Defense) * 0.5;
-
- if (!buildInfo.caster) {
- let dmg = dmgScore(item);
- generalScore += (dmg.dmg + dmg.eDmg);
- }
- }
-
- return generalScore;
- };
-
- /**
- * @todo - better comparison for socketed items to unsocketed items
- * - should compare items with same sockets
- * - should compare some items (wands, voodooheads, primalhelms, pelts) with no sockets to ones with sockets
- * - should be able to compare regular items to class items and take into account the max amount of sockets an item can have
- *
- */
- function getItemToCompare (itemtypes = [], eth = null, sort = (a, b) => a - b) {
- return me.getItemsEx(-1, sdk.items.mode.inStorage)
- .filter(item =>
- itemtypes.includes(item.itemType)
- && ((item.sockets === base.sockets) || (item.sockets > base.sockets))
- && (eth === null || item.ethereal === eth))
- .sort(sort)
- .last();
- }
-
- const defenseSort = (a, b) => {
- let [aDef, bDef] = [a.getStatEx(sdk.stats.Defense), b.getStatEx(sdk.stats.Defense)];
- if (aDef !== bDef) return aDef - bDef;
- return a.getStatEx(sdk.stats.ArmorPercent) - b.getStatEx(sdk.stats.ArmorPercent);
- };
-
- const generalScoreSort = (a, b) => {
- let [aScore, bScore] = [generalScore(a), generalScore(b)];
- if (aScore !== bScore) return aScore - bScore;
- let [aSoc, bSoc] = [a.sockets, b.sockets];
- if (aSoc !== bSoc) return aSoc - bSoc;
- if (a.getItemType() !== "Weapon" && aScore === bScore) {
- let [aDef, bDef] = [a.getStatEx(sdk.stats.Defense), b.getStatEx(sdk.stats.Defense)];
- if (aDef !== bDef) return aDef - bDef;
- if (aDef === bDef) a.getStatEx(sdk.stats.ArmorPercent) - b.getStatEx(sdk.stats.ArmorPercent);
- }
- return a.sockets - b.sockets;
- };
-
- const twoHandDmgSort = (a, b) => {
- let [aDmg, bDmg] = [dmgScore(a), dmgScore(b)];
- if (aDmg.twoHandDmg !== bDmg.twoHandDmg) return aDmg.twoHandDmg - bDmg.twoHandDmg;
- return aDmg.eDmg - bDmg.eDmg;
- };
-
- const defenseScoreCheck = (base, itemToCheck) => {
- let [defScoreBase, defScoreItem] = [defenseScore(base), defenseScore(itemToCheck)];
- verbose && console.log("ÿc9betterThanStashedÿc0 :: BaseScore: ", defScoreBase, " itemToCheckScore: ", defScoreItem);
- if (defScoreBase.def > defScoreItem.def || (defScoreBase.def === defScoreItem.def && (defScoreBase.eDef > defScoreItem.eDef || base.ilvl > itemToCheck.ilvl))) {
- return true;
- }
- return false;
- };
-
- const damageScoreCheck = (base, itemToCheck) => {
- let [gScoreBase, gScoreCheck] = [generalScore(base), generalScore(itemToCheck)];
- verbose && console.log("ÿc9betterThanStashedÿc0 :: BaseScore: " + generalScore(base) + " itemToCheckScore: " + generalScore(itemToCheck));
- switch (true) {
- case (gScoreBase > gScoreCheck || (gScoreBase === gScoreCheck && base.ilvl > itemToCheck.ilvl)):
- case (me.barbarian && (gScoreBase === gScoreCheck && Item.getQuantityOwned(base) < 2)):
- case (me.assassin && !Check.currentBuild().caster && (gScoreBase === gScoreCheck && Item.getQuantityOwned(base) < 2)):
- return true;
- }
- return false;
- };
-
- const generalScoreCheck = (base, itemToCheck) => {
- let [gScoreBase, gScoreCheck] = [generalScore(base), generalScore(itemToCheck)];
- verbose && console.log("ÿc9betterThanStashedÿc0 :: BaseScore: " + generalScore(base) + " itemToCheckScore: " + generalScore(itemToCheck));
- if (gScoreBase > gScoreCheck) return true;
- if (base.getItemType() === "Shield" && gScoreBase === gScoreCheck) return defenseScoreCheck(base, itemToCheck);
- return false;
- };
-
- let checkItem;
-
- switch (base.itemType) {
- case sdk.items.type.Shield:
- case sdk.items.type.AuricShields:
- case sdk.items.type.VoodooHeads:
- if (me.paladin || me.necromancer) {
- let iType = [sdk.items.type.Shield];
- me.necromancer ? iType.push(sdk.items.type.VoodooHeads) : iType.push(sdk.items.type.AuricShields);
-
- checkItem = getItemToCompare(iType, false, generalScoreSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
-
- return (base.isInStorage ? generalScoreCheck(base, checkItem) : false);
- }
-
- if (base.ethereal || base.sockets === 0) return false;
- checkItem = getItemToCompare([sdk.items.type.Shield], false, defenseSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
-
- return (base.isInStorage ? defenseScoreCheck(base, checkItem) : false);
- case sdk.items.type.Armor:
- if (base.ethereal || base.sockets === 0) return false;
- checkItem = getItemToCompare([sdk.items.type.Armor], false, defenseSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
-
- return (base.isInStorage ? defenseScoreCheck(base, checkItem) : false);
- case sdk.items.type.Helm:
- case sdk.items.type.PrimalHelm:
- case sdk.items.type.Circlet:
- case sdk.items.type.Pelt:
- if (me.barbarian || me.druid) {
- let iType = [sdk.items.type.Helm, sdk.items.type.Circlet];
- me.druid ? iType.push(sdk.items.type.Pelt) : iType.push(sdk.items.type.PrimalHelm);
-
- checkItem = getItemToCompare(iType, false, generalScoreSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
-
- return (base.isInStorage ? generalScoreCheck(base, checkItem) : false);
- }
-
- if (base.ethereal || base.sockets === 0) return false;
- checkItem = getItemToCompare([sdk.items.type.Helm, sdk.items.type.Circlet], false, defenseSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
-
- return (base.isInStorage ? defenseScoreCheck(base, checkItem) : false);
- case sdk.items.type.Wand:
- if (!me.necromancer) return false;
-
- checkItem = getItemToCompare([sdk.items.type.Wand], null, generalScoreSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
- return (base.isInStorage ? generalScoreCheck(base, checkItem) : false);
- case sdk.items.type.Scepter:
- 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.Crossbow:
- case sdk.items.type.Mace:
- case sdk.items.type.Orb:
- case sdk.items.type.AmazonBow:
- case sdk.items.type.AmazonSpear:
- // don't toss grief base
- // Can't use so it's worse then what we already have
- // todo - need better solution to know what the max stats are for our current build and wanted final build
- // update - 8/8/2022 - checks final build stat requirements but still need a better check
- // don't keep an item if we are only going to be able to use it when we get to our final build but also sometimes like paladin making grief
- // we need the item to get to our final build but won't actually be able to use it till then so we can't just use max current build str/dex
- if ((Check.finalBuild().maxStr < base.strreq || Check.finalBuild().maxDex < base.dexreq)) return false;
- // need better solution for comparison based on what runeword can be made in a base type
- // should allow comparing multiple item types given they are all for the same runeword
- checkItem = getItemToCompare([base.itemType], false, generalScoreSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
-
- return (base.isInStorage ? damageScoreCheck(base, checkItem) : false);
- case sdk.items.type.HandtoHand:
- case sdk.items.type.AssassinClaw:
- if (!me.assassin) return false;
-
- checkItem = getItemToCompare([sdk.items.type.HandtoHand, sdk.items.type.AssassinClaw], false, generalScoreSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
-
- return (base.isInStorage ? damageScoreCheck(base, checkItem) : false);
- case sdk.items.type.Polearm:
- checkItem = getItemToCompare([sdk.items.type.Polearm], null, twoHandDmgSort);
- if (checkItem === undefined || checkItem.gid === base.gid) return true;
-
- if (base.isInStorage && base.sockets > 0) {
- let [baseDmg, checkItemDmg] = [dmgScore(base), dmgScore(checkItem)];
- switch (true) {
- case (baseDmg.twoHandDmg > checkItemDmg.twoHandDmg):
- case ((baseDmg.twoHandDmg === checkItemDmg.twoHandDmg) && (baseDmg.eDmg > checkItemDmg.eDmg)):
- case ((baseDmg.twoHandDmg === checkItemDmg.twoHandDmg) && (baseDmg.eDmg === checkItemDmg.eDmg) && base.ilvl > checkItem.ilvl):
- verbose && console.log("ÿc9betterThanStashedÿc0 :: BaseScore: ", baseDmg, " itemToCheckScore: ", checkItemDmg);
- return true;
- }
- }
-
- break;
- }
-
- return false;
-};
+includeIfNotIncluded("core/Item.js");
+(function () {
+ /**
+ * @param {ItemUnit} item
+ * @param {Build} buildInfo
+ * @returns {number}
+ * @todo clean this up (sigh) - 8/10/22 - update refactored alot, still think more can be done though
+ */
+ const baseSkillsScore = function (item, buildInfo) {
+ buildInfo === undefined && (buildInfo = Check.currentBuild());
+ let generalScore = 0;
+ let selectedWeights = [30, 20];
+ let selectedSkills = [buildInfo.wantedSkills, buildInfo.usefulSkills];
+ generalScore += item.getStatEx(sdk.stats.AddClassSkills, me.classid) * 200; // + class skills
+ generalScore += item.getStatEx(sdk.stats.AddSkillTab, buildInfo.tabSkills) * 100; // + TAB skills - todo handle array of tab skills
+
+ for (let i = 0; i < selectedWeights.length; i++) {
+ for (let j = 0; j < selectedSkills.length; j++) {
+ for (let k = 0; k < selectedSkills[j].length; k++) {
+ generalScore += item.getStatEx(107, selectedSkills[j][k]) * selectedWeights[i];
+ }
+ }
+ }
+ return generalScore;
+ };
+
+ /**
+ * @param {ItemUnit} base
+ * @param {boolean} verbose
+ * @returns {boolean}
+ */
+ Item.betterBaseThanWearing = function (base, verbose = Developer.debugging.baseCheck) {
+ if (!base || !base.isBaseType) return false;
+
+ let name = "";
+ let itemsResists, baseResists, itemsTotalDmg, baseDmg, itemsDefense, baseDefense;
+ let baseSkillsTier, equippedSkillsTier;
+ let result = false, preSocketCheck = false;
+ let bodyLoc = Item.getBodyLoc(base);
+
+ /** @param {ItemUnit} item */
+ const checkNoSockets = function (item) {
+ return item.normal && [
+ sdk.locale.items.AncientsPledge,
+ sdk.locale.items.Exile,
+ sdk.locale.items.Lore,
+ sdk.locale.items.White,
+ sdk.locale.items.Rhyme
+ ].includes(item.prefixnum) || (item.prefixnum === sdk.locale.items.Spirit && item.getItemType() === "Shield");
+ };
+ /** @param {ItemUnit} item */
+ const getRes = function (item) {
+ return (
+ item.getStatEx(sdk.stats.FireResist) + item.getStatEx(sdk.stats.LightResist)
+ + item.getStatEx(sdk.stats.ColdResist) + item.getStatEx(sdk.stats.PoisonResist)
+ );
+ };
+ /** @param {ItemUnit} item */
+ const getDmg = function (item) {
+ return item.getStatEx(sdk.stats.MinDamage) + item.getStatEx(sdk.stats.MaxDamage);
+ };
+ /** @param {ItemUnit} item */
+ const getRealDmg = function (item, maxED = 0, minOffset = 0, maxOffset = 0) {
+ let ED = item.getStatEx(sdk.stats.EnhancedDamage);
+ ED > maxED && (ED = maxED);
+ let itemsMinDmg = Math.ceil(((item.getStatEx(sdk.stats.MinDamage) - minOffset) / ((ED + 100) / 100)));
+ let itemsMaxDmg = Math.ceil(((item.getStatEx(sdk.stats.MaxDamage) - maxOffset) / ((ED + 100) / 100)));
+ return (itemsMinDmg + itemsMaxDmg);
+ };
+ /** @param {ItemUnit} item */
+ const getDef = function (item) {
+ return item.getStatEx(sdk.stats.Defense);
+ };
+ /** @param {ItemUnit} item */
+ const getRealDef = function (item, maxEDef) {
+ let ED = item.getStatEx(sdk.stats.ArmorPercent);
+ ED > maxEDef && (ED = maxEDef);
+ return (Math.ceil((item.getStatEx(sdk.stats.Defense) / ((ED + 100) / 100))));
+ };
+ const resCheck = function (baseResists, itemsResists) {
+ if (verbose) {
+ console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(" + name + ") BaseResists: " + baseResists + " EquippedItem: " + itemsResists);
+ }
+ return (baseResists > itemsResists);
+ };
+ const defCheck = function (itemsDefense, baseDefense) {
+ if (verbose) {
+ console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(" + name + ") BaseDefense: " + baseDefense + " EquippedItem: " + itemsDefense);
+ }
+ return (baseDefense > itemsDefense);
+ };
+ const dmgCheck = function (itemsTotalDmg, baseDmg) {
+ if (verbose) {
+ console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(" + name + ") BaseDamage: " + baseDmg + " EquippedItem: " + itemsTotalDmg);
+ }
+ return (baseDmg > itemsTotalDmg);
+ };
+
+ // @todo - betterThanMercUsing check for now just keep merc items
+ if ([sdk.items.type.Polearm, sdk.items.type.Spear].includes(base.itemType)
+ || ([sdk.items.type.Armor].includes(base.itemType) && base.ethereal)) {
+ let merc = me.getMercEx();
+ if (merc) {
+ bodyLoc = Item.getBodyLocMerc(base);
+ let eqItem = merc.getItemsEx().filter(i => i.isEquipped && bodyLoc.includes(i.bodylocation));
+ if (!eqItem || !eqItem.runeword || NTIP.GetMercTier(eqItem) >= NTIP.MAX_TIER) return true;
+ name = getLocaleString(eqItem.prefixnum);
+ // todo logic checking before this to ensure we aren't keeping extra merc stuff
+ if (base.sockets === 0) return true;
+ switch (equippedItem.prefixnum) {
+ case sdk.locale.items.Insight:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 260), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.Infinity:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 325), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.Treachery:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ default:
+ return true;
+ }
+ return false;
+ }
+ }
+ // Can't use so its worse then what we already have
+ if ((Check.finalBuild().maxStr < base.strreq || Check.finalBuild().maxDex < base.dexreq)) {
+ console.log("ÿc9BetterThanWearingCheckÿc0 :: " + base.name + " has to high stat requirments strReq: " + base.strreq + " dexReq " + base.dexreq);
+ return false;
+ }
+ // don't toss pb base crescent moon/HoJ/Grief
+ if (base.classid === sdk.items.PhaseBlade && [3, 4, 5].includes(base.sockets)) return true;
+
+ let items = me.getItemsEx()
+ .filter(function (i) {
+ return i.isEquipped && bodyLoc.includes(i.bodylocation);
+ });
+
+ for (let i = 0; i < bodyLoc.length; i++) {
+ let equippedItem = items.find(item => item.bodylocation === bodyLoc[i]);
+ if (!equippedItem || !equippedItem.runeword /* || NTIP.GetTier(equippedItem) >= NTIP.MAX_TIER */) {
+ if (i === 0 && bodyLoc.length > 1) continue;
+ return true;
+ }
+ name = getLocaleString(equippedItem.prefixnum);
+
+ preSocketCheck = checkNoSockets(equippedItem);
+ if (base.sockets === 0 && !preSocketCheck) return true;
+
+ if (base.sockets === equippedItem.sockets || preSocketCheck) {
+ switch (equippedItem.prefixnum) {
+ case sdk.locale.items.AncientsPledge:
+ if (me.paladin) {
+ [itemsResists, baseResists] = [(getRes(equippedItem) - 187), getRes(base)];
+ if (baseResists !== itemsResists && resCheck(baseResists, itemsResists)) return true;
+ } else {
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 50), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+ }
+
+ break;
+ case sdk.locale.items.Black:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 120), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.CrescentMoon:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 220), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.Exile:
+ [itemsResists, baseResists] = [getRes(equippedItem), getRes(base)];
+ if (baseResists !== itemsResists && resCheck(baseResists, itemsResists)) return true;
+
+ break;
+ case sdk.locale.items.Honor:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 160, 9, 9), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.KingsGrace:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 100), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.Lawbringer:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.Lore:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem), getDef(base)];
+
+ if (me.barbarian || me.druid) {
+ // (PrimalHelms and Pelts)
+ [equippedSkillsTier, baseSkillsTier] = [baseSkillsScore(equippedItem), baseSkillsScore(base)];
+
+ verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(Lore) EquippedSkillsTier: " + equippedSkillsTier + " BaseSkillsTier: " + baseSkillsTier);
+ if (equippedSkillsTier !== baseSkillsTier) {
+ // Might need to add some type of std deviation, having the skills is probably better but maybe not if in hell with a 50 defense helm
+ return (baseSkillsTier > equippedSkillsTier);
+ } else if (baseDefense !== itemsDefense) {
+ verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(Lore) BaseDefense: " + baseDefense + " EquippedItem: " + itemsDefense);
+ return (baseDefense > itemsDefense);
+ }
+ } else {
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+ }
+
+ break;
+ case sdk.locale.items.Malice:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 33, 9), getDmg(base)];
+
+ if (me.paladin) {
+ // Paladin TODO: See if its worth it to calculate the added damage skills would add
+ [equippedSkillsTier, baseSkillsTier] = [baseSkillsScore(equippedItem), baseSkillsScore(base)];
+ }
+
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.Rhyme:
+ if (me.necromancer) {
+ [equippedSkillsTier, baseSkillsTier] = [baseSkillsScore(equippedItem), baseSkillsScore(base)];
+
+ if (equippedSkillsTier !== baseSkillsTier) {
+ verbose && console.log("ÿc9BetterThanWearingCheckÿc0 :: RW(Rhyme) EquippedSkillsTier: " + equippedSkillsTier + " BaseSkillsTier: " + baseSkillsTier);
+ // Might need to add some type of std deviation, having the skills is probably better but maybe not if in hell with a 50 defense shield
+ if (baseSkillsTier > equippedSkillsTier) return true;
+ } else if (equippedSkillsTier === baseSkillsTier) {
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+ }
+ } else if (me.paladin) {
+ [itemsResists, baseResists] = [(getRes(equippedItem) - 100), getRes(base)];
+ if (baseResists !== itemsResists && resCheck(baseResists, itemsResists)) return true;
+ }
+
+ break;
+ case sdk.locale.items.Rift:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.Spirit:
+ if (!me.paladin || bodyLoc[i] !== sdk.body.LeftArm || base.getItemType() !== "Shield") return true;
+
+ [itemsResists, baseResists] = [(getRes(equippedItem) - 115), getRes(base)];
+ if (baseResists !== itemsResists && resCheck(baseResists, itemsResists)) return true;
+
+ break;
+ case sdk.locale.items.Steel:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 20, 3, 3), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.Strength:
+ [itemsTotalDmg, baseDmg] = [getRealDmg(equippedItem, 35, 9, 9), getDmg(base)];
+ if (baseDmg !== itemsTotalDmg && dmgCheck(itemsTotalDmg, baseDmg)) return true;
+
+ break;
+ case sdk.locale.items.White:
+ if (me.necromancer) {
+ [equippedSkillsTier, baseSkillsTier] = [(baseSkillsScore(equippedItem) - 550), baseSkillsScore(base)];
+
+ if (equippedSkillsTier !== baseSkillsTier) {
+ if (verbose) {
+ console.debug(
+ "ÿc0 :: RW(White) EquippedSkillsTier: " + equippedSkillsTier
+ + " BaseSkillsTier: " + baseSkillsTier
+ );
+ }
+ if (baseSkillsTier > equippedSkillsTier) return true;
+ }
+ }
+
+ break;
+ case sdk.locale.items.Stone:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 290), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ case sdk.locale.items.Smoke:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 75), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ case sdk.locale.items.Prudence:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 170), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ case sdk.locale.items.Gloom:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 260), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ case sdk.locale.items.Fortitude:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 200), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ case sdk.locale.items.Enlightenment:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 30), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ case sdk.locale.items.Duress:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 200), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ case sdk.locale.items.ChainsofHonor:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem, 70), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ case sdk.locale.items.Bramble:
+ case sdk.locale.items.Bone:
+ case sdk.locale.items.Dragon:
+ case sdk.locale.items.Enigma:
+ case sdk.locale.items.Lionheart:
+ case sdk.locale.items.Myth:
+ case sdk.locale.items.Peace:
+ case sdk.locale.items.Principle:
+ case sdk.locale.items.Rain:
+ case sdk.locale.items.Stealth:
+ case sdk.locale.items.Treachery:
+ case sdk.locale.items.Wealth:
+ [itemsDefense, baseDefense] = [getRealDef(equippedItem), getDef(base)];
+ if (baseDefense !== itemsDefense && defCheck(itemsDefense, baseDefense)) return true;
+
+ break;
+ default:
+ // Runeword base isn't in the list, keep the base
+ return true;
+ }
+ }
+ }
+
+ return result;
+ };
+
+ /**
+ * @param {ItemUnit} base
+ * @param {boolean} verbose
+ */
+ Item.betterThanStashed = function (base, verbose) {
+ if (!base || base.quality > sdk.items.quality.Superior || base.isRuneword) return false;
+ if (base.sockets === 0 && getBaseStat("items", base.classid, "gemsockets") <= 1) return false;
+ if (base.sockets === 1) return false;
+ verbose === undefined && (verbose = Developer.debugging.baseCheck);
+
+ /** @param {ItemUnit} item */
+ const defenseScore = (item) => ({
+ def: item.getStatEx(sdk.stats.Defense),
+ eDef: item.getStatEx(sdk.stats.ArmorPercent)
+ });
+
+ /** @param {ItemUnit} item */
+ const dmgScore = (item) => ({
+ dmg: Math.round((item.getStatEx(sdk.stats.MinDamagePercent) + item.getStatEx(sdk.stats.MaxDamagePercent)) / 2),
+ twoHandDmg: Math.round((item.getStatEx(sdk.stats.SecondaryMinDamage) + item.getStatEx(sdk.stats.SecondaryMaxDamage)) / 2),
+ eDmg: item.getStatEx(sdk.stats.EnhancedDamage)
+ });
+
+ /** @param {ItemUnit} item */
+ const generalScore = (item) => {
+ const buildInfo = Check.currentBuild();
+ let generalScore = baseSkillsScore(item, buildInfo);
+
+ if (generalScore === 0) {
+ // should take into account other skills that would be helpful for the current build
+ me.paladin && (generalScore += item.getStatEx(sdk.stats.FireResist) * 2);
+ generalScore += item.getStatEx(sdk.stats.Defense) * 0.5;
+
+ if (!buildInfo.caster) {
+ let dmg = dmgScore(item);
+ generalScore += (dmg.dmg + dmg.eDmg);
+ }
+ }
+
+ return generalScore;
+ };
+
+ /**
+ * @todo - better comparison for socketed items to unsocketed items
+ * - should compare items with same sockets
+ * - should compare some items (wands, voodooheads, primalhelms, pelts) with no sockets to ones with sockets
+ * - should be able to compare regular items to class items and take into account the max amount of sockets an item can have
+ *
+ */
+ function getItemToCompare (itemtypes = [], eth = null, sort = (a, b) => a - b) {
+ return me.getItemsEx(-1, sdk.items.mode.inStorage)
+ .filter(item =>
+ itemtypes.includes(item.itemType)
+ && ((item.sockets === base.sockets) || (item.sockets > base.sockets))
+ && (eth === null || item.ethereal === eth))
+ .sort(sort)
+ .last();
+ }
+
+ /**
+ * @param {ItemUnit} a
+ * @param {ItemUnit} b
+ */
+ const defenseSort = function (a, b) {
+ let [aDef, bDef] = [a.getStatEx(sdk.stats.Defense), b.getStatEx(sdk.stats.Defense)];
+ if (aDef !== bDef) return aDef - bDef;
+ return a.getStatEx(sdk.stats.ArmorPercent) - b.getStatEx(sdk.stats.ArmorPercent);
+ };
+
+ /**
+ * @param {ItemUnit} a
+ * @param {ItemUnit} b
+ */
+ const generalScoreSort = function (a, b) {
+ let [aScore, bScore] = [generalScore(a), generalScore(b)];
+ if (aScore !== bScore) return aScore - bScore;
+ let [aSoc, bSoc] = [a.sockets, b.sockets];
+ if (aSoc !== bSoc) return aSoc - bSoc;
+ if (a.getItemType() !== "Weapon" && aScore === bScore) {
+ let [aDef, bDef] = [a.getStatEx(sdk.stats.Defense), b.getStatEx(sdk.stats.Defense)];
+ if (aDef !== bDef) return aDef - bDef;
+ if (aDef === bDef) a.getStatEx(sdk.stats.ArmorPercent) - b.getStatEx(sdk.stats.ArmorPercent);
+ }
+ return a.sockets - b.sockets;
+ };
+
+ /**
+ * @param {ItemUnit} a
+ * @param {ItemUnit} b
+ */
+ const twoHandDmgSort = function (a, b) {
+ let [aDmg, bDmg] = [dmgScore(a), dmgScore(b)];
+ if (aDmg.twoHandDmg !== bDmg.twoHandDmg) return aDmg.twoHandDmg - bDmg.twoHandDmg;
+ return aDmg.eDmg - bDmg.eDmg;
+ };
+
+ const defenseScoreCheck = function (base, itemToCheck) {
+ let [defScoreBase, defScoreItem] = [defenseScore(base), defenseScore(itemToCheck)];
+ if (verbose) {
+ console.log("ÿc9betterThanStashedÿc0 :: BaseScore: ", defScoreBase, " itemToCheckScore: ", defScoreItem);
+ }
+ if (defScoreBase.def > defScoreItem.def
+ || (defScoreBase.def === defScoreItem.def
+ && (defScoreBase.eDef > defScoreItem.eDef || base.ilvl > itemToCheck.ilvl))) {
+ return true;
+ }
+ return false;
+ };
+
+ const damageScoreCheck = function (base, itemToCheck) {
+ let [gScoreBase, gScoreCheck] = [generalScore(base), generalScore(itemToCheck)];
+ if (verbose) {
+ console.log("ÿc9betterThanStashedÿc0 :: BaseScore: " + generalScore(base) + " itemToCheckScore: " + generalScore(itemToCheck));
+ }
+ switch (true) {
+ case (gScoreBase > gScoreCheck || (gScoreBase === gScoreCheck && base.ilvl > itemToCheck.ilvl)):
+ case (me.barbarian && (gScoreBase === gScoreCheck && me.getOwned(base).length < 2)):
+ case (me.assassin && !Check.currentBuild().caster
+ && (gScoreBase === gScoreCheck && me.getOwned(base).length < 2)):
+ return true;
+ }
+ return false;
+ };
+
+ const generalScoreCheck = function (base, itemToCheck) {
+ let [gScoreBase, gScoreCheck] = [generalScore(base), generalScore(itemToCheck)];
+ verbose && console.log("ÿc9betterThanStashedÿc0 :: BaseScore: " + generalScore(base) + " itemToCheckScore: " + generalScore(itemToCheck));
+ if (gScoreBase > gScoreCheck) return true;
+ if (base.getItemType() === "Shield" && gScoreBase === gScoreCheck) return defenseScoreCheck(base, itemToCheck);
+ return false;
+ };
+
+ let checkItem;
+
+ switch (base.itemType) {
+ case sdk.items.type.Shield:
+ case sdk.items.type.AuricShields:
+ case sdk.items.type.VoodooHeads:
+ if (me.paladin || me.necromancer) {
+ let iType = [sdk.items.type.Shield];
+ me.necromancer ? iType.push(sdk.items.type.VoodooHeads) : iType.push(sdk.items.type.AuricShields);
+
+ checkItem = getItemToCompare(iType, false, generalScoreSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+
+ return (base.isInStorage ? generalScoreCheck(base, checkItem) : false);
+ }
+
+ if (base.ethereal || base.sockets === 0) return false;
+ checkItem = getItemToCompare([sdk.items.type.Shield], false, defenseSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+
+ return (base.isInStorage ? defenseScoreCheck(base, checkItem) : false);
+ case sdk.items.type.Armor:
+ if (base.ethereal || base.sockets === 0) return false;
+ checkItem = getItemToCompare([sdk.items.type.Armor], false, defenseSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+
+ return (base.isInStorage ? defenseScoreCheck(base, checkItem) : false);
+ case sdk.items.type.Helm:
+ case sdk.items.type.PrimalHelm:
+ case sdk.items.type.Circlet:
+ case sdk.items.type.Pelt:
+ if (me.barbarian || me.druid) {
+ let iType = [sdk.items.type.Helm, sdk.items.type.Circlet];
+ me.druid ? iType.push(sdk.items.type.Pelt) : iType.push(sdk.items.type.PrimalHelm);
+
+ checkItem = getItemToCompare(iType, false, generalScoreSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+
+ return (base.isInStorage ? generalScoreCheck(base, checkItem) : false);
+ }
+
+ if (base.ethereal || base.sockets === 0) return false;
+ checkItem = getItemToCompare([sdk.items.type.Helm, sdk.items.type.Circlet], false, defenseSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+
+ return (base.isInStorage ? defenseScoreCheck(base, checkItem) : false);
+ case sdk.items.type.Wand:
+ if (!me.necromancer) return false;
+
+ checkItem = getItemToCompare([sdk.items.type.Wand], null, generalScoreSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+ return (base.isInStorage ? generalScoreCheck(base, checkItem) : false);
+ case sdk.items.type.Scepter:
+ 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.Crossbow:
+ case sdk.items.type.Mace:
+ case sdk.items.type.Orb:
+ case sdk.items.type.AmazonBow:
+ case sdk.items.type.AmazonSpear:
+ // don't toss grief base
+ // Can't use so it's worse then what we already have
+ // todo - need better solution to know what the max stats are for our current build and wanted final build
+ // update - 8/8/2022 - checks final build stat requirements but still need a better check
+ // don't keep an item if we are only going to be able to use it when we get to our final build but also sometimes like paladin making grief
+ // we need the item to get to our final build but won't actually be able to use it till then so we can't just use max current build str/dex
+ if ((Check.finalBuild().maxStr < base.strreq || Check.finalBuild().maxDex < base.dexreq)) return false;
+ // need better solution for comparison based on what runeword can be made in a base type
+ // should allow comparing multiple item types given they are all for the same runeword
+ checkItem = getItemToCompare([base.itemType], false, generalScoreSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+
+ return (base.isInStorage ? damageScoreCheck(base, checkItem) : false);
+ case sdk.items.type.HandtoHand:
+ case sdk.items.type.AssassinClaw:
+ if (!me.assassin) return false;
+
+ checkItem = getItemToCompare([sdk.items.type.HandtoHand, sdk.items.type.AssassinClaw], false, generalScoreSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+
+ return (base.isInStorage ? damageScoreCheck(base, checkItem) : false);
+ case sdk.items.type.Polearm:
+ checkItem = getItemToCompare([sdk.items.type.Polearm], null, twoHandDmgSort);
+ if (checkItem === undefined || checkItem.gid === base.gid) return true;
+
+ if (base.isInStorage && base.sockets > 0) {
+ let [baseDmg, checkItemDmg] = [dmgScore(base), dmgScore(checkItem)];
+ switch (true) {
+ case (baseDmg.twoHandDmg > checkItemDmg.twoHandDmg):
+ case ((baseDmg.twoHandDmg === checkItemDmg.twoHandDmg) && (baseDmg.eDmg > checkItemDmg.eDmg)):
+ case ((baseDmg.twoHandDmg === checkItemDmg.twoHandDmg) && (baseDmg.eDmg === checkItemDmg.eDmg) && base.ilvl > checkItem.ilvl):
+ verbose && console.log("ÿc9betterThanStashedÿc0 :: BaseScore: ", baseDmg, " itemToCheckScore: ", checkItemDmg);
+ return true;
+ }
+ }
+
+ break;
+ }
+
+ return false;
+ };
+})();
diff --git a/libs/SoloPlay/Functions/LoaderOverrides.js b/libs/SoloPlay/Functions/LoaderOverrides.js
index b26167b5..3db38681 100644
--- a/libs/SoloPlay/Functions/LoaderOverrides.js
+++ b/libs/SoloPlay/Functions/LoaderOverrides.js
@@ -1,244 +1,185 @@
/**
* @filename LoaderOverrides.js
-* @author kolton
-* @desc script loader, based on mBot's Sequencer.js, modifed by theBGuy for SoloPlay
+* @author theBGuy
+* @credit kolton
+* @desc script loader, based on mBot's Sequencer.js
*
*/
-// TODO: make this a loader for the actual scripts run by SoloPlay rather than the just the SoloPlay base script
-
-includeIfNotIncluded("common/Loader.js");
+includeIfNotIncluded("core/Loader.js");
Loader.getScripts = function () {
- let fileList = dopen("libs/SoloPlay/Scripts").getFiles();
+ let fileList = dopen("libs/SoloPlay/Scripts").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")));
- }
- }
+ 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")));
+ }
+ }
};
Loader.scriptName = function (offset = 0) {
- let index = this.scriptIndex + offset;
+ let index = this.scriptIndex + offset;
- if (index >= 0 && index < SoloIndex.scripts.length) {
- return SoloIndex.scripts[index];
- }
+ if (index >= 0 && index < SoloIndex.scripts.length) {
+ return SoloIndex.scripts[index];
+ }
- return "SoloPlay";
+ return "SoloPlay";
};
+/**
+ * @deprecated Loader.run is used instead
+ */
Loader.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) < 0) {
- if (FileTools.exists("libs/SoloPlay/" + 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("SoloPlay/" + script + ".js")) {
- Misc.errorReport("Failed to include script: " + script);
- continue;
- }
-
- if (isIncluded("SoloPlay/" + script + ".js")) {
- try {
- if (typeof (global[script]) !== "function") throw new Error("Invalid script function name");
-
- if (this.skipTown.includes(script) || Town.goToTown()) {
- console.log("ÿc2Starting script: ÿc9" + script);
- Messaging.sendToScript("libs/SoloPlay/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();
-
- if (global[script]()) {
- console.log("ÿc7" + script + " :: ÿc0Complete ÿc0- ÿc7Duration: ÿc0" + (new Date(getTickCount() - tick).toISOString().slice(11, -5)));
- }
- }
- } 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) {
- console.log("ÿc2Reverting back unmodified config properties.");
- this.copy(unmodifiedConfig, Config);
- }
- }
- }
- }
+ return Loader.run();
};
Loader.run = function () {
- let updatedDifficulty = Check.nextDifficulty();
- if (updatedDifficulty) {
- CharData.updateData("me", "setDifficulty", updatedDifficulty);
- !me.realm && Messaging.sendToScript("D2BotSoloPlay.dbj", "diffChange");
- }
-
- for (this.scriptIndex = 0; this.scriptIndex < SoloIndex.scripts.length; this.scriptIndex++) {
- !me.inTown && Town.goToTown();
- Check.checkSpecialCase();
- const scriptName = SoloIndex.scripts[this.scriptIndex];
-
- if (SoloIndex.index[scriptName] !== undefined && SoloIndex.index[scriptName].shouldRun()) {
- let j;
- let tick;
- let currentExp;
-
- try {
- includeIfNotIncluded("SoloPlay/Scripts/" + scriptName + ".js");
-
- tick = getTickCount();
- currentExp = me.getStat(sdk.stats.Experience);
- Messaging.sendToScript("libs/SoloPlay/Threads/ToolsThread.js", JSON.stringify({currScript: scriptName}));
- DataFile.updateStats("lastScript", scriptName);
-
- for (j = 0; j < 5; j += 1) {
- if (global[scriptName]()) {
- break;
- }
- }
-
- (j === 5) && myPrint("script " + scriptName + " failed.");
- } catch (e) {
- console.warn("ÿc8Kolbot-SoloPlayÿc0: ", (typeof e === "object" ? e.message : e));
- console.error(e);
- } finally {
- SoloIndex.doneList.push(scriptName);
- // skip logging if we didn't actually finish it
- !SoloIndex.retryList.includes(scriptName) && Developer.logPerformance && Tracker.script(tick, scriptName, currentExp);
- console.log("ÿc8Kolbot-SoloPlayÿc0: Old maxgametime: " + Developer.formatTime(me.maxgametime));
- me.maxgametime += (getTickCount() - tick);
- console.log("ÿc8Kolbot-SoloPlayÿc0: New maxgametime: " + Developer.formatTime(me.maxgametime));
- console.log("ÿc8Kolbot-SoloPlayÿc0 :: ÿc8" + scriptName + "ÿc0 - ÿc7Duration: ÿc0" + Developer.formatTime(getTickCount() - tick));
-
- // remove script function from function scope, so it can be cleared by GC
- if (this.scriptIndex < SoloIndex.scripts.length) {
- delete global[scriptName];
- }
- }
-
- if (me.sorceress && me.hell && scriptName === "bloodraven" && me.charlvl < 68) {
- console.debug("End-run, we are not ready to keep pushing yet");
-
- break;
- }
-
- if (me.dead) {
- // not sure how we got here but we are dead, why did toolsthread not quit lets check it
- let tThread = getScript("libs/SoloPlay/Threads/ToolsThread.js");
- if (!tThread || !tThread.running) {
- // well that explains why, toolsthread seems to have crashed lets restart it so we quit properly
- load("libs/SoloPlay/Threads/ToolsThread.js");
- }
- }
- }
- }
-
- // Re-check to see if after this run we now meet difficulty requirments
- if (!updatedDifficulty) {
- updatedDifficulty = Check.nextDifficulty(false);
- if (updatedDifficulty) {
- CharData.updateData("me", "setDifficulty", updatedDifficulty);
- !me.realm && Messaging.sendToScript("D2BotSoloPlay.dbj", "diffChange");
- }
- }
-
- return true;
+ let updatedDifficulty = Check.nextDifficulty();
+ const _toolsThread = "libs/SoloPlay/Threads/ToolsThread.js";
+ if (updatedDifficulty) {
+ CharData.updateData("me", "setDifficulty", updatedDifficulty);
+ !me.realm && Messaging.sendToScript("D2BotSoloPlay.dbj", "diffChange");
+ }
+
+ for (this.scriptIndex = 0; this.scriptIndex < SoloIndex.scripts.length; this.scriptIndex++) {
+ const scriptName = SoloIndex.scripts[this.scriptIndex];
+ !me.inTown && !Loader.skipTown.includes(scriptName) && Town.goToTown();
+ Check.checkSpecialCase();
+ if (!SoloIndex.index[scriptName]) continue;
+ if (!SoloIndex.index[scriptName].shouldRun()) continue;
+
+ let j;
+ let tick;
+ let currentExp;
+
+ try {
+ includeIfNotIncluded("SoloPlay/Scripts/" + scriptName + ".js");
+
+ tick = getTickCount();
+ currentExp = me.getStat(sdk.stats.Experience);
+ Messaging.sendToScript(_toolsThread, JSON.stringify({ currScript: scriptName }));
+ DataFile.updateStats("lastScript", scriptName);
+
+ for (j = 0; j < 5; j += 1) {
+ if (global[scriptName]()) {
+ break;
+ }
+ }
+
+ (j === 5) && myPrint("script " + scriptName + " failed.");
+ } catch (e) {
+ console.error(e);
+ } finally {
+ SoloIndex.doneList.push(scriptName);
+ // skip logging if we didn't actually finish it
+ if (!SoloIndex.retryList.includes(scriptName) && Developer.logPerformance) {
+ Tracker.script(tick, scriptName, currentExp);
+ }
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Old maxgametime: " + Time.format(me.maxgametime));
+ me.maxgametime += (getTickCount() - tick);
+ console.log("ÿc8Kolbot-SoloPlayÿc0: New maxgametime: " + Time.format(me.maxgametime));
+ console.log(
+ "ÿc8Kolbot-SoloPlayÿc0 :: ÿc8" + scriptName
+ + "ÿc0 - ÿc7Duration: ÿc0" + Time.format(getTickCount() - tick)
+ );
+
+ // remove script function from function scope, so it can be cleared by GC
+ if (this.scriptIndex < SoloIndex.scripts.length) {
+ delete global[scriptName];
+ }
+ }
+
+ if (me.sorceress && me.hell && scriptName === "bloodraven" && me.charlvl < 68) {
+ console.info(false, "End-run, we are not ready to keep pushing yet");
+
+ break;
+ }
+
+ if (me.dead) {
+ // not sure how we got here but we are dead, why did toolsthread not quit lets check it
+ let tThread = getScript("libs/SoloPlay/Threads/ToolsThread.js");
+ if (!tThread || !tThread.running) {
+ // well that explains why, toolsthread seems to have crashed lets restart it so we quit properly
+ load("libs/SoloPlay/Threads/ToolsThread.js");
+ }
+ }
+ }
+
+ // Re-check to see if after this run we now meet difficulty requirments
+ if (!updatedDifficulty) {
+ updatedDifficulty = Check.nextDifficulty(false);
+ if (updatedDifficulty) {
+ CharData.updateData("me", "setDifficulty", updatedDifficulty);
+ !me.realm && Messaging.sendToScript("D2BotSoloPlay.dbj", "diffChange");
+ }
+ }
+
+ return true;
};
Loader.runScript = function (script, configOverride) {
- let tick;
- let currentExp;
- let failed = false;
- let reconfiguration, unmodifiedConfig = {};
- 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 (includeIfNotIncluded("SoloPlay/Scripts/" + 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);
- console.log(mainScriptStr + "ÿc2Starting script: ÿc9" + script);
- Messaging.sendToScript("libs/SoloPlay/Threads/ToolsThread.js", JSON.stringify({currScript: script}));
- DataFile.updateStats("lastScript", script);
-
- if (typeof configOverride === "function") {
- reconfiguration = true;
- configOverride();
- }
-
- tick = getTickCount();
- currentExp = me.getStat(sdk.stats.Experience);
-
- if (global[script]()) {
- console.log(mainScriptStr + "ÿc7" + script + " :: ÿc0Complete ÿc0- ÿc7Duration: ÿc0" + (Time.format(getTickCount() - tick)));
- }
- }
- } catch (e) {
- failed = true;
- console.warn("ÿc8Kolbot-SoloPlayÿc0: " + (e.message ? e.message : e));
- } finally {
- SoloIndex.doneList.push(script);
- Developer.logPerformance && Tracker.script(tick, script, currentExp);
- delete global[script];
- this.tempList.pop();
-
- if (reconfiguration) {
- console.log("ÿc2Reverting back unmodified config properties.");
- this.copy(unmodifiedConfig, Config);
- }
- }
- } else {
- console.warn("Failed to include: " + script);
- }
-
- return !failed;
+ let tick;
+ let currentExp;
+ let failed = false;
+ let reconfiguration, unmodifiedConfig = {};
+ 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 (includeIfNotIncluded("SoloPlay/Scripts/" + 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);
+ console.log(mainScriptStr + "ÿc2Starting script: ÿc9" + script);
+ Messaging.sendToScript("libs/SoloPlay/Threads/ToolsThread.js", JSON.stringify({ currScript: script }));
+ DataFile.updateStats("lastScript", script);
+
+ if (typeof configOverride === "function") {
+ reconfiguration = true;
+ configOverride();
+ }
+
+ tick = getTickCount();
+ currentExp = me.getStat(sdk.stats.Experience);
+
+ if (global[script]()) {
+ console.log(
+ mainScriptStr + "ÿc7" + script
+ + " :: ÿc0Complete ÿc0- ÿc7Duration: ÿc0" + (Time.format(getTickCount() - tick))
+ );
+ }
+ }
+ } catch (e) {
+ failed = true;
+ console.warn("ÿc8Kolbot-SoloPlayÿc0: " + (e.message ? e.message : e));
+ } finally {
+ SoloIndex.doneList.push(script);
+ Developer.logPerformance && Tracker.script(tick, script, currentExp);
+ delete global[script];
+ this.tempList.pop();
+
+ if (reconfiguration) {
+ console.log("ÿc2Reverting back unmodified config properties.");
+ this.copy(unmodifiedConfig, Config);
+ }
+ }
+ } else {
+ console.warn("Failed to include: " + script);
+ }
+
+ return !failed;
};
diff --git a/libs/SoloPlay/Functions/Me.js b/libs/SoloPlay/Functions/Me.js
index 563b69d8..b6d09ac4 100644
--- a/libs/SoloPlay/Functions/Me.js
+++ b/libs/SoloPlay/Functions/Me.js
@@ -5,166 +5,503 @@
*
*/
-includeIfNotIncluded("common/Prototypes.js");
+includeIfNotIncluded("core/Prototypes.js");
/**
* @description me prototypes for soloplay with checks to ensure forwards compatibility
*/
if (!me.hasOwnProperty("maxNearMonsters")) {
- Object.defineProperty(me, "maxNearMonsters", {
- get: function () {
- return Math.floor((4 * (1 / me.hpmax * me.hp)) + 1);
- }
- });
+ Object.defineProperty(me, "maxNearMonsters", {
+ get: function () {
+ return Math.floor((4 * (1 / me.hpmax * me.hp)) + 1);
+ }
+ });
}
if (!me.hasOwnProperty("dualWielding")) {
- Object.defineProperty(me, "dualWielding", {
- get: function () {
- // only classes that can duel wield
- if (!me.assassin && !me.barbarian) return false;
- let items = me.getItemsEx().filter((item) => item.isEquipped && item.isOnMain);
- return !!items.length && items.length >= 2 && items.every((item) => !item.isShield && !getBaseStat("items", item.classid, "block"));
- }
- });
+ Object.defineProperty(me, "dualWielding", {
+ get: function () {
+ // only classes that can duel wield
+ if (!me.assassin && !me.barbarian) return false;
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item.isEquipped && item.isOnMain;
+ });
+ return items.length >= 2
+ && items.every(function (item) {
+ return !item.isShield && !getBaseStat("items", item.classid, "block");
+ });
+ }
+ });
}
if (!me.hasOwnProperty("realFR")) {
- Object.defineProperty(me, "realFR", {
- get: function () {
- return me.getStat(sdk.stats.FireResist);
- }
- });
+ Object.defineProperty(me, "realFR", {
+ get: function () {
+ return me.getStat(sdk.stats.FireResist);
+ }
+ });
}
if (!me.hasOwnProperty("realCR")) {
- Object.defineProperty(me, "realCR", {
- get: function () {
- return me.getStat(sdk.stats.ColdResist);
- }
- });
+ Object.defineProperty(me, "realCR", {
+ get: function () {
+ return me.getStat(sdk.stats.ColdResist);
+ }
+ });
}
if (!me.hasOwnProperty("realLR")) {
- Object.defineProperty(me, "realLR", {
- get: function () {
- return me.getStat(sdk.stats.LightResist);
- }
- });
+ Object.defineProperty(me, "realLR", {
+ get: function () {
+ return me.getStat(sdk.stats.LightResist);
+ }
+ });
}
if (!me.hasOwnProperty("realPR")) {
- Object.defineProperty(me, "realPR", {
- get: function () {
- return me.getStat(sdk.stats.PoisonResist);
- }
- });
+ Object.defineProperty(me, "realPR", {
+ get: function () {
+ return me.getStat(sdk.stats.PoisonResist);
+ }
+ });
}
if (!me.hasOwnProperty("FR")) {
- Object.defineProperty(me, "FR", {
- get: function () {
- return Math.min(75 + this.getStat(sdk.stats.MaxFireResist), me.realFR - me.resPenalty);
- }
- });
+ Object.defineProperty(me, "FR", {
+ get: function () {
+ return Math.min(75 + this.getStat(sdk.stats.MaxFireResist), me.realFR - me.resPenalty);
+ }
+ });
}
if (!me.hasOwnProperty("CR")) {
- Object.defineProperty(me, "CR", {
- get: function () {
- return Math.min(75 + this.getStat(sdk.stats.MaxColdResist), me.realCR - me.resPenalty);
- }
- });
+ Object.defineProperty(me, "CR", {
+ get: function () {
+ return Math.min(75 + this.getStat(sdk.stats.MaxColdResist), me.realCR - me.resPenalty);
+ }
+ });
}
if (!me.hasOwnProperty("LR")) {
- Object.defineProperty(me, "LR", {
- get: function () {
- return Math.min(75 + this.getStat(sdk.stats.MaxLightResist), me.realLR - me.resPenalty);
- }
- });
+ Object.defineProperty(me, "LR", {
+ get: function () {
+ return Math.min(75 + this.getStat(sdk.stats.MaxLightResist), me.realLR - me.resPenalty);
+ }
+ });
}
if (!me.hasOwnProperty("PR")) {
- Object.defineProperty(me, "PR", {
- get: function () {
- return Math.min(75 + this.getStat(sdk.stats.MaxPoisonResist), me.realPR - me.resPenalty);
- }
- });
+ Object.defineProperty(me, "PR", {
+ get: function () {
+ return Math.min(75 + this.getStat(sdk.stats.MaxPoisonResist), me.realPR - me.resPenalty);
+ }
+ });
+}
+
+if (!me.hasOwnProperty("className")) {
+ Object.defineProperty(me, "className", {
+ get: function () {
+ return sdk.player.class.nameOf(me.classid);
+ }
+ });
}
/**
* Soloplay specific but might as well keep with the format
*/
if (!me.hasOwnProperty("onFinalBuild")) {
- Object.defineProperty(me, "onFinalBuild", {
- get: function () {
- myData === undefined && (myData = CharData.getStats());
- return myData.me.currentBuild === myData.me.finalBuild;
- }
- });
+ Object.defineProperty(me, "onFinalBuild", {
+ get: function () {
+ return me.data.currentBuild === me.data.finalBuild;
+ }
+ });
+}
+
+if (!me.hasOwnProperty("mercid")) {
+ Object.defineProperty(me, "mercid", {
+ get: function () {
+ return me.data.merc.classid || (function () {
+ let merc = me.getMercEx();
+ if (!merc) return 0;
+ me.data.merc.classid = merc.classid;
+ return merc.classid;
+ })();
+ }
+ });
+}
+
+if (!me.hasOwnProperty("trueStr")) {
+ Object.defineProperty(me, "trueStr", {
+ get: function () {
+ return me.data.strength || (function () {
+ let str = me.rawStrength;
+ me.data.strength = str;
+ return str;
+ })();
+ }
+ });
+}
+
+if (!me.hasOwnProperty("trueDex")) {
+ Object.defineProperty(me, "trueDex", {
+ get: function () {
+ return me.data.dexterity || (function () {
+ let dex = me.rawDexterity;
+ me.data.dexterity = dex;
+ return dex;
+ })();
+ }
+ });
+}
+
+if (!me.hasOwnProperty("finalBuild")) {
+ let _finalBuild = null;
+
+ Object.defineProperty(me, "finalBuild", {
+ get: function () {
+ if (_finalBuild) return _finalBuild;
+ let className = me.className.toLowerCase();
+ let build = (["Bumper", "Socketmule", "Imbuemule"].includes(SetUp.finalBuild)
+ ? ["Javazon", "Cold", "Bone", "Hammerdin", "Whirlwind", "Wind", "Trapsin"][me.classid]
+ : SetUp.finalBuild) + "Build";
+ _finalBuild = require("../BuildFiles/" + className + "/" + className + "." + build);
+ return _finalBuild;
+ },
+ set: function (v) {
+ if (v.hasOwnProperty("AutoBuildTemplate")) {
+ // Object.assign(this.finalBuild, v);
+ _finalBuild = v;
+ }
+ },
+ });
+}
+
+if (!me.hasOwnProperty("currentBuild")) {
+ let _currentBuild = null;
+
+ Object.defineProperty(me, "currentBuild", {
+ get: function () {
+ if (_currentBuild) return _currentBuild;
+ let className = me.className.toLowerCase();
+ let build = SetUp.currentBuild + "Build";
+ _currentBuild = require("../BuildFiles/" + className + "/" + className + "." + build);
+ return _currentBuild;
+ },
+ set: function (v) {
+ if (v.hasOwnProperty("AutoBuildTemplate")) {
+ // Object.assign(this.currentBuild, v);
+ _currentBuild = v;
+ }
+ },
+ });
+}
+
+if (!me.hasOwnProperty("data")) {
+ let _data = null;
+
+ Object.defineProperty(me, "data", {
+ get: function () {
+ if (_data) return _data;
+ _data = CharData.getStats();
+ // handle if it was an old data file
+ if (!_data.hasOwnProperty("startTime")) {
+ let oldData = copyObj(_data);
+ _data = CharData.create();
+ Object.assign(_data, oldData.me);
+ Object.assign(_data.merc, oldData.merc);
+ if (oldData.merc.hasOwnProperty("type")) {
+ _data.merc.skillName = oldData.merc.type;
+ _data.merc.skill = MercData.findByName(_data.merc.skillName, _data.merc.act).skill;
+ }
+ }
+ return _data;
+ },
+ set: function (v) {
+ if (v.hasOwnProperty("startTime")) {
+ _data = v;
+ }
+ },
+ });
+
+ Object.defineProperty(me, "update", {
+ value: function () {
+ let obj = JSON.stringify(copyObj(me.data));
+ let myThread = getScript(true).name;
+ CharData.threads.forEach(function (script) {
+ let curr = getScript(script);
+ if (curr && myThread !== curr.name) {
+ curr.send("data--" + obj);
+ }
+ });
+ },
+ });
+}
+
+if (!me.hasOwnProperty("equipped")) {
+ me.equipped = (function () {
+ const _defaultsMap = new Map([
+ ["gid", -1],
+ ["classid", -1],
+ ["name", ""],
+ ["code", ""],
+ ["fname", ""],
+ ["quality", -1],
+ ["ethereal", false],
+ ["itemType", -1],
+ ["strreq", -1],
+ ["dexreq", -1],
+ ["lvlreq", -1],
+ ["sockets", -1],
+ ["prefixnum", -1],
+ ["suffixnum", -1],
+ ]);
+
+ /**
+ * @constructor
+ * @param {ItemUnit} item
+ */
+ function EquippedItem (item) {
+ /** @type {ItemUnit} */
+ this._item = (item instanceof Unit)
+ ? copyUnit(item)
+ : null;
+ /** @type {number} */
+ this._gid = item
+ ? item.gid
+ : -1;
+ /** @type {number} */
+ this._bodylocation = item
+ ? item.bodylocation
+ : -1;
+
+ return new Proxy(this, {
+ get: function (target, prop) {
+ if (prop in target) {
+ return target[prop];
+ }
+
+ if (target._item && prop in target._item) {
+ return target._item[prop];
+ }
+
+ return _defaultsMap.get(prop);
+ }
+ });
+ }
+
+ Object.defineProperties(EquippedItem.prototype, {
+ location: {
+ /** @this EquippedItem */
+ get: function () {
+ return this._item ? this._item.bodylocation : -1;
+ },
+ },
+ durability: {
+ /** @this EquippedItem */
+ get: function () {
+ return this._item ? this._item.durabilityPercent : -1;
+ },
+ },
+ tier: {
+ /** @this EquippedItem */
+ get: function () {
+ return this._item ? NTIP.GetTier(this._item) : -1;
+ },
+ },
+ tierScore: {
+ /** @this EquippedItem */
+ get: function () {
+ return this._item ? tierscore(this._item, 1, this._bodylocation) : -1;
+ },
+ },
+ secondaryTier: {
+ /** @this EquippedItem */
+ get: function () {
+ return this._item ? NTIP.GetSecondaryTier(this._item) : -1;
+ },
+ },
+ socketed: {
+ /** @this EquippedItem */
+ get: function () {
+ return this._item ? this._item.getItemsEx().length > 0 : false;
+ },
+ },
+ });
+
+ EquippedItem.prototype.twoHandedCheck = function (strict = false) {
+ return this._item
+ ? strict
+ ? this._item.strictlyTwoHanded
+ : this._item.twoHanded
+ : false;
+ };
+
+ EquippedItem.prototype.getStat = function (stat, subid) {
+ return this._item ? this._item.getStat(stat, subid) : -1;
+ };
+
+ EquippedItem.prototype.getStatEx = function (stat, subid) {
+ return this._item ? this._item.getStatEx(stat, subid) : -1;
+ };
+
+ /** @type {Map} */
+ const _bodyMap = new Map([
+ [sdk.body.Head, new EquippedItem()],
+ [sdk.body.Neck, new EquippedItem()],
+ [sdk.body.Armor, new EquippedItem()],
+ [sdk.body.RightArm, new EquippedItem()],
+ [sdk.body.LeftArm, new EquippedItem()],
+ [sdk.body.Gloves, new EquippedItem()],
+ [sdk.body.RingRight, new EquippedItem()],
+ [sdk.body.RingLeft, new EquippedItem()],
+ [sdk.body.Belt, new EquippedItem()],
+ [sdk.body.Feet, new EquippedItem()],
+ [sdk.body.RightArmSecondary, new EquippedItem()],
+ [sdk.body.LeftArmSecondary, new EquippedItem()],
+ ]);
+
+ // const _dummy = new EquippedItem();
+
+ return {
+ /**
+ * @param {number} bodylocation
+ * @returns {EquippedItem}
+ */
+ get: function (bodylocation) {
+ // if (!_bodyMap.has(bodylocation)) return _dummy;
+ let item = _bodyMap.get(bodylocation);
+ if (item._gid === -1
+ || (item._gid !== item.gid
+ || (item._bodylocation !== item.location && me.weaponswitch !== sdk.player.slot.Secondary))) {
+ // item has changed - find the new item
+ let newItem = me.getItemsEx()
+ .filter(function (el) {
+ return el.isEquipped && el.bodylocation === bodylocation;
+ })
+ .first();
+ _bodyMap.set(bodylocation, new EquippedItem(newItem));
+ }
+ return _bodyMap.get(bodylocation);
+ },
+ /**
+ * @param {number} bodylocation
+ * @returns {boolean}
+ */
+ has: function (bodylocation) {
+ return _bodyMap.has(bodylocation);
+ },
+ /**
+ * @param {number} bodylocation
+ * @param {ItemUnit} item
+ */
+ set: function (bodylocation, item) {
+ if (_bodyMap.has(bodylocation) && item instanceof Unit) {
+ _bodyMap.set(bodylocation, new EquippedItem(item));
+ }
+ },
+ /**
+ * @description Initializes the equipped item map with the items currently equipped
+ */
+ init: function () {
+ me.getItemsEx()
+ .filter(function (item) {
+ return item.isEquipped;
+ })
+ .forEach(function (item) {
+ _bodyMap.set(item.bodylocation, new EquippedItem(item));
+ });
+ },
+ };
+ })();
}
/** @returns {boolean} */
me.canTpToTown = function () {
- // can't tp if dead - or not currently enabled to
- if (me.dead || !Misc.townEnabled) return false;
- const myArea = me.area;
- 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(myArea)) return false;
- // If we made it this far, we can only tp if we even have a tp
- return !!me.getTpTool();
+ // can't tp if dead - or not currently enabled to
+ if (me.dead || SoloEvents.townChicken.disabled) return false;
+ const myArea = me.area;
+ 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(myArea)) return false;
+ // If we made it this far, we can only tp if we even have a tp
+ return !!me.getTpTool();
};
me.getMercEx = function () {
- if (!Config.UseMerc || me.classic || me.mercrevivecost) return null;
- if (!myData.merc.type) return null;
- let merc = Misc.poll(() => me.getMerc(), 250, 50);
+ if (!Config.UseMerc || me.classic || me.mercrevivecost) return null;
+ let merc = Misc.poll(function () {
+ return me.getMerc();
+ }, 250, 50);
- return !!merc && !merc.dead ? merc : null;
+ return !!merc && !merc.dead ? merc : null;
};
me.getEquippedItem = function (bodyLoc) {
- if (!bodyLoc) return null;
- return me.getItemsEx().filter(i => i.isEquipped && i.bodylocation === bodyLoc).first();
+ if (!bodyLoc) return null;
+ let equippedItem = me.getItemsEx()
+ .filter(function (item) {
+ return item.isEquipped && item.bodylocation === bodyLoc;
+ });
+ if (!equippedItem.length) return null;
+ return equippedItem.first();
+};
+
+/**
+ * @param {number} bodyLoc
+ */
+me.getWeaponQuantityPercent = function (bodyLoc) {
+ if (!bodyLoc) return 0;
+ let weapon = me.getEquippedItem(bodyLoc);
+ if (!weapon) return 0;
+ return weapon.quantityPercent;
};
me.getSkillTabs = function (classid = me.classid) {
- return [
- [sdk.skills.tabs.BowandCrossbow, sdk.skills.tabs.PassiveandMagic, sdk.skills.tabs.JavelinandSpear],
- [sdk.skills.tabs.Fire, sdk.skills.tabs.Lightning, sdk.skills.tabs.Cold],
- [sdk.skills.tabs.Curses, sdk.skills.tabs.PoisonandBone, sdk.skills.tabs.NecroSummoning],
- [sdk.skills.tabs.PalaCombat, sdk.skills.tabs.Offensive, sdk.skills.tabs.Defensive],
- [sdk.skills.tabs.BarbCombat, sdk.skills.tabs.Masteries, sdk.skills.tabs.Warcries],
- [sdk.skills.tabs.DruidSummon, sdk.skills.tabs.ShapeShifting, sdk.skills.tabs.Elemental],
- [sdk.skills.tabs.Traps, sdk.skills.tabs.ShadowDisciplines, sdk.skills.tabs.MartialArts]
- ][classid];
+ return [
+ [sdk.skills.tabs.BowandCrossbow, sdk.skills.tabs.PassiveandMagic, sdk.skills.tabs.JavelinandSpear],
+ [sdk.skills.tabs.Fire, sdk.skills.tabs.Lightning, sdk.skills.tabs.Cold],
+ [sdk.skills.tabs.Curses, sdk.skills.tabs.PoisonandBone, sdk.skills.tabs.NecroSummoning],
+ [sdk.skills.tabs.PalaCombat, sdk.skills.tabs.Offensive, sdk.skills.tabs.Defensive],
+ [sdk.skills.tabs.BarbCombat, sdk.skills.tabs.Masteries, sdk.skills.tabs.Warcries],
+ [sdk.skills.tabs.DruidSummon, sdk.skills.tabs.ShapeShifting, sdk.skills.tabs.Elemental],
+ [sdk.skills.tabs.Traps, sdk.skills.tabs.ShadowDisciplines, sdk.skills.tabs.MartialArts]
+ ][classid];
};
// @todo better determination of what actually constitutes being in danger
// need check for ranged mobs so we can stick and move to avoid missiles
me.inDanger = function (checkLoc, range) {
- let count = 0;
- let _this = typeof checkLoc !== "undefined" && checkLoc.hasOwnProperty("x") ? checkLoc : me;
- range === undefined && (range = 10);
- let nearUnits = getUnits(sdk.unittype.Monster).filter((mon) => mon && mon.attackable && getDistance(_this, mon) < 10);
- nearUnits.forEach(u => u.isSpecial
- ? [sdk.states.Fanaticism, sdk.states.Conviction].some(state => u.getState(state))
- ? (count += 3)
- : (count += 2)
- : (count += 1));
- if (count > me.maxNearMonsters) return true;
- let dangerClose = nearUnits
- .find(mon => [sdk.enchant.ManaBurn, sdk.enchant.LightningEnchanted, sdk.enchant.FireEnchanted].some(chant => mon.getEnchant(chant)));
- return dangerClose;
+ let count = 0;
+ const _this = typeof checkLoc !== "undefined" && checkLoc.hasOwnProperty("x")
+ ? checkLoc
+ : me;
+ range === undefined && (range = 10);
+ let nearUnits = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon && mon.attackable && getDistance(_this, mon) < 10;
+ });
+ nearUnits.forEach(function (u) {
+ return u.isSpecial
+ ? [sdk.states.Fanaticism, sdk.states.Conviction].some(state => u.getState(state))
+ ? (count += 3)
+ : (count += 2)
+ : (count += 1);
+ });
+ if (count > me.maxNearMonsters) return true;
+ let dangerClose = nearUnits
+ .find(function (mon) {
+ return [
+ sdk.enchant.ManaBurn, sdk.enchant.LightningEnchanted, sdk.enchant.FireEnchanted
+ ].some(chant => mon.getEnchant(chant));
+ });
+ return dangerClose;
};
/**
- *
* @param {number} skillId
* @param {number} subId
* @returns boolean
@@ -172,299 +509,593 @@ me.inDanger = function (checkLoc, range) {
*/
me.checkSkill = (skillId = 0, subId = 0) => !!me.getSkill(skillId, subId);
+me.switchToPrimary = function () {
+ if (me.classic) return true;
+ return me.switchWeapons(sdk.player.slot.Main);
+};
+
+me.switchToSecondary = function () {
+ if (me.classic) return true;
+ return me.switchWeapons(sdk.player.slot.Secondary);
+};
+
+/**
+ * @description Check if healing is needed, based on character config
+ * @returns {boolean}
+ */
+me.needHealing = function () {
+ if (me.hpPercent <= Config.HealHP || me.mpPercent <= Config.HealMP) {
+ if (me.hpPercent >= 90 && me.mpPercent >= 90
+ && Town.getDistance(Town.tasks.get(me.act).Heal) > 10) {
+ // it's not convenient to heal right now, and we aren't in danger
+ return false;
+ }
+ 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);
+ }));
+};
+
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
- let needCleanup = Town.checkColumns(beltSize).some(slot => slot > 0);
-
- if (needCleanup) {
- const 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);
-
- potsInInventory.length > 0 && 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 = Town.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)) {
- 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();
- }
- });
- }
-
- return true;
+ 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
+ let 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) {
+ 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)) {
+ 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(function () {
+ return !me.itemoncursor;
+ }, 300, 30);
+ moved = Storage.Belt.checkColumns(beltSize)[i] === freeSpace[i] - 1;
+ }
+ Cubing.cursorCheck();
+ }
+ });
+ }
+
+ return true;
+};
+
+me.cleanUpScrolls = function (tome, scrollId) {
+ if (!tome || !scrollId) return 0;
+
+ let cleanedUp = 0;
+ let myScrolls = me.getItemsEx()
+ .filter(function (el) {
+ return el.isInInventory && el.classid === scrollId;
+ });
+
+ if (myScrolls.length) {
+ try {
+ // If we are at an npc already, open the window otherwise moving potions around fails
+ if (getUIFlag(sdk.uiflags.NPCMenu) && !getUIFlag(sdk.uiflags.Shop)) {
+ console.info(null, "Opening npc menu to clean up scrolls");
+ Misc.useMenu(sdk.menu.Trade) || me.cancelUIFlags();
+ }
+
+ myScrolls.forEach(function (el) {
+ if (tome && tome.getStat(sdk.stats.Quantity) < 20) {
+ let currQuantity = tome.getStat(sdk.stats.Quantity);
+ if (el.toCursor()) {
+ new PacketBuilder()
+ .byte(sdk.packets.send.ScrollToMe)
+ .dword(el.gid)
+ .dword(tome.gid)
+ .send();
+ Misc.poll(function () {
+ return !me.itemoncursor;
+ }, 100, 25);
+
+ if (tome.getStat(sdk.stats.Quantity) > currQuantity) {
+ console.info(null, "Placed scroll in tome");
+ cleanedUp++;
+ }
+ } else {
+ console.warn("failed to place scroll in tome");
+ }
+ }
+ });
+ } catch (e) {
+ console.error(e);
+ me.cancelUIFlags();
+ }
+ }
+
+ return cleanedUp;
+};
+
+me.needBeltPots = function () {
+ // we aren't using MinColumn if none of the values are set
+ if (!Config.MinColumn.some(el => el > 0)) return false;
+ // no hp pots or mp pots in Config.BeltColumn (who uses only rejuv pots?)
+ if (!Config.BeltColumn.some(el => ["hp", "mp"].includes(el))) return false;
+
+ // Start
+ if (me.charlvl > 2 && me.gold > 1000) {
+ let pots = { hp: [], mp: [], };
+ const beltSize = Storage.BeltSize();
+
+ // only run this bit if we aren't wearing a belt for now
+ beltSize === 1 && me.cleanUpInvoPotions(beltSize);
+ // now check what's in our belt
+ me.getItemsEx(-1, sdk.items.mode.inBelt)
+ .filter(function (p) {
+ return p.x < 4
+ && [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion].includes(p.itemType);
+ })
+ .forEach(function (p) {
+ if (p.itemType === sdk.items.type.HealingPotion) {
+ pots.hp.push(copyUnit(p));
+ } else if (p.itemType === sdk.items.type.ManaPotion) {
+ pots.mp.push(copyUnit(p));
+ }
+ });
+
+ // quick check
+ if ((Config.BeltColumn.includes("hp") && !pots.hp.length)
+ || (Config.BeltColumn.includes("mp") && !pots.mp.length)) {
+ return true;
+ }
+
+ // should we check the actual amount in the column?
+ // For now just keeping the way it was and checking if a column is empty
+ for (let i = 0; i < 4; i += 1) {
+ if (Config.MinColumn[i] <= 0) {
+ continue;
+ }
+
+ switch (Config.BeltColumn[i]) {
+ case "hp":
+ if (!pots.hp.some(p => p.x === i)) {
+ console.debug("Column: " + (i + 1) + " needs hp pots");
+ return true;
+ }
+ break;
+ case "mp":
+ if (!pots.mp.some(p => p.x === i)) {
+ console.debug("Column: " + (i + 1) + " needs mp pots");
+ return true;
+ }
+ break;
+ }
+ }
+ }
+
+ return false;
+};
+
+me.needBufferPots = function () {
+ // not using buffers
+ if (Config.HPBuffer < 0 && Config.MPBuffer < 0) return false;
+
+ // Start
+ if (me.charlvl > 2 && me.gold > 1000) {
+ const pots = { hp: 0, mp: 0, };
+ const beltSize = Storage.BeltSize();
+
+ // only run this bit if we aren't wearing a belt for now
+ beltSize === 1 && me.cleanUpInvoPotions(beltSize);
+ // now check what's in our belt
+ me.getItemsEx()
+ .filter(function (p) {
+ return p.isInInventory
+ && [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion].includes(p.itemType);
+ })
+ .forEach(function (p) {
+ if (p.itemType === sdk.items.type.HealingPotion) {
+ pots.hp++;
+ } else if (p.itemType === sdk.items.type.ManaPotion) {
+ pots.mp++;
+ }
+ });
+
+ return (pots.mp < Config.MPBuffer || pots.hp < Config.HPBuffer);
+ }
+
+ return false;
};
me.needPotions = function () {
- // 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(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;
- }
-
- // 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;
+ return me.needBeltPots() || me.needBufferPots();
+};
+
+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());
+
+ while (clearList.length > 0) {
+ let pot = clearList.shift();
+ (Storage.Inventory.CanFit(pot) && Storage.Inventory.MoveTo(pot)) || pot.interact();
+ delay(200);
+ }
+ }
+
+ return true;
};
me.getIdTool = function () {
- let items = me.getItemsEx().filter((i) => i.isInInventory && [sdk.items.ScrollofIdentify, sdk.items.TomeofIdentify].includes(i.classid));
- let scroll = items.find((i) => i.isInInventory && i.classid === sdk.items.ScrollofIdentify);
- if (scroll) return scroll;
- let tome = items.find((i) => i.isInInventory && i.classid === sdk.items.TomeofIdentify);
- if (tome && tome.getStat(sdk.stats.Quantity) > 0) return tome;
+ let items = me.getItemsEx()
+ .filter(function (i) {
+ return i.isInInventory
+ && [sdk.items.ScrollofIdentify, sdk.items.TomeofIdentify].includes(i.classid);
+ });
+ if (!items.length) return null;
+ let scroll = items.find(function (i) {
+ return i.isInInventory && i.classid === sdk.items.ScrollofIdentify;
+ });
+ if (scroll) return scroll;
+ let tome = items.find(function (i) {
+ return i.isInInventory && i.classid === sdk.items.TomeofIdentify;
+ });
+ if (tome && tome.getStat(sdk.stats.Quantity) > 0) return tome;
- return null;
+ return null;
};
me.getTpTool = function () {
- let items = me.getItemsEx(-1, sdk.items.mode.inStorage).filter((i) => i.isInInventory && [sdk.items.ScrollofTownPortal, sdk.items.TomeofTownPortal].includes(i.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;
+ let items = me.getItemsEx(-1, sdk.items.mode.inStorage)
+ .filter(function (i) {
+ return i.isInInventory && [sdk.items.ScrollofTownPortal, sdk.items.TomeofTownPortal].includes(i.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;
+ });
+ if (scroll) return scroll;
+ return null;
};
me.getUnids = function () {
- let list = [];
- let item = me.getItem(-1, sdk.items.mode.inStorage);
+ let list = [];
+ let item = me.getItem(-1, sdk.items.mode.inStorage);
- if (!item) return [];
+ if (!item) return [];
- do {
- if (item.isInInventory && !item.identified) {
- list.push(copyUnit(item));
- }
- } while (item.getNext());
+ do {
+ if (item.isInInventory && !item.identified) {
+ list.push(copyUnit(item));
+ }
+ } while (item.getNext());
- return list;
+ return list;
};
me.fieldID = function () {
- let list = me.getUnids();
- if (!list) return false;
-
- while (list.length > 0) {
- let idTool = me.getIdTool();
- if (!idTool) return false;
-
- let item = list.shift();
- let result = Pickit.checkItem(item);
- // Force ID for unid items matching autoEquip/cubing criteria
- Town.needForceID(item) && (result.result = -1);
-
- // unid item that should be identified
- if (result.result === Pickit.Result.UNID) {
- Town.identifyItem(item, idTool, Config.FieldID.PacketID);
- delay(50);
- result = Pickit.checkItem(item);
- }
- Town.itemResult(item, result, "Field", false);
- }
-
- delay(200);
- me.cancel();
-
- return true;
+ let list = me.getUnids();
+ if (!list) return false;
+ const loc = me.inTown ? "Town" : "Field";
+
+ while (list.length > 0) {
+ let idTool = me.getIdTool();
+ if (!idTool) return false;
+
+ let item = list.shift();
+ let result = Pickit.checkItem(item);
+ // Force ID for unid items matching autoEquip/cubing criteria
+ Town.needForceID(item) && (result.result = -1);
+
+ // unid item that should be identified
+ if (result.result === Pickit.Result.UNID) {
+ Town.identifyItem(item, idTool, Config.FieldID.PacketID);
+ delay(50);
+ result = Pickit.checkItem(item);
+ }
+ Town.itemResult(item, result, loc, false);
+ }
+
+ delay(200);
+ me.cancel();
+
+ return true;
};
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;
+ let currItem = me.getItemsEx(-1, sdk.items.mode.Equipped)
+ .filter(function (i) {
+ return i.bodylocation === weaponLoc;
+ })
+ .first();
+ return !!currItem ? currItem.getStat(sdk.stats.Quantity) : 0;
};
me.getItemsForRepair = function (repairPercent, chargedItems) {
- const lowLevelCheck = me.charlvl < 5;
- // lower the required percent as we are a low level
- (lowLevelCheck && repairPercent > 30) && (repairPercent = 15);
- let itemList = [];
- let item = me.getItem(-1, sdk.items.mode.Equipped);
-
- if (item) {
- do {
- if (lowLevelCheck && !item.isOnMain && !item.isOnSwap) continue;
- // 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;
- default:
- // Durability check
- 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;
+ const lowLevelCheck = me.charlvl < 5;
+ // lower the required percent as we are a low level
+ (lowLevelCheck && repairPercent > 30) && (repairPercent = 15);
+ let itemList = [];
+ let item = me.getItem(-1, sdk.items.mode.Equipped);
+
+ if (item) {
+ do {
+ if (lowLevelCheck && !item.isOnMain && !item.isOnSwap) continue;
+ // 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;
+ default:
+ // Durability check
+ 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;
};
me.needRepair = function () {
- let repairAction = [];
- let bowCheck = Attack.usingBow();
- let switchBowCheck = CharData.skillData.bowData.bowOnSwitch;
- let canAfford = me.gold >= me.getRepairCost();
- !bowCheck && switchBowCheck && (bowCheck = (() => {
- switch (CharData.skillData.bowData.bowType) {
- case sdk.items.type.Bow:
- case sdk.items.type.AmazonBow:
- return "bow";
- case sdk.items.type.Crossbow:
- return "crossbow";
- default:
- return "";
- }
- })());
-
- if (bowCheck) {
- let [quiver, inventoryQuiver] = (() => {
- switch (bowCheck) {
- case "crossbow":
- return [me.getItem("cqv", sdk.items.mode.Equipped), me.getItem("cqv", sdk.items.mode.inStorage)];
- case "bow":
- default:
- return [me.getItem("aqv", sdk.items.mode.Equipped), me.getItem("aqv", sdk.items.mode.inStorage)];
- }
- })();
-
- // Out of arrows/bolts
- if (!quiver) {
- inventoryQuiver ? switchBowCheck ? Item.secondaryEquip(inventoryQuiver, sdk.body.LeftArmSecondary) : Item.equip(inventoryQuiver, 5) : repairAction.push("buyQuiver") && repairAction.push("buyQuiver");
- } else {
- let quantity = quiver.getStat(sdk.stats.Quantity);
-
- if (typeof quantity === "number" && quantity * 100 / getBaseStat("items", quiver.classid, "maxstack") <= Config.RepairPercent) {
- inventoryQuiver ? switchBowCheck ? Item.secondaryEquip(inventoryQuiver, sdk.body.LeftArmSecondary) : Item.equip(inventoryQuiver, 5) : repairAction.push("buyQuiver") && repairAction.push("buyQuiver");
- }
- }
- }
-
- // Repair durability/quantity/charges
- if (canAfford && this.getItemsForRepair(Config.RepairPercent, true).length > 0) {
- repairAction.push("repair");
- }
-
- return repairAction;
+ let repairAction = [];
+ let bowCheck = Attack.usingBow();
+ let switchBowCheck = CharData.skillData.bow.onSwitch;
+ if (getInteractedNPC() && !getUIFlag(sdk.uiflags.Shop)) {
+ // fix crash with d2bs
+ me.cancel();
+ }
+ let canAfford = me.gold >= me.getRepairCost();
+ !bowCheck && switchBowCheck && (bowCheck = (function () {
+ switch (CharData.skillData.bow.bowType) {
+ case sdk.items.type.Bow:
+ case sdk.items.type.AmazonBow:
+ return "bow";
+ case sdk.items.type.Crossbow:
+ return "crossbow";
+ default:
+ return "";
+ }
+ })());
+
+ if (bowCheck) {
+ let [quiver, inventoryQuiver] = (function () {
+ switch (bowCheck) {
+ case "crossbow":
+ return [
+ me.getItem("cqv", sdk.items.mode.Equipped),
+ me.getItem("cqv", sdk.items.mode.inStorage)
+ ];
+ case "bow":
+ default:
+ return [
+ me.getItem("aqv", sdk.items.mode.Equipped),
+ me.getItem("aqv", sdk.items.mode.inStorage)
+ ];
+ }
+ })();
+
+ // Out of arrows/bolts
+ if (!quiver) {
+ inventoryQuiver
+ ? switchBowCheck
+ ? Item.secondaryEquip(inventoryQuiver, sdk.body.LeftArmSecondary)
+ : Item.equip(inventoryQuiver, 5)
+ : repairAction.push("buyQuiver") && repairAction.push("buyQuiver");
+ if (me.gold < 200) {
+ return [];
+ }
+ } else {
+ let quantity = quiver.getStat(sdk.stats.Quantity);
+
+ if (typeof quantity === "number"
+ && quantity * 100 / getBaseStat("items", quiver.classid, "maxstack") <= Config.RepairPercent) {
+ inventoryQuiver
+ ? switchBowCheck
+ ? Item.secondaryEquip(inventoryQuiver, sdk.body.LeftArmSecondary)
+ : Item.equip(inventoryQuiver, 5)
+ : repairAction.push("buyQuiver") && repairAction.push("buyQuiver");
+ }
+ }
+ }
+
+ // Repair durability/quantity/charges
+ if (canAfford && this.getItemsForRepair(Config.RepairPercent, true).length > 0) {
+ repairAction.push("repair");
+ }
+
+ return repairAction;
};
me.needMerc = function () {
- if (me.classic || !Config.UseMerc || me.gold < me.mercrevivecost || me.mercrevivecost === 0) return false;
+ 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.getMercEx();
+ if (!!merc && !merc.dead) 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.getMercEx();
- if (!!merc && !merc.dead) return false;
+ delay(100);
+ }
+
+ // In case we never had a merc and Config.UseMerc is still set to true for some odd reason
+ return true;
+};
+
+me.sortInventory = function () {
+ return Storage.Inventory.SortItems(
+ SetUp.sortSettings.ItemsSortedFromLeft,
+ SetUp.sortSettings.ItemsSortedFromRight
+ );
+};
+
+/**
+ * @description Returns boolean if we have all the runes given by itemInfo array
+ * @param {number[]} itemInfo - Array of rune classids
+ * @returns Boolean
+ */
+me.haveRunes = function (itemInfo = []) {
+ if (!Array.isArray(itemInfo) || typeof itemInfo[0] !== "number") return false;
+ let itemList = this.getItemsEx()
+ .filter(i => i.isInStorage && i.itemType === sdk.items.type.Rune);
+ if (!itemList.length || itemList.length < itemInfo.length) return false;
+ const checkedGids = new Set();
+
+ return itemInfo.every(function (rune) {
+ return itemList.some(function (item) {
+ if (item.classid === rune && !checkedGids.has(item.gid)) {
+ checkedGids.add(item.gid);
+ return true;
+ }
+ return false;
+ });
+ });
+};
+
+/**
+ * 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();
- delay(100);
- }
+ 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());
+ }
- // In case we never had a merc and Config.UseMerc is still set to true for some odd reason
- return true;
+ return itemList;
};
diff --git a/libs/SoloPlay/Functions/Mercenary.js b/libs/SoloPlay/Functions/Mercenary.js
index e7f1cee8..4623f48c 100644
--- a/libs/SoloPlay/Functions/Mercenary.js
+++ b/libs/SoloPlay/Functions/Mercenary.js
@@ -6,243 +6,344 @@
*
*/
+const MercData = new function MercData () {
+ /**
+ * @constructor Merc
+ * @param {number} classid
+ * @param {number} skill
+ * @param {number} act
+ * @param {number} [difficulty]
+ */
+ function Merc (classid, skill, act, difficulty) {
+ this.classid = classid;
+ this.skill = skill;
+ this.skillName = getSkillById(skill);
+ this.act = act;
+ this.difficulty = difficulty || sdk.difficulty.Normal;
+ }
+
+ // Act 1
+ this[sdk.skills.FireArrow] = new Merc(sdk.mercs.Rogue, sdk.skills.FireArrow, 1);
+ this[sdk.skills.ColdArrow] = new Merc(sdk.mercs.Rogue, sdk.skills.ColdArrow, 1);
+
+ // Act 2
+ this[sdk.skills.Prayer] = new Merc(sdk.mercs.Guard, sdk.skills.Prayer, 2, sdk.difficulty.Normal);
+ this[sdk.skills.BlessedAim] = new Merc(sdk.mercs.Guard, sdk.skills.BlessedAim, 2, sdk.difficulty.Normal);
+ this[sdk.skills.Defiance] = new Merc(sdk.mercs.Guard, sdk.skills.Defiance, 2, sdk.difficulty.Normal);
+
+ this[sdk.skills.HolyFreeze] = new Merc(sdk.mercs.Guard, sdk.skills.HolyFreeze, 2, sdk.difficulty.Nightmare);
+ this[sdk.skills.Might] = new Merc(sdk.mercs.Guard, sdk.skills.Might, 2, sdk.difficulty.Nightmare);
+ this[sdk.skills.Thorns] = new Merc(sdk.mercs.Guard, sdk.skills.Thorns, 2, sdk.difficulty.Nightmare);
+
+ // Act 3
+ this[sdk.skills.IceBlast] = new Merc(sdk.mercs.IronWolf, sdk.skills.IceBlast, 3, sdk.difficulty.Normal);
+ this[sdk.skills.FireBall] = new Merc(sdk.mercs.IronWolf, sdk.skills.FireBall, 3, sdk.difficulty.Normal);
+ this[sdk.skills.Lightning] = new Merc(sdk.mercs.IronWolf, sdk.skills.Lightning, 3, sdk.difficulty.Normal);
+
+ // Act 5
+ this[sdk.skills.Bash] = new Merc(sdk.mercs.A5Barb, sdk.skills.Bash, 5, sdk.difficulty.Normal);
+
+ /** @type {Map} */
+ this.actMap = new Map();
+ this.actMap.set(sdk.mercs.Rogue, 1);
+ this.actMap.set(1, [this[sdk.skills.FireArrow], this[sdk.skills.ColdArrow]]);
+
+ this.actMap.set(sdk.mercs.Guard, 2);
+ this.actMap.set(2, [
+ this[sdk.skills.Prayer], this[sdk.skills.BlessedAim],
+ this[sdk.skills.Defiance], this[sdk.skills.HolyFreeze],
+ this[sdk.skills.Might], this[sdk.skills.Thorns]
+ ]);
+
+ this.actMap.set(sdk.mercs.IronWolf, 3);
+ this.actMap.set(3, [this[sdk.skills.IceBlast], this[sdk.skills.FireBall], this[sdk.skills.Lightning]]);
+
+ this.actMap.set(sdk.mercs.A5Barb, 5);
+ this.actMap.set(5, [this[sdk.skills.Bash]]);
+
+ this.findByName = function (name, act) {
+ let merc = this.actMap.get(act)
+ .find(m => m.skillName === name);
+ return merc;
+ };
+};
+
const Mercenary = {
- minCost: -1,
-
- // only a2 mercs for now, need to test others to see if ModifierListSkill returns their skill
- getMercSkill: function (merc = undefined) {
- !merc && (merc = Misc.poll(() => me.getMerc(), 1000, 50));
- if (!merc) return false;
- let mercSkill = (() => {
- switch (merc.classid) {
- case sdk.mercs.Rogue:
- return [sdk.skills.FireArrow, sdk.skills.ColdArrow].find(s => merc.getSkill(s, sdk.skills.subindex.HardPoints));
- case sdk.mercs.Guard:
- let checkStat = merc.getStat(sdk.stats.ModifierListSkill);
- // if ([sdk.skills.Meditation, sdk.skills.Conviction, sdk.skills.Concentration, sdk.skills.HolyFire].includes(checkStat)) {
- // return [sdk.skills.Prayer, sdk.skills.BlessedAim, sdk.skills.Defiance].find(s => merc.getSkill(s, sdk.skills.subindex.HardPoints));
- // }
- if (![sdk.skills.Prayer, sdk.skills.BlessedAim, sdk.skills.Defiance, sdk.skills.HolyFreeze, sdk.skills.Might, sdk.skills.Thorns].includes(checkStat)) {
- // check items for aura granting one then subtract it's skillId
- merc.getItemsEx().forEach(item => {
- if (!item.unique && !item.runeword) return false;
- switch (true) {
- case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.Meditation)):
- return (checkStat -= sdk.skills.Meditation);
- case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.Conviction)):
- return (checkStat -= sdk.skills.Conviction);
- case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.Concentration)):
- return (checkStat -= sdk.skills.Concentration);
- case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.HolyFreeze)):
- return (checkStat -= sdk.skills.HolyFreeze);
- case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.HolyFire)):
- return (checkStat -= sdk.skills.HolyFire);
- case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.HolyShock)):
- return (checkStat -= sdk.skills.HolyShock);
- }
- return true;
- });
- }
- return checkStat >= sdk.skills.Might ? checkStat : 0;
- case sdk.mercs.IronWolf:
- return [sdk.skills.IceBlast, sdk.skills.FireBall, sdk.skills.Lightning].find(s => merc.getSkill(s, sdk.skills.subindex.HardPoints));
- case sdk.mercs.A5Barb:
- return sdk.skills.Bash;
- default:
- return 0;
- }
- })();
-
- return mercSkill ? getSkillById(mercSkill) : "";
- },
-
- // only a2 mercs for now
- getMercDifficulty: function (merc = undefined) {
- !merc && (merc = Misc.poll(() => me.getMerc(), 1000, 50));
- if (!merc) return false;
- let mercSkill = merc.getStat(sdk.stats.ModifierListSkill);
-
- switch (mercSkill) {
- case sdk.skills.Thorns:
- case sdk.skills.HolyFreeze:
- case sdk.skills.Might:
- return sdk.difficulty.Nightmare;
- default:
- return sdk.difficulty.Normal;
- }
- },
-
- getMercAct: function (merc) {
- !merc && (merc = Misc.poll(() => me.getMerc(), 1000, 50));
- if (!merc) return 0;
- switch (merc.classid) {
- case sdk.mercs.Rogue:
- return 1;
- case sdk.mercs.Guard:
- return 2;
- case sdk.mercs.IronWolf:
- return 3;
- case sdk.mercs.A5Barb:
- return 5;
- default:
- return 0;
- }
- },
-
- getMercInfo: function (merc) {
- !merc && (merc = Misc.poll(() => me.getMerc(), 1000, 50));
- if (!merc) return { classid: 0, act: 0, difficulty: 0, type: "" };
- return {
- classid: merc.classid,
- act: this.getMercAct(merc),
- difficulty: this.getMercDifficulty(merc),
- type: this.getMercSkill(merc)
- };
- },
-
- checkMercSkill: function (wanted = "", merc = undefined) {
- merc = !!merc ? merc : me.getMerc();
- if (!merc) return false;
- let mercSkill = merc.getStat(sdk.stats.ModifierListSkill);
-
- // only a2 mercs for now, need to test others to see if above returns their skill
- switch (wanted.toLowerCase()) {
- case "defiance":
- return mercSkill === sdk.skills.Defiance;
- case "prayer":
- return mercSkill === sdk.skills.Prayer;
- case "blessed aim":
- return mercSkill === sdk.skills.BlessedAim;
- case "thorns":
- return mercSkill === sdk.skills.Thorns;
- case "holy freeze":
- return mercSkill === sdk.skills.HolyFreeze;
- case "might":
- return mercSkill === sdk.skills.Might;
- case "cold arrow":
- return merc.getSkill(sdk.skills.ColdArrow, sdk.skills.subindex.HardPoints);
- case "fire arrow":
- return merc.getSkill(sdk.skills.FireArrow, sdk.skills.subindex.HardPoints);
- case "fire ball":
- return merc.getSkill(sdk.skills.FireBall, sdk.skills.subindex.HardPoints);
- case "lightning":
- return merc.getSkill(sdk.skills.Lightning, sdk.skills.subindex.HardPoints);
- case "glacial spike":
- return merc.getSkill(sdk.skills.GlacialSpike, sdk.skills.subindex.HardPoints);
- case "bash":
- return merc.getSkill(sdk.skills.Bash, sdk.skills.subindex.HardPoints);
- default:
- return false;
- }
- },
-
- // only supports act 2 mercs for now
- hireMerc: function () {
- if (me.classic) return true;
- let _a;
- let { mercAct, mercAuraWanted, mercDiff } = Check.finalBuild();
- let typeOfMerc = (!Pather.accessToAct(2) && me.normal ? 1 : mercAct);
- let tmpAuraName = "Defiance";
-
- // don't hire if using correct a1 merc, or passed merc hire difficulty
- // we've already gotten the correct a1 merc or haven't yet completed Bloodraven
- // we are not in the correct difficulty to hire our wanted merc
- // we don't have access to the act of our wanted merc
- // we've already hired our wanted merc
- // we aren't in our wanted mercs difficulty but we have already hired the correct temp a2 merc
- // we've gone back a difficulty - (with using the data file it shouldn't get here but still handle it just in case)
- // we don't have enough spare gold to buy a1 merc
- // we don't have enough gold to hire our wanted merc
- switch (true) {
- case typeOfMerc === 1 && (myData.merc.type === "Cold Arrow" || !Misc.checkQuest(sdk.quest.id.SistersBurialGrounds, sdk.quest.states.Completed)):
- case me.diff > mercDiff:
- case me.diff === mercDiff && !Pather.accessToAct(mercAct):
- case myData.merc.type === mercAuraWanted:
- case me.diff !== mercDiff && myData.merc.type === "Defiance":
- case (me.charlvl > CharInfo.levelCap + 10 && Mercenary.checkMercSkill(myData.merc.type)):
- case me.gold < Math.round((((me.charlvl - 1) * (me.charlvl - 1)) / 2) * 7.5):
- case this.minCost > 0 && me.gold < this.minCost:
- return true;
- }
-
- // lets check what our current actually merc is
- let checkMyMerc = Misc.poll(() => me.getMerc(), 50, 500);
- const wantedSkill = (typeOfMerc === 1
- ? ["Cold Arrow", "Fire Arrow"].includes(mercAuraWanted) ? mercAuraWanted : "Cold Arrow"
- : me.normal ? tmpAuraName : mercAuraWanted);
-
- if (checkMyMerc && Mercenary.checkMercSkill(wantedSkill, checkMyMerc)) {
- // we have our wanted merc, data file was probably erased so lets re-update it
- myData.merc.act = Mercenary.getMercAct(checkMyMerc);
- myData.merc.classid = checkMyMerc.classid;
- myData.merc.difficulty = Mercenary.getMercDifficulty(checkMyMerc);
- myData.merc.type = wantedSkill;
- CharData.updateData("merc", myData) && updateMyData();
- return true;
- } else if (!!checkMyMerc && checkMyMerc.classid === sdk.mercs.Guard) {
- let checkSkill = checkMyMerc.getStat(sdk.stats.ModifierListSkill);
- // aura isn't active so we can't check it
- if (!checkSkill) return true;
- // or we might have multiple aura's going
- if ([sdk.skills.Meditation, sdk.skills.Conviction, sdk.skills.Concentration].includes(checkSkill)) return true;
- if (checkSkill > 123) return true;
- }
-
- let MercLib_1 = require("../Modules/MercLib");
- try {
- Town.goToTown(typeOfMerc);
- myPrint("ÿc9Mercenaryÿc0 :: getting merc");
- Town.move(Town.tasks[me.act - 1].Merc);
- Town.sortInventory();
- Item.removeItemsMerc(); // strip temp merc gear
- delay(500 + me.ping);
- addEventListener("gamepacket", MercLib_1.mercPacket);
- Town.initNPC("Merc", "getMerc");
- let wantedMerc = MercLib_1.default
- .filter((merc) => merc.skills.some((skill) => (skill === null || skill === void 0 ? void 0 : skill.name) === wantedSkill))
- .sort((a, b) => b.level - a.level)
- .first();
- if (wantedMerc) {
- if (wantedMerc.cost > me.gold) {
- this.minCost = wantedMerc.cost;
- throw new Error();
- }
- let oldGid_1 = (_a = me.getMerc()) === null || _a === void 0 ? void 0 : _a.gid;
- console.log("ÿc9Mercenaryÿc0 :: Found a merc to hire " + JSON.stringify(wantedMerc));
- wantedMerc === null || wantedMerc === void 0 ? void 0 : wantedMerc.hire();
- let newMerc = Misc.poll(function () {
- let merc = me.getMerc();
- if (!merc) return false;
- if (oldGid_1 && oldGid_1 === merc.gid) return false;
- return merc;
- });
- console.log("Hired a merc?");
- if (newMerc) {
- console.log("Yep");
- myData.merc.act = me.act;
- myData.merc.classid = newMerc.classid;
- myData.merc.difficulty = me.diff;
- myData.merc.type = wantedMerc.skills.find(sk => sk.name === wantedSkill).name;
- CharData.updateData("merc", myData) && updateMyData();
- console.log("ÿc9Mercenaryÿc0 :: " + myData.merc.type + " merc hired.");
- }
- me.cancelUIFlags();
- while (getInteractedNPC()) {
- delay(me.ping || 5);
- me.cancel();
- }
- }
- } catch (e) {
- //
- } finally {
- removeEventListener("gamepacket", MercLib_1.mercPacket);
- }
-
- Item.autoEquipMerc();
- Pickit.pickItems(); // safetycheck for merc items on ground
- Item.autoEquipMerc();
-
- return true;
- },
+ minCost: -1,
+ timeout: 0,
+
+ /**
+ * only a2 mercs for now, need to test others to see if ModifierListSkill returns their skill
+ * @param {MercUnit} merc
+ * @returns {string}
+ */
+ getMercSkill: function (merc) {
+ !merc && (merc = Misc.poll(function () {
+ return me.getMerc();
+ }, 1000, 50));
+ if (!merc) return false;
+ let mercSkill = (function () {
+ switch (merc.classid) {
+ case sdk.mercs.Rogue:
+ return [
+ sdk.skills.FireArrow,
+ sdk.skills.ColdArrow
+ ].find(s => merc.getSkill(s, sdk.skills.subindex.HardPoints));
+ case sdk.mercs.Guard:
+ let checkStat = merc.getStat(sdk.stats.ModifierListSkill);
+ // if ([sdk.skills.Meditation, sdk.skills.Conviction, sdk.skills.Concentration, sdk.skills.HolyFire].includes(checkStat)) {
+ // return [sdk.skills.Prayer, sdk.skills.BlessedAim, sdk.skills.Defiance].find(s => merc.getSkill(s, sdk.skills.subindex.HardPoints));
+ // }
+ if (![
+ sdk.skills.Prayer, sdk.skills.BlessedAim,
+ sdk.skills.Defiance, sdk.skills.HolyFreeze,
+ sdk.skills.Might, sdk.skills.Thorns
+ ].includes(checkStat)) {
+ // check items for aura granting one then subtract it's skillId
+ merc.getItemsEx().forEach(function (item) {
+ if (!item.unique && !item.runeword) return false;
+ switch (true) {
+ case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.Meditation)):
+ return (checkStat -= sdk.skills.Meditation);
+ case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.Conviction)):
+ return (checkStat -= sdk.skills.Conviction);
+ case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.Concentration)):
+ return (checkStat -= sdk.skills.Concentration);
+ case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.HolyFreeze)):
+ return (checkStat -= sdk.skills.HolyFreeze);
+ case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.HolyFire)):
+ return (checkStat -= sdk.skills.HolyFire);
+ case (item.getStat(sdk.stats.SkillOnAura, sdk.skills.HolyShock)):
+ return (checkStat -= sdk.skills.HolyShock);
+ }
+ return true;
+ });
+ }
+ return checkStat >= sdk.skills.Might ? checkStat : 0;
+ case sdk.mercs.IronWolf:
+ return [
+ sdk.skills.IceBlast,
+ sdk.skills.FireBall,
+ sdk.skills.Lightning
+ ].find(s => merc.getSkill(s, sdk.skills.subindex.HardPoints));
+ case sdk.mercs.A5Barb:
+ return sdk.skills.Bash;
+ default:
+ return 0;
+ }
+ })();
+
+ return mercSkill ? getSkillById(mercSkill) : "";
+ },
+
+ /**
+ * @param {MercUnit} merc
+ * @returns {number}
+ */
+ getMercDifficulty: function (merc) {
+ !merc && (merc = Misc.poll(function () {
+ return me.getMerc();
+ }, 1000, 50));
+ if (!merc) return false;
+ if (merc.classid !== sdk.mercs.Guard) return sdk.difficulty.Normal;
+
+ let mercSkill = merc.getStat(sdk.stats.ModifierListSkill);
+
+ switch (mercSkill) {
+ case sdk.skills.Thorns:
+ case sdk.skills.HolyFreeze:
+ case sdk.skills.Might:
+ return sdk.difficulty.Nightmare;
+ default:
+ return sdk.difficulty.Normal;
+ }
+ },
+
+ /**
+ * @param {MercUnit} merc
+ * @returns {number}
+ */
+ getMercAct: function (merc) {
+ !merc && (merc = Misc.poll(function () {
+ return me.getMerc();
+ }, 1000, 50));
+ if (!merc) return 0;
+ return MercData.actMap.get(merc.classid) || 0;
+ },
+
+ /**
+ * @param {MercUnit} merc
+ */
+ getMercInfo: function (merc) {
+ !merc && (merc = Misc.poll(function () {
+ return me.getMerc();
+ }, 1000, 50));
+ if (!merc) return { classid: 0, act: 0, difficulty: 0, type: "" };
+ return {
+ classid: merc.classid,
+ act: this.getMercAct(merc),
+ difficulty: this.getMercDifficulty(merc),
+ skillName: this.getMercSkill(merc)
+ };
+ },
+
+ /**
+ *
+ * @param {{ classid: number, act: number, skill: number, skillName: string, difficulty: number }} wanted
+ * @param {MercUnit} merc
+ * @returns {boolean}
+ */
+ checkMercSkill: function (wanted, merc) {
+ merc = !!merc ? merc : me.getMerc();
+ if (!merc) return false;
+ let mercSkill = merc.getStat(sdk.stats.ModifierListSkill);
+
+ if (merc.classid === sdk.mercs.Guard) {
+ return mercSkill === wanted.skill;
+ } else {
+ return merc.getSkill(wanted.skill, sdk.skills.subindex.HardPoints) > 0;
+ }
+ },
+
+ // only supports act 2 mercs for now
+ hireMerc: function () {
+ if (me.classic) return true;
+ if (Mercenary.timeout && getTickCount() < Mercenary.timeout) return true;
+ let _a;
+ let { wantedMerc } = Check.finalBuild();
+ let mercAct = (!me.accessToAct(2) && me.normal ? 1 : wantedMerc.act);
+ let tmpAuraName = "Defiance";
+ let currMerc = me.data.merc;
+
+ // don't hire if using correct a1 merc, or passed merc hire difficulty
+ // we've already gotten the correct a1 merc or haven't yet completed Bloodraven
+ // we are not in the correct difficulty to hire our wanted merc
+ // we don't have access to the act of our wanted merc
+ // we've already hired our wanted merc
+ // we aren't in our wanted mercs difficulty but we have already hired the correct temp a2 merc
+ // we've gone back a difficulty - (with using the data file it shouldn't get here but still handle it just in case)
+ // we don't have enough spare gold to buy a1 merc
+ // we don't have enough gold to hire our wanted merc
+ switch (true) {
+ case mercAct === 1 && (currMerc.skillName === "Cold Arrow" || !Misc.checkQuest(sdk.quest.id.SistersBurialGrounds, sdk.quest.states.Completed)):
+ case currMerc.skillName === wantedMerc.skillName:
+ case me.diff > wantedMerc.difficulty:
+ case me.diff === wantedMerc.difficulty && !me.accessToAct(wantedMerc.act):
+ case me.diff !== wantedMerc.difficulty && currMerc.skillName === "Defiance":
+ case (me.charlvl > CharInfo.levelCap + 10 && Mercenary.checkMercSkill(wantedMerc)):
+ case me.gold < Math.round((((me.charlvl - 1) * (me.charlvl - 1)) / 2) * 7.5):
+ case this.minCost > 0 && me.gold < this.minCost:
+ return true;
+ }
+
+ // lets check what our current actually merc is
+ /** @type {MercUnit} */
+ let checkMyMerc = Misc.poll(function () {
+ return me.getMerc();
+ }, 50, 500);
+
+ const wantedSkill = (mercAct === 1
+ ? "Fire Arrow" === wantedMerc.skillName
+ ? wantedMerc.skillName
+ : "Cold Arrow"
+ : me.normal
+ ? tmpAuraName
+ : wantedMerc.skillName
+ );
+
+ if (checkMyMerc && Mercenary.checkMercSkill(wantedMerc, checkMyMerc)) {
+ // we have our wanted merc, data file was probably erased so lets re-update it
+ me.data.merc.act = Mercenary.getMercAct(checkMyMerc);
+ me.data.merc.classid = checkMyMerc.classid;
+ me.data.merc.difficulty = Mercenary.getMercDifficulty(checkMyMerc);
+ me.data.merc.skillName = wantedMerc.skillName;
+ me.data.merc.skill = MercData.findByName(me.data.merc.skillName, me.act).skill;
+ CharData.updateData("merc", me.data) && me.update();
+
+ return true;
+ } else if (!!checkMyMerc && checkMyMerc.classid === sdk.mercs.Guard) {
+ let checkSkill = checkMyMerc.getStat(sdk.stats.ModifierListSkill);
+ // aura isn't active so we can't check it
+ if (!checkSkill) return true;
+ // or we might have multiple aura's going
+ if ([sdk.skills.Meditation, sdk.skills.Conviction, sdk.skills.Concentration].includes(checkSkill)) return true;
+ if (checkSkill > sdk.skills.Conviction) return true;
+ }
+
+ let MercLib_1 = require("../Modules/MercLib");
+ try {
+ Town.goToTown(mercAct);
+ myPrint("ÿc9Mercenaryÿc0 :: getting merc");
+ Town.move(Town.tasks.get(me.act).Merc);
+ me.sortInventory();
+ Item.removeItemsMerc(); // strip temp merc gear
+ delay(500 + me.ping);
+
+ addEventListener("gamepacket", MercLib_1.mercPacket);
+ Town.initNPC("Merc", "getMerc");
+
+ delay(500);
+
+ if (!MercLib_1.default.length) throw new Error("No mercs found");
+
+ let wantedMerc = MercLib_1.default
+ .filter(function (merc) {
+ return merc.skills
+ .some(function (skill) {
+ return (skill === null || skill === void 0 ? void 0 : skill.name) === wantedSkill;
+ });
+ })
+ .sort(function (a, b) {
+ return b.level - a.level;
+ })
+ .first();
+ if (!wantedMerc) throw new Error("No merc found with skill " + wantedSkill);
+ if (wantedMerc.cost > me.gold) {
+ Mercenary.minCost = wantedMerc.cost;
+ throw new Error("Too expensive " + wantedMerc.cost);
+ }
+
+ let oldGid_1 = (_a = me.getMercEx()) === null || _a === void 0 ? void 0 : _a.gid;
+ console.log("ÿc9Mercenaryÿc0 :: Found a merc to hire " + JSON.stringify(wantedMerc));
+
+ (wantedMerc === null || wantedMerc === void 0)
+ ? void 0
+ : wantedMerc.hire();
+ let newMerc = Misc.poll(function () {
+ let merc = me.getMerc();
+ if (!merc) return false;
+ if (oldGid_1 && oldGid_1 === merc.gid) return false;
+ return merc;
+ });
+
+ console.log("Hired a merc?");
+ if (newMerc) {
+ console.log("Yep");
+ me.data.merc.act = me.act;
+ me.data.merc.classid = newMerc.classid;
+ me.data.merc.difficulty = me.diff;
+ me.data.merc.skillName = wantedMerc.skills.find(sk => sk.name === wantedSkill).name;
+ me.data.merc.skill = MercData.findByName(me.data.merc.skillName, me.act).skill;
+ CharData.updateData("merc", me.data) && me.update();
+ console.log("ÿc9Mercenaryÿc0 :: " + me.data.merc.skillName + " merc hired.");
+ }
+ me.cancelUIFlags();
+ while (getInteractedNPC()) {
+ delay(me.ping || 5);
+ me.cancel();
+ }
+ } catch (e) {
+ console.error(e);
+ Mercenary.timeout = getTickCount() + Time.minutes(3);
+ } finally {
+ removeEventListener("gamepacket", MercLib_1.mercPacket);
+ }
+
+ Item.autoEquipMerc();
+ Pickit.pickItems(); // safetycheck for merc items on ground
+ Item.autoEquipMerc();
+
+ return true;
+ },
};
diff --git a/libs/SoloPlay/Functions/MiscOverrides.js b/libs/SoloPlay/Functions/MiscOverrides.js
index 54bcfa93..1fd344b3 100644
--- a/libs/SoloPlay/Functions/MiscOverrides.js
+++ b/libs/SoloPlay/Functions/MiscOverrides.js
@@ -6,78 +6,50 @@
*
*/
-includeIfNotIncluded("common/Misc.js");
-
-Misc.townEnabled = true;
-
-Misc.townCheck = function () {
- if (!me.canTpToTown()) return false;
-
- let check = false;
-
- if (Config.TownCheck && !me.inTown) {
- try {
- if (me.needPotions() || (Config.OpenChests.Enabled && Town.needKeys())) {
- check = true;
- }
- } catch (e) {
- check = false;
- }
- }
-
- if (check) {
- if (Messaging.sendToScript("libs/SoloPlay/Threads/TownChicken.js", "fastTown")) {
- console.log("BroadCasted townCheck");
-
- return true;
- }
- }
-
- return false;
-};
+includeIfNotIncluded("core/Misc.js");
+const ShrineData = require("../../core/GameData/ShrineData");
Misc.openChestsEnabled = true;
-Misc.presetChestIds = [
- 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
-];
+Misc.screenshotErrors = true;
+/**
+ * @override
+ * @template T
+ * @param {number} area
+ * @param {number[]} chestIds
+ * @param {function(T, T): number} [sort]
+ * @returns {boolean}
+ */
Misc.openChestsInArea = function (area, chestIds = [], sort = undefined) {
- !area && (area = me.area);
- area !== me.area && Pather.journeyTo(area);
-
- let presetUnits = Game.getPresetObjects(area);
- if (!presetUnits) return false;
-
- !chestIds.length && (chestIds = Misc.presetChestIds.slice(0));
-
- 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 ? 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;
+ !area && (area = me.area);
+ typeof sort !== "function" && (sort = Sort.units);
+ area !== me.area && Pather.journeyTo(area);
+ !chestIds.length && (chestIds = sdk.objects.chestIds.slice(0));
+
+ const presetUnits = Game.getPresetObjects(area)
+ .filter(function (preset) {
+ return chestIds.includes(preset.id);
+ });
+ if (!presetUnits.length) return false;
+
+ let coords = presetUnits
+ .map(function (preset) {
+ return preset.realCoords();
+ });
+
+ while (coords.length) {
+ coords.sort(sort);
+ 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;
};
/**
@@ -86,807 +58,860 @@ Misc.openChestsInArea = function (area, chestIds = [], sort = undefined) {
* @returns {boolean} If we opened the chest
*/
Misc.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);
- let useDodge = Pather.useTeleport() && Skill.useTK(unit);
- if (useTK) {
- unit.distance > 18 && Attack.getIntoPosition(unit, 18, sdk.collision.WallOrRanged, false, true);
- if (!Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit)) {
- console.debug("Failed to tk: attempt: " + i);
- continue;
- }
- } else {
- if (useDodge && me.inDanger()) {
- if (Attack.getIntoPosition(unit, 18, sdk.collision.WallOrRanged, false, true)) continue;
- }
- [(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;
+ 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);
+ let useDodge = Pather.useTeleport() && Skill.useTK(unit);
+ if (useTK) {
+ unit.distance > 18 && Attack.getIntoPosition(unit, 18, sdk.collision.WallOrRanged, false, true);
+ if (!Packet.telekinesis(unit)) {
+ console.debug("Failed to tk: attempt: " + i);
+ continue;
+ }
+ } else {
+ if (useDodge && me.inDanger()) {
+ if (Attack.getIntoPosition(unit, 18, sdk.collision.WallOrRanged, false, true)) continue;
+ }
+ [(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 || 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;
};
+/**
+ * @param {number} range
+ * @returns {boolean}
+ * @todo Take path parameter to we can open the chests in an order that brings us closer to our destination
+ */
Misc.openChests = function (range = 15) {
- if (!Misc.openChestsEnabled) return false;
- const containers = [
- "chest", "loose rock", "hidden stash", "loose boulder", "corpseonstick", "casket", "armorstand", "weaponrack",
- "holeanim", "roguecorpse", "corpse", "tomb2", "tomb3", "chest3",
- "skeleton", "guardcorpse", "sarcophagus", "object2", "cocoon", "hollow log", "hungskeleton",
- "bonechest", "woodchestl", "woodchestr",
- "burialchestr", "burialchestl", "chestl", "chestr", "groundtomb", "tomb3l", "tomb1l",
- "deadperson", "deadperson2", "groundtombl", "casket"
- ];
-
- if (Config.OpenChests.Types.some((el) => el.toLowerCase() === "all")) {
- containers.push(
- "barrel", "ratnest", "goo pile", "largeurn", "urn", "jug", "basket", "stash",
- "pillar", "skullpile", "skull pile", "jar3", "jar2", "jar1", "barrel wilderness",
- "explodingchest", "icecavejar1", "icecavejar2", "icecavejar3",
- "icecavejar4", "evilurn"
- );
- }
-
- me.baal && containers.push("evilurn");
-
- let unitList = getUnits(sdk.unittype.Object)
- .filter(c => c.name && c.mode === sdk.objects.mode.Inactive && c.distance <= range && containers.includes(c.name.toLowerCase()));
-
- while (unitList.length > 0) {
- unitList.sort(Sort.units);
- let unit = unitList.shift();
-
- if (unit) {
- // check mob count at chest - think I need a new prototype for faster checking
- // allow specifying an amount and return true/false, rather than building the whole list then deciding what amount is too much
- // possibly also specify a danger modifier - 3 champions around a chest is much more dangerous than 3 fallens
- // also think we need to take into account mob count arround us, we shouldn't open chests when we are surrounded and in the process of clearing
- // that needs a handler as well though, if we aren't clearing and are just pathing (tele char) opening a chest and moving on is fine
- }
-
- if (unit && (Pather.useTeleport() || !checkCollision(me, unit, sdk.collision.WallOrRanged)) && this.openChest(unit)) {
- Pickit.pickItems();
- }
- }
-
- return true;
+ if (!Misc.openChestsEnabled) return false;
+ const containers = [
+ "chest", "loose rock", "hidden stash", "loose boulder", "corpseonstick", "casket", "armorstand", "weaponrack",
+ "holeanim", "roguecorpse", "corpse", "tomb2", "tomb3", "chest3",
+ "skeleton", "guardcorpse", "sarcophagus", "object2", "cocoon", "hollow log", "hungskeleton",
+ "bonechest", "woodchestl", "woodchestr",
+ "burialchestr", "burialchestl", "chestl", "chestr", "groundtomb", "tomb3l", "tomb1l",
+ "deadperson", "deadperson2", "groundtombl", "casket"
+ ];
+
+ if (Config.OpenChests.Types.some((el) => el.toLowerCase() === "all")) {
+ containers.push(
+ "barrel", "ratnest", "goo pile", "largeurn", "urn", "jug", "basket", "stash",
+ "pillar", "skullpile", "skull pile", "jar3", "jar2", "jar1", "barrel wilderness",
+ "explodingchest", "icecavejar1", "icecavejar2", "icecavejar3",
+ "icecavejar4", "evilurn"
+ );
+ }
+
+ me.baal && containers.push("evilurn");
+
+ let unitList = getUnits(sdk.unittype.Object)
+ .filter(function (c) {
+ return c.name
+ && c.mode === sdk.objects.mode.Inactive
+ && c.distance <= range
+ && containers.includes(c.name.toLowerCase());
+ });
+
+ while (unitList.length > 0) {
+ unitList.sort(Sort.units);
+ let unit = unitList.shift();
+
+ if (unit && Pather.currentWalkingPath.length) {
+ /**
+ * @todo - check if the chest is in our path of if in the future we would be closer to it
+ * and if so assign a hook to be triggered at our point nearest to it so we can open it
+ * and save time
+ */
+ if (unit.distance > 5 && PathDebug.coordsInPath(Pather.currentWalkingPath, unit.x, unit.y)) {
+ console.log("Skipping chest for now as it is in our path for later");
+ continue;
+ }
+ // check mob count at chest - think I need a new prototype for faster checking
+ // allow specifying an amount and return true/false, rather than building the whole list then deciding what amount is too much
+ // possibly also specify a danger modifier - 3 champions around a chest is much more dangerous than 3 fallens
+ // also think we need to take into account mob count arround us, we shouldn't open chests when we are surrounded and in the process of clearing
+ // that needs a handler as well though, if we aren't clearing and are just pathing (tele char) opening a chest and moving on is fine
+ }
+
+ /**
+ * @todo
+ * - evaluate actual walking distance to chest, as if it's far out of the way it maybe be better to skip it
+ * especially early on when we are trying to get to the next area
+ */
+
+ if (unit
+ && (Pather.useTeleport() || !checkCollision(me, unit, sdk.collision.BlockWalk))
+ && this.openChest(unit)) {
+ Pickit.pickItems();
+ }
+ }
+
+ return true;
};
+/**
+ * @param {ObjectUnit} unit
+ * @returns {boolean}
+ */
Misc.getWell = function (unit) {
- if (!unit || 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);
- checkCollision(me, unit, sdk.collision.Ranged) && Attack.getIntoPosition(unit, 20, sdk.collision.Ranged);
- Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, unit);
- } else {
- if (unit.distance < 4 || Pather.moveToUnit(unit, 3, 0)) {
- Misc.click(0, 0, unit);
- }
- }
-
- if (Misc.poll(() => unit.mode, 1000, 50)) return true;
- Packet.flash(me.gid);
- }
-
- return false;
+ if (!unit || 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 (checkCollision(me, unit, sdk.collision.Ranged)) {
+ Attack.getIntoPosition(unit, 20, sdk.collision.Ranged);
+ }
+ Packet.telekinesis(unit);
+ } else {
+ if (unit.distance < 4 || Pather.moveToUnit(unit, 3, 0)) {
+ Misc.click(0, 0, unit);
+ }
+ }
+
+ if (Misc.poll(function () { return unit.mode; }, 1000, 50)) return true;
+ Packet.flash(me.gid);
+ }
+
+ return false;
};
Misc.useWell = function (range = 15) {
- // I'm in perfect health, don't need this shit
- if (me.hpPercent >= 95 && me.mpPercent >= 95 && me.staminaPercent >= 50
- && [sdk.states.Frozen, sdk.states.Poison, sdk.states.AmplifyDamage, sdk.states.Decrepify].every((states) => !me.getState(states))) {
- return true;
- }
-
- Pather.canTeleport() && me.hpPercent < 60 && (range = 25);
-
- let unitList = getUnits(sdk.unittype.Object, "well").filter(function (well) {
- return well.distance < range && well.mode !== sdk.objects.mode.Active;
- });
-
- while (unitList.length > 0) {
- unitList.sort(Sort.units);
- let unit = unitList.shift();
+ // I'm in perfect health, don't need this shit
+ if (me.hpPercent >= 95 && me.mpPercent >= 95 && me.staminaPercent >= 50
+ && [
+ sdk.states.Frozen, sdk.states.Poison,
+ sdk.states.AmplifyDamage, sdk.states.Decrepify
+ ].every(function (states) {
+ return !me.getState(states);
+ })) {
+ return true;
+ }
+
+ Pather.canTeleport() && me.hpPercent < 60 && (range = 25);
+
+ let unitList = getUnits(sdk.unittype.Object, "well").filter(function (well) {
+ return well.distance < range && well.mode !== sdk.objects.mode.Active;
+ });
+
+ while (unitList.length > 0) {
+ unitList.sort(Sort.units);
+ let unit = unitList.shift();
+
+ if (unit && (Pather.useTeleport() || !checkCollision(me, unit, sdk.collision.WallOrRanged))) {
+ this.getWell(unit);
+ }
+ }
+
+ return true;
+};
- if (unit && (Pather.useTeleport() || !checkCollision(me, unit, sdk.collision.WallOrRanged))) {
- this.getWell(unit);
- }
- }
+Misc.lastShrine = new function () {
+ this.tick = 0;
+ this.duration = 0;
+ this.type = -1;
+ this.state = -1;
+
+ /** @param {ObjectUnit} unit */
+ this.update = function (unit) {
+ if (!unit || !unit.hasOwnProperty("objtype")) return;
+ // we only care about tracking shrines with states
+ if (!ShrineData.getState(unit.objtype)) return;
+ this.tick = getTickCount();
+ this.type = unit.objtype;
+ this.duration = ShrineData.getDuration(unit.objtype);
+ this.state = ShrineData.getState(unit.objtype);
+ };
+
+ this.remaining = function () {
+ return this.duration - (getTickCount() - this.tick);
+ };
+
+ this.isMyCurrentState = function () {
+ if (this.state <= 0) return false;
+ return me.getState(this.state);
+ };
+};
- return true;
+/**
+ * Use a shrine Unit
+ * @param {ObjectUnit} unit
+ * @returns {boolean}
+ */
+Misc.getShrine = function (unit) {
+ if (unit.mode === sdk.objects.mode.Active) return false;
+ AreaData.get(me.area).addShrine(unit);
+ if (Misc.lastShrine.remaining() > Time.seconds(30) && Misc.lastShrine.isMyCurrentState()) {
+ // skip for now, don't waste the shrine we have 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)) {
+ AreaData.get(me.area).updateShrine(unit);
+ Misc.lastShrine.update(unit);
+ if (unit.objtype === sdk.shrines.Gem) {
+ Pickit.pickItems();
+ }
+ return true;
+ }
+ }
+
+ return false;
};
+/**
+ * @param {number} range
+ * @param {number[]} ignore
+ * @returns {boolean}
+ */
Misc.scanShrines = function (range, ignore = []) {
- if (!Config.ScanShrines.length) return false;
-
- !range && (range = Pather.useTeleport() ? 25 : 15);
- !Array.isArray(ignore) && (ignore = [ignore]);
-
- let shrineList = [];
- const rangeCheck = (shrineType) => {
- switch (true) {
- case shrineType === sdk.shrines.Refilling && (me.hpPercent < 50 || me.mpPercent < 50 || me.staminaPercent < 50):
- case shrineType === sdk.shrines.Mana && me.mpPercent < 50:
- case shrineType === sdk.shrines.ManaRecharge && me.mpPercent < 50 && me.charlvl < 20:
- case [sdk.shrines.Skill, sdk.shrines.Experience].includes(shrineType):
- return 30;
- case [sdk.shrines.Poison, sdk.shrines.Exploding].includes(shrineType):
- return 15;
- }
- return range;
- };
-
- // add exploding/poision shrines
- if (me.normal) {
- Config.ScanShrines.indexOf(sdk.shrines.Poison) === -1 && Config.ScanShrines.push(sdk.shrines.Poison);
- Config.ScanShrines.indexOf(sdk.shrines.Exploding) === -1 && Config.ScanShrines.push(sdk.shrines.Exploding);
- }
-
- // 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) <= rangeCheck(shrine.objtype)) {
- 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;
+ if (!Config.ScanShrines.length) return false;
+
+ !range && (range = Pather.useTeleport() ? 25 : 15);
+ !Array.isArray(ignore) && (ignore = [ignore]);
+
+ /** @type {ObjectUnit[]} */
+ let shrineList = [];
+
+ const rangeCheck = function (shrineType) {
+ switch (true) {
+ case shrineType === sdk.shrines.Refilling && (me.hpPercent < 50 || me.mpPercent < 50 || me.staminaPercent < 50):
+ case shrineType === sdk.shrines.Mana && me.mpPercent < 50:
+ case shrineType === sdk.shrines.ManaRecharge && me.mpPercent < 50 && me.charlvl < 20:
+ case [sdk.shrines.Skill, sdk.shrines.Experience].includes(shrineType):
+ return 30;
+ case [sdk.shrines.Poison, sdk.shrines.Exploding].includes(shrineType):
+ return 15;
+ }
+ return range;
+ };
+
+ // add exploding/poision shrines
+ if (me.normal) {
+ Config.ScanShrines.indexOf(sdk.shrines.Poison) === -1 && Config.ScanShrines.push(sdk.shrines.Poison);
+ Config.ScanShrines.indexOf(sdk.shrines.Exploding) === -1 && Config.ScanShrines.push(sdk.shrines.Exploding);
+ }
+
+ // Initiate shrine states
+ if (!Misc.shrineStates) {
+ Misc.shrineStates = [];
+ let i = 0;
+ for (let shrine of Config.ScanShrines) {
+ if (shrine > 0) {
+ Misc.shrineStates[i] = ShrineData.getState(shrine);
+ i++;
+ }
+ }
+ }
+
+ /**
+ * @todo - We should build a list of shrines by their preset values when we scan the area
+ */
+
+ let shrine = Game.getObject();
+
+ /**
+ * Fix for a3/a5 shrines
+ */
+ if (shrine) {
+ // Build a list of nearby shrines
+ do {
+ if (shrine.name.toLowerCase().includes("shrine") && ShrineData.has(shrine.objtype)
+ && shrine.mode === sdk.objects.mode.Inactive && !ignore.includes(shrine.objtype)
+ && getDistance(me.x, me.y, shrine.x, shrine.y) <= rangeCheck(shrine.objtype)) {
+ shrineList.push(copyUnit(shrine));
+ }
+ } while (shrine.getNext());
+ if (!shrineList.length) return false;
+
+ // Check if we have a shrine state, store its index if yes
+ const index = Misc.shrineStates.findIndex(function (state) {
+ return state > 0 && me.getState(state);
+ });
+
+ 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]
+ && (Pather.useTeleport() || !checkCollision(me, shrine, sdk.collision.WallOrRanged))) {
+ this.getShrine(shrine);
+
+ // Gem shrine - pick gem
+ if (Config.ScanShrines[i] === sdk.shrines.Gem) {
+ Pickit.pickItems();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return true;
};
-Misc.presetShrineIds = [2, 81, 83];
+/**
+ * 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 If we are trying to find a specific shrine then generate path and perform callback after each node to see if we are within range
+ * of getUnit and can see the shrine type so we know whether to continue moving to it or not.
+ */
Misc.getShrinesInArea = function (area, type, use) {
- let shrineLocs = [];
- const shrineIds = [2, 81, 83]; // @todo add more of the shrine id's and document them for sdk
- let unit = getPresetUnits(area);
-
- 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)) {
- return true;
- }
-
- if (use && type >= sdk.shrines.Armor && type <= sdk.shrines.Experience && me.getState(type + 122)) {
- return true;
- }
- }
- } while (shrine.getNext());
- }
- }
- } finally {
- NodeAction.shrinesToIgnore.remove(type);
- }
-
- return false;
+ if (!area || !AreaData.has(area)) return false;
+ let shrineLocs = [];
+ let result = false;
+ let 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());
+ }
+ } else if (AreaData.get(area).getShrines().length) {
+ shrineLocs = AreaData.get(area)
+ .getShrines()
+ .filter(function (shrine) {
+ return shrine.useable();
+ });
+ } else {
+ return false;
+ }
+
+ try {
+ NodeAction.shrinesToIgnore.push(type);
+
+ while (shrineLocs.length > 0) {
+ shrineLocs.sort(Sort.units);
+ let coords = shrineLocs.shift();
+
+ Pather.move(coords, { minDist: Skill.haveTK ? 20 : 5, callback: function () {
+ 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 === 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(5);
+ }
+ return true;
+ }
+
+ if (use && type >= sdk.shrines.Armor
+ && type <= sdk.shrines.Experience
+ && me.getState(type + 122)) {
+ return true;
+ }
+ }
+ } while (shrine.getNext());
+ }
+ }
+ } finally {
+ NodeAction.shrinesToIgnore.remove(type);
+ }
+
+ return result;
};
Misc.getExpShrine = function (shrineLocs = []) {
- if (me.getState(sdk.states.ShrineExperience)) return true;
-
- for (let get = 0; get < shrineLocs.length; get++) {
- me.overhead("Looking for xp shrine");
-
- if (shrineLocs[get] === sdk.areas.BloodMoor) {
- Pather.journeyTo(shrineLocs[get]);
- } else {
- Pather.checkWP(shrineLocs[get], true) ? Pather.useWaypoint(shrineLocs[get]) : Pather.getWP(shrineLocs[get]);
- }
-
- Precast.doPrecast(true);
- Misc.getShrinesInArea(shrineLocs[get], sdk.shrines.Experience, true);
-
- if (me.getState(sdk.states.ShrineExperience)) {
- break;
- }
-
- !me.inTown && Town.goToTown();
- }
-
- return true;
+ if (me.getState(sdk.states.ShrineExperience)) return true;
+
+ for (let area of shrineLocs) {
+ me.overhead("Looking for xp shrine");
+
+ if (area === sdk.areas.BloodMoor) {
+ Pather.journeyTo(area);
+ } else {
+ Pather.checkWP(area, true)
+ ? Pather.useWaypoint(area)
+ : Pather.getWP(area);
+ }
+
+ Precast.doPrecast(true);
+ Misc.getShrinesInArea(area, sdk.shrines.Experience, true);
+
+ if (me.getState(sdk.states.ShrineExperience)) {
+ return true;
+ }
+
+ !me.inTown && Town.goToTown();
+ }
+
+ // this needs work but idea is we can leverage the shrine data gathered during regular script actions
+ // to find the closest xp shrine to us and go to it without having to search a bunch of different areas
+ // let _xpShrineAreas = AreaData.getAreasWithShrine(sdk.shrines.Experience);
+ // if (_xpShrineAreas.length) {
+ // for (let area of _xpShrineAreas) {
+ // me.overhead("Looking for xp shrine");
+ // Pather.journeyTo(area.Index);
+ // let _shrine = area.Shrines.find(function (shrine) {
+ // return shrine.Type === sdk.shrines.Experience;
+ // });
+ // Pather.move(_shrine, { minDist: Skill.haveTK ? 20 : 5, callback: function () {
+ // let shrine = Game.getObject(-1, sdk.objects.mode.Inactive, _shrine.gid);
+ // return !!shrine && shrine.x === _shrine.x && shrine.y === _shrine.y;
+ // } });
+ // if (Misc.getShrine(Game.getObject(-1, sdk.objects.mode.Inactive, _shrine.gid))) {
+ // return true;
+ // }
+ // }
+ // }
+ return true;
};
+/**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
Misc.unsocketItem = function (item) {
- if (me.classic || !me.getItem(sdk.items.quest.Cube) || !item) return false;
- // Item doesn't have anything socketed
- if (item.getItemsEx().length === 0) return true;
-
- let hel = me.getItem(sdk.items.runes.Hel, sdk.items.mode.inStorage);
- if (!hel) return false;
-
- let scroll = Runewords.getScroll();
- let bodyLoc;
- let {classid, quality} = item;
- item.isEquipped && (bodyLoc = item.bodylocation);
-
- // 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;
-
- try {
- // failed to move any of the items to the cube
- if (!Storage.Cube.MoveTo(item) || !Storage.Cube.MoveTo(hel) || !Storage.Cube.MoveTo(scroll)) throw "Failed to move items to cube";
-
- // probably only happens on server crash
- if (!Cubing.openCube()) throw "Failed to open cube";
-
- myPrint("ÿc4Removing sockets from: ÿc0" + item.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, ""));
- transmute();
- delay(500);
- // unsocketing an item causes loss of reference, so re-find our item
- item = me.findItem(classid, -1, sdk.storage.Cube);
- !!item && bodyLoc && item.equip(bodyLoc);
-
- // can't pull the item out = no space = fail
- if (!Cubing.emptyCube()) throw "Failed to empty cube";
- } catch (e) {
- console.debug(e);
- } finally {
- // lost the item, so relocate it
- !item && (item = me.findItem(classid, -1, -1, quality));
- // In case error was thrown before hitting above re-equip statement
- bodyLoc && !item.isEquipped && item.equip(bodyLoc);
- // No bodyloc so move back to stash
- !bodyLoc && !item.isInStash && Storage.Stash.MoveTo(item);
- getUIFlag(sdk.uiflags.Cube) && me.cancel();
- }
-
- return item.getItemsEx().length === 0;
+ if (me.classic || !me.getItem(sdk.items.quest.Cube) || !item) return false;
+ // Item doesn't have anything socketed
+ if (item.getItemsEx().length === 0) return true;
+
+ let hel = me.getItem(sdk.items.runes.Hel, sdk.items.mode.inStorage);
+ if (!hel) return false;
+
+ let scroll = Runewords.getScroll();
+ let bodyLoc;
+ let { classid, quality } = item;
+ item.isEquipped && (bodyLoc = item.bodylocation);
+
+ // 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;
+
+ try {
+ // failed to move any of the items to the cube
+ if (!Storage.Cube.MoveTo(item)
+ || !Storage.Cube.MoveTo(hel)
+ || !Storage.Cube.MoveTo(scroll)) {
+ throw new Error("Failed to move items to cube");
+ }
+
+ // probably only happens on server crash
+ if (!Cubing.openCube()) throw "Failed to open cube";
+
+ myPrint("ÿc4Removing sockets from: ÿc0" + item.prettyPrint);
+ transmute();
+ delay(500);
+ // unsocketing an item causes loss of reference, so re-find our item
+ item = me.findItem(classid, -1, sdk.storage.Cube);
+ !!item && bodyLoc && item.equip(bodyLoc);
+
+ // can't pull the item out = no space = fail
+ if (!Cubing.emptyCube()) throw "Failed to empty cube";
+ } catch (e) {
+ console.debug(e);
+ } finally {
+ // lost the item, so relocate it
+ !item && (item = me.findItem(classid, -1, -1, quality));
+ // In case error was thrown before hitting above re-equip statement
+ bodyLoc && !item.isEquipped && item.equip(bodyLoc);
+ // No bodyloc so move back to stash
+ !bodyLoc && !item.isInStash && Storage.Stash.MoveTo(item);
+ getUIFlag(sdk.uiflags.Cube) && me.cancel();
+ }
+
+ return item.getItemsEx().length === 0;
};
Misc.checkItemsForSocketing = function () {
- if (me.classic || !me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.ReqComplete)) return false;
-
- let items = me.getItemsEx()
- .filter(item => item.sockets === 0 && getBaseStat("items", item.classid, "gemsockets") > 0)
- .sort((a, b) => NTIP.GetTier(b) - NTIP.GetTier(a));
-
- for (let i = 0; i < items.length; i++) {
- let curr = Config.socketables.find(({ classid }) => items[i].classid === classid);
- if (curr && curr.condition(items[i]) && curr.useSocketQuest) {
- return items[i];
- }
- }
-
- return false;
+ if (me.classic || !me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.ReqComplete)) return false;
+
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item.sockets === 0 && getBaseStat("items", item.classid, "gemsockets") > 0;
+ })
+ .sort(function (a, b) {
+ return NTIP.GetTier(b) - NTIP.GetTier(a);
+ });
+
+ for (let item of items) {
+ let curr = Config.socketables.find(({ classid }) => item.classid === classid);
+ if (curr && curr.condition(item) && curr.useSocketQuest) {
+ return item;
+ }
+ }
+
+ return false;
};
Misc.checkItemsForImbueing = function () {
- if (!me.getQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete)) return false;
+ if (!me.getQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete)) return false;
- let items = me.getItemsEx().filter(item => item.sockets === 0 && (item.normal || item.superior));
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item.sockets === 0 && (item.normal || item.superior);
+ });
- for (let i = 0; i < items.length; i++) {
- if (Config.imbueables.some(item => item.name === items[i].classid && Item.canEquip(items[i]))) {
- return items[i];
- }
- }
+ for (let item of items) {
+ if (Config.imbueables
+ .some(imbueable => imbueable.name === item.classid && Item.canEquip(item))) {
+ return item;
+ }
+ }
- return false;
+ return false;
};
+/**
+ * @param {ItemUnit} item
+ * @param {ItemUnit[]} runes
+ * @returns {boolean}
+ */
Misc.addSocketablesToItem = function (item, runes = []) {
- if (!item || item.sockets === 0) return false;
- let preSockets = item.getItemsEx().length;
- let original = preSockets;
- let bodyLoc;
-
- if (item.isEquipped) {
- bodyLoc = item.bodylocation;
-
- if (!Storage.Inventory.CanFit(item)) {
- Town.sortInventory();
-
- if (!Storage.Inventory.CanFit(item) && !Storage.Inventory.MoveTo(item)) {
- console.log("ÿc8AddSocketableToItemÿc0 :: No space to get item back");
- return false;
- }
- } else {
- if (!Storage.Inventory.MoveTo(item)) return false;
- }
- }
-
- if (!Town.openStash()) return false;
-
- for (let i = 0; i < runes.length; i++) {
- let rune = runes[i];
- if (!rune.toCursor()) return false;
-
- for (let i = 0; i < 3; i += 1) {
- sendPacket(1, sdk.packets.send.InsertSocketItem, 4, rune.gid, 4, item.gid);
- let tick = getTickCount();
-
- while (getTickCount() - tick < 2000) {
- if (!me.itemoncursor) {
- delay(300);
-
- break;
- }
-
- delay(10);
- }
-
- if (item.getItemsEx().length > preSockets) {
- D2Bot.printToConsole("Added socketable: " + rune.fname + " to " + item.fname, sdk.colors.D2Bot.Gold);
- Misc.logItem("Added " + rune.name + " to: ", item, null, true);
- preSockets++;
- }
- }
- }
-
- bodyLoc && Item.equip(item, bodyLoc);
-
- return item.getItemsEx().length > original;
+ if (!item || item.sockets === 0) return false;
+ let preSockets = item.getItemsEx().length;
+ let original = preSockets;
+ let bodyLoc;
+
+ if (item.isEquipped) {
+ bodyLoc = item.bodylocation;
+
+ if (!Storage.Inventory.CanFit(item)) {
+ me.sortInventory();
+
+ if (!Storage.Inventory.CanFit(item) && !Storage.Inventory.MoveTo(item)) {
+ console.log("ÿc8AddSocketableToItemÿc0 :: No space to get item back");
+ return false;
+ }
+ } else {
+ if (!Storage.Inventory.MoveTo(item)) return false;
+ }
+ }
+
+ if (!Town.openStash()) return false;
+
+ for (let rune of runes) {
+ if (!rune.toCursor()) return false;
+
+ for (let i = 0; i < 3; i += 1) {
+ new PacketBuilder()
+ .byte(sdk.packets.send.InsertSocketItem)
+ .dword(rune.gid)
+ .dword(item.gid)
+ .send();
+
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < 2000) {
+ if (!me.itemoncursor) {
+ delay(300);
+
+ break;
+ }
+
+ delay(10);
+ }
+
+ if (item.getItemsEx().length > preSockets) {
+ D2Bot.printToConsole("Added socketable: " + rune.fname + " to " + item.fname, sdk.colors.D2Bot.Gold);
+ Item.logItem("Added " + rune.name + " to: ", item, null, true);
+ preSockets++;
+ }
+ }
+ }
+
+ bodyLoc && Item.equip(item, bodyLoc);
+
+ return item.getItemsEx().length > original;
};
+/**
+ * @param {ItemUnit} item
+ * @param {{ classid: number, socketWith: number[], temp: number[], useSocketQuest: boolean, condition: Function }} [itemInfo]
+ * @returns {boolean}
+ */
Misc.getSocketables = function (item, itemInfo) {
- if (!item) return false;
- let itemtype, gemType, runeType;
- let [multiple, temp] = [[], []];
- let itemSocketInfo = item.getItemsEx();
- let preSockets = itemSocketInfo.length;
- let allowTemp = (!!itemInfo && !!itemInfo.temp && itemInfo.temp.length > 0 && (preSockets === 0 || preSockets > 0 && itemSocketInfo.some(el => !itemInfo.socketWith.includes(el.classid))));
- let sockets = item.sockets;
- let openSockets = sockets - preSockets;
- let { classid, quality } = item;
- let socketables = me.getItemsEx().filter(item => item.isInsertable);
-
- if (!socketables || (!allowTemp && openSockets === 0)) return false;
-
- function highestGemAvailable (gem, checkList = []) {
- if (!gem) return false;
-
- // filter out all items that aren't the gem type we are looking for
- // then sort the highest classid (better gems first)
- let myItems = me.getItemsEx()
- .filter(item => item.itemType === gem.itemType)
- .sort((a, b) => b.classid - a.classid);
-
- for (let i = 0; i < myItems.length; i++) {
- if (!checkList.includes(myItems[i])) return true;
- }
-
- return false;
- }
-
- if (!itemInfo || (!!itemInfo && itemInfo.socketWith.length === 0)) {
- itemtype = item.getItemType();
- if (!itemtype) return false;
- gemType = ["Helmet", "Armor"].includes(itemtype) ? "Ruby" : itemtype === "Shield" ? "Diamond" : itemtype === "Weapon" && !Check.currentBuild().caster ? "Skull" : "";
-
- // Tir rune in normal, Io rune otherwise and Shael's if assassin
- !gemType && (runeType = me.normal ? "Tir" : me.assassin ? "Shael" : "Io");
-
- // TODO: Use Jewels
- // would need to score them and way to compare to runes/gems by what itemtype we are looking at
- // then keep upgrading until we actually are ready to insert in the item
- }
-
- for (let i = 0; i < socketables.length; i++) {
- if (!!itemInfo && itemInfo.socketWith.length > 0) {
- // In case we are trying to use different runes, check if item already has current rune inserted
- // or if its already in the muliple list. If it is, remove that socketables classid from the list of wanted classids
- if (itemInfo.socketWith.length > 1
- && (itemSocketInfo.some(el => el.classid === socketables[i].classid) || multiple.some(el => el.classid === socketables[i].classid))) {
- itemInfo.socketWith.remove(socketables[i].classid);
- }
-
- if (itemInfo.socketWith.includes(socketables[i].classid) && !multiple.includes(socketables[i])) {
- if (multiple.length < sockets) {
- multiple.push(socketables[i]);
- }
- }
-
- if (allowTemp && itemInfo.temp.includes(socketables[i].classid) && !temp.includes(socketables[i])) {
- if (temp.length < sockets) {
- temp.push(socketables[i]);
- }
- }
- } else {
- // If itemtype was matched with a gemType
- if (gemType) {
- // current item matches wanted gemType
- if (socketables[i].itemType === sdk.items.type[gemType]) {
- // is the highest gem of that type
- if (highestGemAvailable(socketables[i], multiple)) {
- if (multiple.length < sockets) {
- multiple.push(socketables[i]);
- }
- }
- }
- } else if (runeType) {
- if (socketables[i].classid === sdk.items.runes[runeType] && !multiple.includes(socketables[i])) {
- if (multiple.length < sockets) {
- multiple.push(socketables[i]);
- }
- }
- }
- }
-
- if (multiple.length === sockets) {
- break;
- }
- }
-
- if (allowTemp) {
- // we have all our wanted socketables
- if (multiple.length === sockets) {
- // Failed to remove temp socketables
- if (!Misc.unsocketItem(item)) return false;
- // relocate our item as unsocketing it causes loss of reference
- item = me.findItem(classid, -1, -1, quality);
- openSockets = sockets;
- } else {
- if (temp.length > 0) {
- // use temp socketables
- multiple = temp.slice(0);
- } else if (item.getItemsEx().some((el) => itemInfo.temp.includes(el.classid))) {
- return false;
- }
- }
- }
-
- if (multiple.length > 0) {
- multiple.length > openSockets && (multiple.length = openSockets);
- if (openSockets === 0) return false;
- // check to ensure I am a high enough level to use wanted socketables
- for (let i = 0; i < multiple.length; i++) {
- if (me.charlvl < multiple[i].lvlreq) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Not high enough level for " + multiple[i].fname);
- return false;
- }
- }
-
- if (Misc.addSocketablesToItem(item, multiple)) {
- delay(250 + me.ping);
- } else {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to add socketable to " + item.fname);
- }
-
- return item.getItemsEx().length === sockets || item.getItemsEx().length > preSockets;
- }
-
- return false;
+ if (!item) return false;
+ itemInfo === undefined && (itemInfo = {});
+
+ let itemtype, gemType, runeType;
+ let [multiple, temp] = [[], []];
+ let itemSocketInfo = item.getItemsEx();
+ let preSockets = itemSocketInfo.length;
+ let allowTemp = (itemInfo.hasOwnProperty("temp") && itemInfo.temp.length > 0
+ && (preSockets === 0 || preSockets > 0 && itemSocketInfo.some(el => !itemInfo.socketWith.includes(el.classid))));
+ let sockets = item.sockets;
+ let openSockets = sockets - preSockets;
+ let { classid, quality } = item;
+ let socketables = me.getItemsEx()
+ .filter(function (item) {
+ return item.isInsertable;
+ });
+
+ if (!socketables || (!allowTemp && openSockets === 0)) return false;
+
+ function highestGemAvailable (gem, checkList = []) {
+ if (!gem) return false;
+
+ // filter out all items that aren't the gem type we are looking for
+ // then sort the highest classid (better gems first)
+ let myItems = me.getItemsEx()
+ .filter(function (item) {
+ return item.itemType === gem.itemType;
+ })
+ .sort(function (a, b) {
+ return b.classid - a.classid;
+ });
+
+ for (let item of myItems) {
+ if (!checkList.includes(item)) return true;
+ }
+
+ return false;
+ }
+
+ if (!itemInfo.hasOwnProperty("socketWith")
+ || (itemInfo.hasOwnProperty("socketWith") && itemInfo.socketWith.length === 0)) {
+ itemtype = item.getItemType();
+ if (!itemtype) return false;
+ gemType = ["Helmet", "Armor"].includes(itemtype)
+ ? "Ruby" : itemtype === "Shield"
+ ? "Diamond" : itemtype === "Weapon" && !Check.currentBuild().caster
+ ? "Skull" : "";
+
+ // Tir rune in normal, Io rune otherwise and Shael's if assassin
+ !gemType && (runeType = me.normal ? "Tir" : me.assassin ? "Shael" : "Io");
+
+ // TODO: Use Jewels
+ // would need to score them and way to compare to runes/gems by what itemtype we are looking at
+ // then keep upgrading until we actually are ready to insert in the item
+ }
+
+ for (let i = 0; i < socketables.length; i++) {
+ if (itemInfo.hasOwnProperty("socketWith") && itemInfo.socketWith.length > 0) {
+ // In case we are trying to use different runes, check if item already has current rune inserted
+ // or if its already in the muliple list. If it is, remove that socketables classid from the list of wanted classids
+ if (itemInfo.socketWith.length > 1
+ && (itemSocketInfo.some(el => el.classid === socketables[i].classid) || multiple.some(el => el.classid === socketables[i].classid))) {
+ itemInfo.socketWith.remove(socketables[i].classid);
+ }
+
+ if (itemInfo.socketWith.includes(socketables[i].classid) && !multiple.includes(socketables[i])) {
+ if (multiple.length < sockets) {
+ multiple.push(socketables[i]);
+ }
+ }
+
+ if (allowTemp && itemInfo.temp.includes(socketables[i].classid) && !temp.includes(socketables[i])) {
+ if (temp.length < sockets) {
+ temp.push(socketables[i]);
+ }
+ }
+ } else {
+ // If itemtype was matched with a gemType
+ if (gemType) {
+ // current item matches wanted gemType
+ if (socketables[i].itemType === sdk.items.type[gemType]) {
+ // is the highest gem of that type
+ if (highestGemAvailable(socketables[i], multiple)) {
+ if (multiple.length < sockets) {
+ multiple.push(socketables[i]);
+ }
+ }
+ }
+ } else if (runeType) {
+ if (socketables[i].classid === sdk.items.runes[runeType] && !multiple.includes(socketables[i])) {
+ if (multiple.length < sockets) {
+ multiple.push(socketables[i]);
+ }
+ }
+ }
+ }
+
+ if (multiple.length === sockets) {
+ break;
+ }
+ }
+
+ if (allowTemp) {
+ // we have all our wanted socketables
+ if (multiple.length === sockets) {
+ // Failed to remove temp socketables
+ if (!Misc.unsocketItem(item)) return false;
+ // relocate our item as unsocketing it causes loss of reference
+ item = me.findItem(classid, -1, -1, quality);
+ openSockets = sockets;
+ } else {
+ if (temp.length > 0) {
+ // use temp socketables
+ multiple = temp.slice(0);
+ } else if (item.getItemsEx().some((el) => itemInfo.temp.includes(el.classid))) {
+ return false;
+ }
+ }
+ }
+
+ if (multiple.length > 0) {
+ multiple.length > openSockets && (multiple.length = openSockets);
+ if (openSockets === 0) return false;
+ // check to ensure I am a high enough level to use wanted socketables
+ for (let i = 0; i < multiple.length; i++) {
+ if (me.charlvl < multiple[i].lvlreq) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Not high enough level for " + multiple[i].fname);
+ return false;
+ }
+ }
+
+ if (Misc.addSocketablesToItem(item, multiple)) {
+ delay(250 + me.ping);
+ } else {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to add socketable to " + item.fname);
+ }
+
+ return item.getItemsEx().length === sockets || item.getItemsEx().length > preSockets;
+ }
+
+ return false;
};
Misc.checkSocketables = function () {
- let items = me.getItemsEx()
- .filter(item => item.sockets > 0 && AutoEquip.hasTier(item) && item.quality > sdk.items.quality.Superior)
- .sort((a, b) => NTIP.GetTier(b) - NTIP.GetTier(a));
-
- if (!items) return;
-
- for (let i = 0; i < items.length; i++) {
- let sockets = items[i].sockets;
-
- switch (items[i].quality) {
- case sdk.items.quality.Magic:
- case sdk.items.quality.Rare:
- case sdk.items.quality.Crafted:
- // no need to check anything else if already socketed
- if (items[i].getItemsEx().length === sockets) {
- continue;
- }
- // Any magic, rare, or crafted item with open sockets
- if (items[i].isEquipped && [sdk.body.Head, sdk.body.Armor, sdk.body.RightArm, sdk.body.LeftArm].includes(items[i].bodylocation)) {
- Misc.getSocketables(items[i]);
- }
-
- break;
- case sdk.items.quality.Set:
- case sdk.items.quality.Unique:
- {
- let curr = Config.socketables.find(({ classid }) => items[i].classid === classid);
-
- // item is already socketed and we don't use temp socketables on this item
- if ((!curr || (curr && !curr.temp)) && items[i].getItemsEx().length === sockets) {
- continue;
- }
-
- if (curr && curr.condition(items[i])) {
- Misc.getSocketables(items[i], curr);
- } else if (items[i].isEquipped) {
- Misc.getSocketables(items[i]);
- }
- }
-
- break;
- default:
- break;
- }
- }
-};
-
-// Log kept item stats in the manager.
-Misc.logItem = function (action, unit, keptLine, force) {
- if (!this.useItemLog || unit === undefined || !unit || !unit.fname) 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));
- }
-
- const mercCheck = action.match("Merc");
- const hasTier = AutoEquip.hasTier(unit);
- const charmCheck = (unit.isCharm && Item.autoEquipCharmCheck(unit));
- const nTResult = NTIP.CheckItem(unit, NTIP_CheckListNoTier) === 1;
-
- if (!action.match("kept", "i") && !action.match("Shopped") && hasTier) {
- if (!mercCheck) {
- NTIP.GetCharmTier(unit) > 0 && (desc += ("\n\\xffc0Autoequip charm tier: " + NTIP.GetCharmTier(unit)));
- NTIP.GetTier(unit) > 0 && (desc += ("\n\\xffc0Autoequip char tier: " + NTIP.GetTier(unit)));
- } else {
- desc += ("\n\\xffc0Autoequip merc tier: " + NTIP.GetMercTier(unit));
- }
- }
-
- // should stop logging items unless we wish to see them or it's part of normal pickit
- if (!nTResult && !force) {
- switch (true) {
- case (unit.questItem || unit.isBaseType):
- case (!unit.isCharm && hasTier && !Developer.debugging.autoEquip):
- case (charmCheck && !Developer.debugging.smallCharm && unit.classid === sdk.items.SmallCharm):
- case (charmCheck && !Developer.debugging.largeCharm && unit.classid === sdk.items.LargeCharm):
- case (charmCheck && !Developer.debugging.grandCharm && unit.classid === sdk.items.GrandCharm):
- return true;
- default:
- break;
- }
- }
-
- 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.getFlag(sdk.items.flags.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;
-};
-
-Misc.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: " + Pather.getAreaName(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 += ", ";
- }
- }
- }
- }
- }
-
- if (stackLog) {
- filemsg += "Stack: " + stackLog + "\n";
- }
- }
-
- if (this.errorConsolePrint) {
- D2Bot.printToConsole(oogmsg, sdk.colors.D2Bot.Gray);
- }
-
- showConsole();
- console.log(msg);
- this.fileAction("logs/ScriptErrorLog.txt", 2, filemsg);
- takeScreenshot();
- delay(500);
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item.sockets > 0 && AutoEquip.hasTier(item)
+ && (item.quality >= sdk.items.quality.Magic
+ || ((item.normal || item.superior) && item.isEquipped));
+ })
+ .sort(function (a, b) {
+ return NTIP.GetTier(b) - NTIP.GetTier(a);
+ });
+
+ if (!items) return;
+
+ for (let item of items) {
+ let sockets = item.sockets;
+
+ switch (item.quality) {
+ case sdk.items.quality.Normal:
+ case sdk.items.quality.Superior:
+ case sdk.items.quality.Magic:
+ case sdk.items.quality.Rare:
+ case sdk.items.quality.Crafted:
+ // no need to check anything else if already socketed
+ if (item.getItemsEx().length === sockets) {
+ continue;
+ }
+ // Any magic, rare, or crafted item with open sockets
+ if (item.isEquipped && [sdk.body.Head, sdk.body.Armor, sdk.body.RightArm, sdk.body.LeftArm].includes(item.bodylocation)) {
+ Misc.getSocketables(item);
+ }
+
+ break;
+ case sdk.items.quality.Set:
+ case sdk.items.quality.Unique:
+ {
+ let curr = Config.socketables.find(({ classid }) => item.classid === classid);
+
+ // item is already socketed and we don't use temp socketables on this item
+ if ((!curr || (curr && !curr.temp)) && item.getItemsEx().length === sockets) {
+ continue;
+ }
+
+ if (curr && curr.condition(item)) {
+ Misc.getSocketables(item, curr);
+ } else if (item.isEquipped) {
+ Misc.getSocketables(item);
+ }
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
};
Misc.updateRecursively = function (oldObj, newObj, path) {
- if (path === void 0) { path = []; }
- Object.keys(newObj).forEach(function (key) {
- if (typeof newObj[key] === "function") return; // skip
- if (typeof newObj[key] !== "object") {
- if (!oldObj.hasOwnProperty(key) || oldObj[key] !== newObj[key]) {
- oldObj[key] = newObj[key];
- }
- } else if (Array.isArray(newObj[key]) && !newObj[key].some(k => typeof k === "object")) {
- // copy array (shallow copy)
- if (oldObj[key] === undefined || !oldObj[key].equals(newObj[key])) {
- oldObj[key] = newObj[key].slice(0);
- }
- } else {
- if (typeof oldObj[key] !== "object") {
- oldObj[key] = {};
- }
- path.push(key);
- Misc.updateRecursively(oldObj[key], newObj[key], path);
- }
- });
+ if (path === void 0) { path = []; }
+ Object.keys(newObj).forEach(function (key) {
+ if (typeof newObj[key] === "function") return; // skip
+ if (typeof newObj[key] !== "object") {
+ if (!oldObj.hasOwnProperty(key) || oldObj[key] !== newObj[key]) {
+ oldObj[key] = newObj[key];
+ }
+ } else if (Array.isArray(newObj[key]) && !newObj[key].some(k => typeof k === "object")) {
+ // copy array (shallow copy)
+ if (oldObj[key] === undefined || !oldObj[key].equals(newObj[key])) {
+ oldObj[key] = newObj[key].slice(0);
+ }
+ } else {
+ if (typeof oldObj[key] !== "object") {
+ oldObj[key] = {};
+ }
+ path.push(key);
+ Misc.updateRecursively(oldObj[key], newObj[key], path);
+ }
+ });
};
Misc.recursiveSearch = function (o, n, changed) {
- if (changed === void 0) { changed = {}; }
- Object.keys(n).forEach(function (key) {
- if (typeof n[key] === "function") return; // skip
- if (typeof n[key] !== "object") {
- if (!o.hasOwnProperty(key) || o[key] !== n[key]) {
- changed[key] = n[key];
- }
- } else {
- if (typeof changed[key] !== "object" || !changed[key]) {
- changed[key] = {};
- }
- Misc.recursiveSearch((o === null || o === void 0 ? void 0 : o[key]) || {}, (n === null || n === void 0 ? void 0 : n[key]) || {}, changed[key]);
- if (!Object.keys(changed[key]).length) {
- delete changed[key];
- }
- }
- });
- return changed;
+ if (changed === void 0) { changed = {}; }
+ Object.keys(n).forEach(function (key) {
+ if (typeof n[key] === "function") return; // skip
+ if (typeof n[key] !== "object") {
+ if (!o.hasOwnProperty(key) || o[key] !== n[key]) {
+ changed[key] = n[key];
+ }
+ } else {
+ if (typeof changed[key] !== "object" || !changed[key]) {
+ changed[key] = {};
+ }
+ Misc.recursiveSearch((o === null || o === void 0 ? void 0 : o[key]) || {}, (n === null || n === void 0 ? void 0 : n[key]) || {}, changed[key]);
+ if (!Object.keys(changed[key]).length) {
+ delete changed[key];
+ }
+ }
+ });
+ return changed;
};
diff --git a/libs/SoloPlay/Functions/MuleloggerOverrides.js b/libs/SoloPlay/Functions/MuleloggerOverrides.js
index 1a91ce53..a98ceaed 100644
--- a/libs/SoloPlay/Functions/MuleloggerOverrides.js
+++ b/libs/SoloPlay/Functions/MuleloggerOverrides.js
@@ -5,134 +5,146 @@
*
*/
-includeIfNotIncluded("MuleLogger.js");
+includeIfNotIncluded("systems/mulelogger/MuleLogger.js");
includeIfNotIncluded("SoloPlay/Functions/NTIPOverrides.js");
includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
// Added type parameter and logging tier value under picture on char viewer tab
MuleLogger.logItem = function (unit, logIlvl, type = "Player") {
- if (!isIncluded("common/misc.js")) {
- include("common/misc.js");
- include("common/util.js");
- }
-
- logIlvl === undefined && (logIlvl = this.LogItemLevel);
-
- 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) || "");
- let color = (unit.getColor() || -1);
- let code = Misc.getItemCode(unit);
-
- if (NTIP.GetMercTier(unit) > 0 || NTIP.GetTier(unit) > 0 || NTIP.GetCharmTier(unit) > 0 || NTIP.GetSecondaryTier(unit) > 0) {
- if (unit.mode === sdk.items.mode.inStorage && type === "Player") {
- if (unit.isCharm) {
- desc += ("\n\\xffc0Autoequip charm tier: " + NTIP.GetCharmTier(unit));
- } else {
- desc += ("\n\\xffc0Autoequip tier: " + NTIP.GetTier(unit));
-
- if (NTIP.GetSecondaryTier(unit) > 0) {
- desc += ("\n\\xffc0Autoequip Secondary tier: " + NTIP.GetSecondaryTier(unit));
- }
- }
- } else if (unit.mode === sdk.items.mode.inStorage && type === "Merc") {
- desc += ("\n\\xffc0Autoequip merctier: " + NTIP.GetMercTier(unit));
- }
- }
-
- let sock = unit.getItems();
-
- if (sock) {
- 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]);
- }
- }
- }
-
- desc += "$" + unit.gid + ":" + unit.classid + ":" + unit.location + ":" + unit.x + ":" + unit.y + (unit.getFlag(sdk.items.flags.Ethereal) ? ":eth" : "");
-
- return {
- itemColor: color,
- image: code,
- title: name,
- description: desc,
- header: header,
- sockets: Misc.getItemSockets(unit)
- };
+ if (!isIncluded("core/misc.js")) {
+ include("core/misc.js");
+ include("core/util.js");
+ }
+
+ logIlvl === undefined && (logIlvl = this.LogItemLevel);
+
+ let header = "";
+ let name = unit.itemType + "_" + unit.fname.split("\n").reverse().join(" ").replace(/(y|ÿ)c[0-9!"+<:;.*]|\/|\\/g, "").trim();
+ let desc = (Item.getItemDesc(unit, logIlvl) || "");
+ let color = (unit.getColor() || -1);
+ let code = Item.getItemCode(unit);
+
+ if (AutoEquip.hasTier(unit)) {
+ if (unit.mode === sdk.items.mode.inStorage && type === "Player") {
+ if (unit.isCharm) {
+ desc += ("\n\\xffc0Autoequip charm tier: " + NTIP.GetCharmTier(unit));
+ } else {
+ desc += ("\n\\xffc0Autoequip tier: " + NTIP.GetTier(unit));
+
+ if (NTIP.GetSecondaryTier(unit) > 0) {
+ desc += ("\n\\xffc0Autoequip Secondary tier: " + NTIP.GetSecondaryTier(unit));
+ }
+ }
+ } else if (unit.mode === sdk.items.mode.inStorage && type === "Merc") {
+ desc += ("\n\\xffc0Autoequip merctier: " + NTIP.GetMercTier(unit));
+ }
+ }
+
+ let sock = unit.getItems();
+
+ if (sock) {
+ 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]);
+ }
+ }
+ }
+
+ desc += "$" + unit.gid + ":" + unit.classid + ":" + unit.location + ":" + unit.x + ":" + unit.y + (unit.getFlag(sdk.items.flags.Ethereal) ? ":eth" : "");
+
+ return {
+ itemColor: color,
+ image: code,
+ title: name,
+ description: desc,
+ header: header,
+ sockets: Item.getItemSockets(unit)
+ };
};
MuleLogger.logEquippedItems = function () {
- while (!me.gameReady) {
- delay(100);
- }
-
- let folder, string, parsedItem;
- let realm = me.realm || "Single Player";
- let finalString = "";
- let items = me.getItemsEx().filter(item => item.isEquipped || item.isEquippedCharm || (item.isInStorage && item.itemType === sdk.items.type.Rune));
- if (!items || !items.length) return;
- items.sort((a, b) => b.itemType - a.itemType);
-
- if (!FileTools.exists("mules/" + realm)) {
- folder = dopen("mules");
- folder.create(realm);
- }
-
- if (!FileTools.exists("mules/" + realm + "/" + "Kolbot-SoloPlay")) {
- folder = dopen("mules/" + realm);
- folder.create("Kolbot-SoloPlay");
- }
-
- if (!FileTools.exists("mules/" + realm + "/" + "Kolbot-SoloPlay/" + me.account)) {
- folder = dopen("mules/" + realm + "/Kolbot-SoloPlay");
- folder.create(me.account);
- }
-
- for (let i = 0; i < items.length; i += 1) {
- parsedItem = this.logItem(items[i], true, "Player");
- // 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);
-
- switch (items[i].mode) {
- case sdk.items.mode.inStorage:
- parsedItem.title += ((items[i].isInInventory && items[i].isEquippedCharm) ? " (equipped charm)" : " (in stash)");
-
- break;
- case sdk.items.mode.Equipped:
- parsedItem.title += (items[i].isOnSwap ? " (secondary equipped)" : " (equipped)");
-
- break;
- }
-
- string = JSON.stringify(parsedItem);
- finalString += (string + "\n");
- }
-
- if (Config.UseMerc) {
- let merc = me.getMercEx();
-
- if (merc) {
- items = merc.getItemsEx();
-
- for (let i = 0; i < items.length; i += 1) {
- parsedItem = this.logItem(items[i], true, "Merc");
- parsedItem.title += " (merc)";
-
- string = JSON.stringify(parsedItem);
- finalString += (string + "\n");
- }
- }
-
- }
-
- let charClass = ["amazon-", "sorceress-", "necromancer-", "paladin-", "barbarian-", "druid-", "assassin-"][me.classid];
-
- // hccl = hardcore classic ladder
- // scnl = softcore expan nonladder
- FileTools.writeText("mules/" + realm + "/" + "Kolbot-SoloPlay/" + me.account + "/" + charClass + "-" + me.profile + "-" + me.name + "." + ( me.playertype ? "hc" : "sc" ) + (me.classic ? "c" : "" ) + ( me.ladder > 0 ? "l" : "nl" ) + ".txt", finalString);
- console.log("Item logging done.");
+ while (!me.gameReady) {
+ delay(100);
+ }
+
+ let folder, string, parsedItem;
+ let realm = me.realm || "Single Player";
+ let finalString = "";
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item.isEquipped || item.isEquippedCharm || (item.isInStorage && item.itemType === sdk.items.type.Rune);
+ })
+ .sort(function (a, b) {
+ return b.itemType - a.itemType;
+ });
+ if (!items || !items.length) return;
+
+ if (!FileTools.exists("mules/" + realm)) {
+ folder = dopen("mules");
+ folder.create(realm);
+ }
+
+ if (!FileTools.exists("mules/" + realm + "/" + "Kolbot-SoloPlay")) {
+ folder = dopen("mules/" + realm);
+ folder.create("Kolbot-SoloPlay");
+ }
+
+ if (!FileTools.exists("mules/" + realm + "/" + "Kolbot-SoloPlay/" + me.account)) {
+ folder = dopen("mules/" + realm + "/Kolbot-SoloPlay");
+ folder.create(me.account);
+ }
+
+ for (let item of items) {
+ parsedItem = this.logItem(item, true, "Player");
+ // 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);
+
+ switch (item.mode) {
+ case sdk.items.mode.inStorage:
+ parsedItem.title += ((item.isInInventory && item.isEquippedCharm) ? " (equipped charm)" : " (in stash)");
+
+ break;
+ case sdk.items.mode.Equipped:
+ parsedItem.title += (item.isOnSwap ? " (secondary equipped)" : " (equipped)");
+
+ break;
+ }
+
+ string = JSON.stringify(parsedItem);
+ finalString += (string + "\n");
+ }
+
+ if (Config.UseMerc) {
+ let merc = me.getMercEx();
+
+ if (merc) {
+ items = merc.getItemsEx();
+
+ for (let item of items) {
+ parsedItem = this.logItem(item, true, "Merc");
+ parsedItem.title += " (merc)";
+
+ string = JSON.stringify(parsedItem);
+ finalString += (string + "\n");
+ }
+ }
+
+ }
+
+ let charClass = ["amazon-", "sorceress-", "necromancer-", "paladin-", "barbarian-", "druid-", "assassin-"][me.classid];
+
+ // hccl = hardcore classic ladder
+ // scnl = softcore expan nonladder
+ FileTools.writeText(
+ "mules/" + realm
+ + "/" + "Kolbot-SoloPlay/"
+ + me.account + "/" + charClass + "-" + me.profile + "-" + me.name
+ + "." + ( me.playertype ? "hc" : "sc" ) + (me.classic ? "c" : "" ) + ( me.ladder > 0 ? "l" : "nl" )
+ + ".txt",
+ finalString
+ );
+ console.log("Item logging done.");
};
diff --git a/libs/SoloPlay/Functions/NPCAction.js b/libs/SoloPlay/Functions/NPCAction.js
new file mode 100644
index 00000000..49edb966
--- /dev/null
+++ b/libs/SoloPlay/Functions/NPCAction.js
@@ -0,0 +1,835 @@
+/**
+* @filename NPCAction.js
+* @author theBGuy
+* @desc NPC related functions
+*
+*/
+
+// ugly but should handle scope issues if I decide to add this to the core in which case I can come back and remove this
+// but won't get immeadiate issues of trying to redefine a const
+(function (NPCAction) {
+ const PotData = require("../Modules/GameData/PotData");
+ /**
+ * Easier shopping done at a specific npc
+ * @param {string} npcName - NPC.NameOfNPC
+ * @returns {boolean}
+ */
+ NPCAction.shopAt = function (npcName) {
+ if (!me.inTown) return false;
+ // keep track of where we start from
+ const origAct = me.act;
+ const npcAct = NPC.getAct(npcName);
+ if (!npcAct.length) return false;
+
+ try {
+ // check for invaid npcs to shop at
+ if ([
+ NPC.Kashya, NPC.Warriv,
+ NPC.Meshif, NPC.Atma,
+ NPC.Greiz, NPC.Tyrael,
+ NPC.Qual_Kehk, NPC.Cain
+ ].includes(npcName.toLowerCase())) {
+ throw new Error(npcName + " is an invalid npc to shop at");
+ }
+
+ if (!npcAct.includes(origAct)) {
+ Town.goToTown(npcAct[0]);
+ }
+
+ Town.move(npcName);
+ let npc = Game.getNPC(npcName);
+
+ if (!npc && Town.move(npcName)) {
+ npc = Game.getNPC(npcName);
+ }
+
+ if (!getUIFlag(sdk.uiflags.Shop) && !npc.startTrade("Shop")) {
+ throw new Error("Failed to shop at " + npc.name);
+ }
+
+ return Town.shopItems();
+ } catch (e) {
+ console.error(e);
+
+ return false;
+ } finally {
+ me.act !== origAct && Town.goToTown(origAct);
+ }
+ };
+
+ NPCAction.buyPotions = function () {
+ if (me.gold < 450 || !me.getItem(sdk.items.TomeofTownPortal)) return false;
+
+ me.clearBelt();
+ const buffer = { hp: 0, mp: 0 };
+ const beltSize = Storage.BeltSize();
+ let [needPots, needBuffer, specialCheck] = [false, true, false];
+ let col = Storage.Belt.checkColumns(beltSize);
+
+ const getNeededBuffer = function () {
+ [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(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;
+ }
+ }
+
+ // 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
+ let [wantedHpPot, wantedMpPot] = [5, 5];
+ // only do this if we are low on gold in the first place
+ if (me.normal && me.gold < Config.LowGold) {
+ const mpPotsEffects = PotData.getMpPots().map(function (el) {
+ return el.effect[me.classid];
+ });
+ const hpPotsEffects = PotData.getHpPots().map(function (el) {
+ return el.effect[me.classid];
+ });
+
+ wantedHpPot = (hpPotsEffects.findIndex(eff => me.hpmax / 2 < eff) + 1 || hpPotsEffects.length - 1);
+ wantedMpPot = (mpPotsEffects.findIndex(eff => me.mpmax / 2 < eff) + 1 || mpPotsEffects.length - 1);
+ console.debug("Wanted hpPot: " + wantedHpPot + " Wanted mpPot: " + wantedMpPot);
+ }
+
+ if (me.normal) {
+ if (me.highestAct >= 4) {
+ let pAct = Math.max(wantedHpPot, wantedMpPot);
+ pAct >= 4 ? me.act < 4 && Town.goToTown(4) : pAct > me.act && Town.goToTown(pAct);
+ }
+ } else if (!me.normal && me.act === 3
+ && Town.getDistance(Town.tasks.get(me.act).Shop) > 10) {
+ // if we need to repair items as well or stack pots we should go ahead and change act
+ // unless we are already at our intended npc
+ let _needRepair = me.needRepair().length > 0;
+ let _needStack = CharData.pots.get("thawing").need() || CharData.pots.get("antidote").need();
+ let _needMerc = me.needMerc();
+ if (_needRepair || _needStack || _needMerc) {
+ Town.goToTown(me.highestAct >= 4 ? 4 : 1);
+ }
+ }
+
+ console.debug(
+ "Buying potions, needPots: " + needPots
+ + " needBuffer: " + needBuffer
+ + " specialCheck: " + specialCheck
+ );
+ 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);
+ const wantedPot = Config.BeltColumn[i] === "hp" ? wantedHpPot : wantedMpPot;
+ let pot = Town.getPotion(npc, Config.BeltColumn[i], wantedPot);
+
+ 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 = Storage.Belt.checkColumns(beltSize); // Re-initialize columns (needed because 1 shift-buy can fill multiple columns)
+ }
+
+ // re-check
+ !needBuffer && (Config.HPBuffer > 0 || Config.MPBuffer > 0) && getNeededBuffer();
+
+ const buyHPBuffers = function () {
+ if (needBuffer && buffer.hp < Config.HPBuffer) {
+ for (let i = 0; i < Config.HPBuffer - buffer.hp; i += 1) {
+ let pot = Town.getPotion(npc, "hp", wantedHpPot);
+ !!pot && Storage.Inventory.CanFit(pot) && pot.buy(false);
+ }
+ }
+ return true;
+ };
+ const buyMPBuffers = function () {
+ if (needBuffer && buffer.mp < Config.MPBuffer) {
+ for (let i = 0; i < Config.MPBuffer - buffer.mp; i += 1) {
+ let pot = Town.getPotion(npc, "mp", wantedMpPot);
+ !!pot && Storage.Inventory.CanFit(pot) && pot.buy(false);
+ }
+ }
+ return true;
+ };
+ // priortize mana pots if caster
+ Check.currentBuild().caster
+ ? buyMPBuffers() && buyHPBuffers()
+ : buyHPBuffers() && buyMPBuffers();
+
+ // keep cold/pois res high with potions
+ if (me.gold > 50000 && npc.getItem(sdk.items.ThawingPotion)) {
+ CharData.pots.get("thawing").need() && Town.buyPots(12, "thawing", true);
+ CharData.pots.get("antidote").need() && Town.buyPots(12, "antidote", true);
+ }
+
+ return true;
+ };
+
+ /**
+ * @param {number} classid
+ * @param {boolean} force
+ * @returns {boolean}
+ */
+ NPCAction.fillTome = function (classid, force = false) {
+ const scrollId = (classid === sdk.items.TomeofTownPortal
+ ? sdk.items.ScrollofTownPortal
+ : sdk.items.ScrollofIdentify);
+ const have = Town.checkScrolls(classid, force);
+ let myTome = me.getTome(classid);
+ let invoScrolls = 0;
+
+ if (have > 0 && have < 20) {
+ // lets see if we have scrolls to cleanup
+ invoScrolls = me.cleanUpScrolls(myTome, scrollId);
+ }
+
+ if (have + invoScrolls >= (me.charlvl < 12 ? 5 : 13)) return true;
+ if (me.gold < 450) return false;
+
+ if (me.act === 3
+ && Town.getDistance(Town.tasks.get(me.act).Shop) > 10) {
+ // if we need to repair items as well or stack pots we should go ahead and change act
+ // unless we are already at our intended npc
+ let _needRepair = me.needRepair().length > 0;
+ let _needStack = CharData.pots.get("thawing").need() || CharData.pots.get("antidote").need();
+ let _needMerc = me.needMerc();
+ if (_needRepair || _needStack || _needMerc) {
+ if (!me.normal || me.accessToAct(4) || !me.needPotions()) {
+ Town.goToTown(me.highestAct >= 4 ? 4 : 1);
+ }
+ }
+ }
+
+ let npc = Town.initNPC("Shop", "fillTome");
+ if (!npc) return false;
+
+ delay(500);
+
+ if (!myTome) {
+ let tome = npc.getItem(classid);
+
+ try {
+ if (tome) {
+ Storage.Inventory.CanFit(tome) && tome.buy();
+ } else {
+ // couldn't buy tome, lets see if we can just buy a single scroll
+ if (me.getItem(scrollId)) return true;
+ let scroll = npc.getItem(scrollId);
+ if (!scroll || !Storage.Inventory.CanFit(scroll)) return false;
+ scroll.buy();
+
+ return true;
+ }
+ } catch (e) {
+ console.error(e);
+
+ return false;
+ }
+ }
+
+ /** @type {ItemUnit} */
+ let scroll = npc.getItem(scrollId);
+ if (!scroll) return false;
+ if (!myTome && !(myTome = me.getTome(classid))) return false;
+
+ // place scrolls in tome if we have any now that we know we have a tome (possibly just bought one)
+ me.cleanUpScrolls(myTome, scrollId);
+
+ try {
+ if (me.gold < 5000) {
+ myTome = me.getTome(classid);
+
+ if (myTome) {
+ while (myTome.getStat(sdk.stats.Quantity) < 5 && me.gold > 500) {
+ scroll = npc.getItem(scrollId);
+ scroll && Packet.buyScroll(scroll, myTome, false);
+ delay(50);
+ }
+ }
+ } else {
+ scroll.buy(true);
+
+ if (scrollId !== sdk.items.ScrollofIdentify && me.gold > 10000) {
+ // we are already in the shop, lets check if we need id scrolls too
+ let idTome = me.getTome(sdk.items.TomeofIdentify);
+ if (idTome && idTome.getStat(sdk.stats.Quantity) < 20) {
+ scroll = npc.getItem(sdk.items.ScrollofIdentify);
+ if (scroll) {
+ scroll.buy(true);
+ }
+ }
+ }
+ }
+ } catch (e2) {
+ console.error(e2);
+
+ return false;
+ }
+
+ return true;
+ };
+
+ NPCAction.cainID = function (force = false) {
+ if ((!Config.CainID.Enable && !force)
+ || !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed)) {
+ return false;
+ }
+
+ let npc = getInteractedNPC();
+
+ // Check if we're already in a shop. It would be pointless to go to Cain if so.
+ if (npc && npc.name.toLowerCase() === Town.tasks.get(me.act).Shop) return false;
+ // Check if we may use Cain - minimum gold
+ if (me.gold < Config.CainID.MinGold && !force) return false;
+
+ me.cancel();
+
+ let unids = me.getUnids();
+ if (!unids.length) return true;
+
+ // Check if we may use Cain - number of unid items
+ if (unids.length < Config.CainID.MinUnids && !force) return false;
+
+ let cain = Town.initNPC("CainID", "cainID");
+ if (!cain) return false;
+
+ me.cancelUIFlags();
+
+ while (unids.length) {
+ const item = unids.shift();
+ const { result, line } = Pickit.checkItem(item);
+
+ switch (result) {
+ case Pickit.Result.TRASH:
+ Town.sell.push(item);
+
+ break;
+ case Pickit.Result.WANTED:
+ case Pickit.Result.SOLOWANTS:
+ Item.logger("Kept", item);
+ Item.logItem("Kept", item, line);
+
+ break;
+ case Pickit.Result.CUBING:
+ Item.logger("Kept", item, "Cubing-Town");
+ Cubing.update();
+
+ break;
+ case Pickit.Result.RUNEWORD:
+ Item.logger("Kept", item, "Runewords-Town");
+ Runewords.update(item.classid, item.gid);
+
+ break;
+ case Pickit.Result.CRAFTING:
+ Item.logger("Kept", item, "CraftSys-Town");
+ CraftingSystem.update(item);
+
+ break;
+ case Pickit.Result.SOLOSYSTEM:
+ Item.logger("Kept", item, "SoloWants-Town");
+ SoloWants.update(item);
+
+ break;
+ case Pickit.Result.UNID:
+ default:
+ break;
+ }
+ }
+
+ return true;
+ };
+
+ // todo - allow earlier shopping, mainly to get a belt
+ NPCAction.shopItems = function (force = false) {
+ if (!Config.MiniShopBot) return true;
+ if (!me.getTome(sdk.items.TomeofTownPortal)) return false;
+
+ // todo - better gold scaling
+ // let goldLimit = [10000, 20000, 30000][me.diff];
+ const startingGold = me.gold;
+ let goldLimit = Math.floor(startingGold * 0.60);
+ let itemTypes = [];
+ let lowLevelShop = false;
+
+ if (me.charlvl < 6 && startingGold > 200) {
+ Storage.BeltSize() === 1 && itemTypes.push(sdk.items.type.Belt);
+ !CharData.skillData.bow.onSwitch && itemTypes.push(sdk.items.type.Bow, sdk.items.type.Crossbow);
+ if (!itemTypes.length) return true;
+ lowLevelShop = true;
+ }
+
+ let npc = getInteractedNPC();
+ if (!npc || !npc.itemcount) {
+ // for now we only do force shop on low level
+ if ((force || lowLevelShop) && itemTypes.length) {
+ console.debug("Attempt force shopping");
+ Town.initNPC("Repair", "shopItems");
+ npc = getInteractedNPC();
+ if (!npc || !npc.itemcount) return false;
+ } else {
+ return false;
+ }
+ }
+
+ if (getTickCount() - Town.lastShopped.tick < Time.seconds(3)
+ && Town.lastShopped.who === npc.name) {
+ return false;
+ }
+ let items = npc.getItemsEx()
+ .filter((item) => !Town.ignoreType(item.itemType)
+ && (itemTypes.length === 0 || itemTypes.includes(item.itemType))
+ && (NTIP.CheckItem(item) !== Pickit.Result.UNWANTED)
+ && (startingGold - item.getItemCost(sdk.items.cost.ToBuy) > goldLimit))
+ .sort(function (a, b) {
+ let priorityA = itemTypes.includes(a.itemType);
+ let priorityB = itemTypes.includes(b.itemType);
+ if (priorityA && priorityB) return NTIP.GetTier(b) - NTIP.GetTier(a);
+ if (priorityA) return 1;
+ if (priorityB) return -1;
+ return NTIP.GetTier(b) - NTIP.GetTier(a);
+ });
+ if (!items.length) return false;
+
+ const checkedItems = items.length;
+
+ console.time("shopItems");
+
+ let bought = 0;
+ const haveMerc = !!me.getMercEx();
+ console.info(true, "ÿc4MiniShopBotÿc0: Scanning " + npc.itemcount + " items.");
+
+ /**
+ * @param {ItemUnit} item
+ * @param {string} action
+ * @param {{ result: PickitResult, line: string }} result
+ * @param {number | string} tierInfo
+ */
+ const shopReport = function (item, action, result, tierInfo) {
+ action === undefined && (action = "");
+ tierInfo === undefined && (tierInfo = "");
+ console.log("ÿc8Kolbot-SoloPlayÿc0: " + action + (tierInfo ? " " + tierInfo : ""));
+ Item.logger(action, item);
+ if (Developer.debugging.autoEquip) {
+ Item.logItem("Shopped " + action, item, result.line !== undefined ? result.line : "null");
+ }
+ };
+
+ /**
+ * Buy dependancy item if it's needed
+ * @param {ItemUnit} item
+ */
+ const checkDependancy = function (item) {
+ let check = Item.hasDependancy(item);
+ if (check) {
+ let el = npc.getItem(check);
+ !!el && el.buy();
+ }
+ };
+
+ for (let item of items) {
+ const myGold = me.gold;
+ const itemCost = item.getItemCost(sdk.items.cost.ToBuy);
+ if (myGold < itemCost) continue;
+ const { result, line } = Pickit.checkItem(item);
+
+ // no tier'ed items
+ if (!lowLevelShop && result === Pickit.Result.SOLOWANTS
+ && NTIP.CheckItem(item, NTIP.NoTier, true).result !== Pickit.Result.UNWANTED) {
+ try {
+ if (Storage.Inventory.CanFit(item) && myGold >= itemCost && (myGold - itemCost > goldLimit)) {
+ if (item.isBaseType) {
+ if (Item.betterThanStashed(item) && Item.betterBaseThanWearing(item, Developer.debugging.baseCheck)) {
+ shopReport(item, "better base", line);
+ item.buy() && bought++;
+ }
+ } else {
+ shopReport(item, "NoTier", line);
+ item.buy() && bought++;
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ } else if (result === Pickit.Result.SOLOWANTS && AutoEquip.wanted(item)) {
+ // tier'ed items
+ try {
+ if (Storage.Inventory.CanFit(item) && myGold >= itemCost && (myGold - itemCost > goldLimit)) {
+ let [mainTier] = [NTIP.GetTier(item)];
+
+ // we want this to be at least a 5% increase in the tier value
+ if (Item.hasTier(item)
+ && Item.autoEquipCheck(item)
+ && ((Item.getEquippedItem(item.bodyLocation().first()).tier - mainTier) / (mainTier * 100)) > 5) {
+ shopReport(item, "AutoEquip", line, (item.prettyPrint + " Tier: " + NTIP.GetTier(item)));
+ item.buy() && bought++;
+ Item.autoEquip("InShop");
+ checkDependancy(item);
+ } else if (Item.hasSecondaryTier(item) && Item.autoEquipCheckSecondary(item)) {
+ shopReport(item, "AutoEquip Switch Shopped", line, (item.prettyPrint + " SecondaryTier: " + NTIP.GetSecondaryTier(item)));
+ item.buy() && bought++;
+ Item.autoEquip("InShop");
+ checkDependancy(item);
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ delay(2);
+ }
+
+ // merc tier'ed items
+ if (haveMerc && !lowLevelShop) {
+ items = npc.getItemsEx()
+ .filter(function (item) {
+ return !Town.ignoreType(item.itemType) && NTIP.GetMercTier(item) > 0;
+ })
+ .sort(function (a, b) {
+ return NTIP.GetMercTier(b) - NTIP.GetMercTier(a);
+ })
+ .forEach(function (item) {
+ const myGold = me.gold;
+ const itemCost = item.getItemCost(sdk.items.cost.ToBuy);
+ if (myGold < itemCost) return;
+ const result = Pickit.checkItem(item);
+
+ if (result.result === Pickit.Result.SOLOWANTS && AutoEquip.wanted(item)) {
+ try {
+ if (Storage.Inventory.CanFit(item) && myGold >= itemCost && (myGold - itemCost > goldLimit)) {
+ if (Item.hasMercTier(item) && Item.autoEquipCheckMerc(item)) {
+ shopReport(item, "AutoEquipMerc", result.line, (item.fname + " Tier: " + NTIP.GetMercTier(item)));
+ item.buy() && bought++;
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ delay(2);
+ });
+ }
+
+ Town.lastShopped.tick = getTickCount();
+ Town.lastShopped.who = npc.name;
+
+ console.info(false, "Evaluated " + checkedItems + " items. Bought " + bought + " items. Spent " + (startingGold - me.gold) + " gold", "shopItems");
+
+ return true;
+ };
+
+ NPCAction.gamble = function () {
+ if (!Town.needGamble() || Config.GambleItems.length === 0) return true;
+
+ let list = [];
+
+ 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
+ if (me.act === 3
+ || (Town.getDistance(Town.tasks.get(me.act).Gamble) > 25 && me.gold < Config.GambleGoldStart * 1.5)) {
+ // avoid changing towns as its time wasting
+ wantedTasks.add("gamble");
+ return true;
+ }
+ // me.act === 3 && Town.goToTown(me.accessToAct(4) ? 4 : 2);
+
+ let npc = Town.initNPC("Gamble", "gamble");
+ if (!npc) return false;
+
+ 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 {
+ Town.gambleIds.has(item.classid) && items.push(copyUnit(item));
+ } while (item.getNext());
+
+ for (let item of items) {
+ if (!Storage.Inventory.CanFit(item)) return false;
+
+ 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");
+ newItem.sell();
+
+ if (!Config.PacketShopping) {
+ delay(500);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ me.cancel();
+ }
+
+ return true;
+ };
+
+ NPCAction.repair = function (force = false) {
+ if (Town.cubeRepair()) return true;
+
+ let npc;
+ let repairAction = me.needRepair();
+ force && repairAction.indexOf("repair") === -1 && repairAction.push("repair");
+ if (!repairAction || !repairAction.length) return false;
+
+ 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 = me.getItemsEx()
+ .filter(function (el) {
+ return el.isEquipped
+ && [sdk.items.type.Bow, sdk.items.type.Crossbow, sdk.items.type.AmazonBow].includes(el.itemType);
+ })
+ .first();
+
+ if (bowCheck) {
+ const quiverType = bowCheck.itemType === sdk.items.type.Crossbow
+ ? sdk.items.Bolts : sdk.items.Arrows;
+ const onSwitch = bowCheck.isOnSwap;
+ try {
+ onSwitch && me.switchWeapons(sdk.player.slot.Secondary);
+ npc = Town.initNPC("Repair", "buyQuiver");
+ if (!npc) return false;
+
+ let myQuiver = me.getItem(quiverType, sdk.items.mode.Equipped);
+ !!myQuiver && myQuiver.sell();
+
+ let quiver = npc.getItem(quiverType);
+ !!quiver && quiver.buy();
+ } finally {
+ onSwitch && me.switchWeapons(sdk.player.slot.Main);
+ }
+ }
+
+ break;
+ }
+ }
+
+ NPCAction.shopItems();
+
+ return true;
+ };
+
+ NPCAction.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();
+ if (!dialog) continue;
+
+ 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")) {
+ dialog.find(line => !line.text.match(getLocaleString(sdk.locale.dialog.youDoNotHaveEnoughGoldForThat), "gi")).handler();
+ delay(Math.max(750, me.ping * 2));
+ me.cancelUIFlags();
+ console.error("Could not revive merc: My current gold: " + me.gold + ", current mercrevivecost: " + me.mercrevivecost);
+ return false;
+ }
+ }
+
+ let merc = null;
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < 2000) {
+ if ((merc = me.getMercEx())) {
+ delay(Math.max(750, me.ping * 2));
+ // check stats and update if necessary
+ let _temp = copyObj(me.data.merc);
+ let mercInfo = Mercenary.getMercInfo(merc);
+ if (mercInfo.classid !== me.data.merc.classid) {
+ me.data.merc.classid = mercInfo.classid;
+ }
+ if (mercInfo.act !== me.data.merc.act) {
+ me.data.merc.act = mercInfo.act;
+ }
+ if (mercInfo.difficulty !== me.data.merc.difficulty) {
+ me.data.merc.difficulty = mercInfo.difficulty;
+ }
+ if (merc.charlvl !== me.data.merc.level) {
+ me.data.merc.level = merc.charlvl;
+ }
+ if (merc.rawStrength !== me.data.merc.strength) {
+ me.data.merc.strength = merc.rawStrength;
+ }
+ if (merc.rawDexterity !== me.data.merc.dexterity) {
+ me.data.merc.dexterity = merc.rawDexterity;
+ }
+
+ if (merc.classid !== sdk.mercs.Guard) {
+ try {
+ if (mercInfo.skillName !== me.data.merc.skillName) {
+ me.data.merc.skillName = mercInfo.skillName;
+ me.data.merc.skill = MercData.findByName(me.data.merc.skillName, me.data.merc.act).skill;
+ }
+ } catch (e) {
+ //
+ }
+ }
+ let changed = Misc.recursiveSearch(me.data.merc, _temp);
+
+ if (Object.keys(changed).length > 0) {
+ CharData.updateData("merc", me.data.merc);
+ }
+ me.cancel();
+
+ break MainLoop;
+ }
+
+ delay(200);
+ }
+ }
+
+ Attack.checkInfinity();
+
+ if (me.getMercEx()) {
+ // 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;
+ };
+
+})(global.NPCAction = global.NPCAction || {});
diff --git a/libs/SoloPlay/Functions/NTIPOverrides.js b/libs/SoloPlay/Functions/NTIPOverrides.js
index 7fd7dbea..d4c38bde 100644
--- a/libs/SoloPlay/Functions/NTIPOverrides.js
+++ b/libs/SoloPlay/Functions/NTIPOverrides.js
@@ -6,789 +6,783 @@
*
*/
-includeIfNotIncluded("NTItemParser.dbl");
+includeIfNotIncluded("core/NTItemParser.js");
includeIfNotIncluded("SoloPlay/Functions/PrototypeOverrides.js");
/**
* @todo
- * - need a way to build and update/remove elements from checklists during gameplay
+ * - need a way to build and update/remove elements from checklists during gameplay
+ * - Build parser, takes obj then coverts to normal nip string
*/
NTIPAliasStat["addfireskills"] = [sdk.stats.ElemSkill, 1];
NTIPAliasStat["plusskillwhirlwind"] = [sdk.stats.NonClassSkill, sdk.skills.Whirlwind];
NTIP.MAX_TIER = 100000;
+
+/** @constructor */
+function NTIPList () {
+ /** @type {Array>} */
+ this.list = [];
+ /** @type {Array<{ line: string, file: string, string: string }>} */
+ this.strArray = [];
+}
+
+NTIPList.prototype.add = function (parsedLine, info) {
+ this.list.push(parsedLine);
+ this.strArray.push(info);
+};
+
+NTIPList.prototype.remove = function (index) {
+ this.list.splice(index, 1);
+ this.strArray.splice(index, 1);
+};
+
+NTIPList.prototype.clear = function () {
+ this.list = [];
+ this.strArray = [];
+};
// think this might be ugly but want to work on seperating soloplay from the base so pickits don't interfere
-NTIP.RuntimeCheckList = [];
-NTIP.RuntimeStringArray = [];
-NTIP.SoloCheckList = [];
-NTIP.SoloCheckListNoTier = [];
-NTIP.SoloStringArray = [];
+NTIP.SoloList = new NTIPList();
+NTIP.Runtime = new NTIPList();
+NTIP.FinalGear = new NTIPList();
+NTIP.NoTier = new NTIPList();
+// handle regular pickits
+NTIP.CheckList = new NTIPList();
+// NTIP.CheckListNoTier = new NTIPList(); // all items in a normal pickit are treated as no tier
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.SoloCheckList.length; i += 1) {
- if (NTIP.SoloCheckList[i].length !== 3) {
- continue;
- }
-
- const [type, stat, wanted] = NTIP.SoloCheckList[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 = NTIP.SoloStringArray[i];
- Misc.errorReport("ÿc1Pickit Tier (" + tierType + ") error! Line # ÿc2" + info.line + " ÿc1Entry: ÿc0" + info.string + " (" + info.file + ") Error message: " + e.message + " Trigger item: " + item.fname.split("\n").reverse().join(" "));
- }
- }
- }
-
- return tier;
- };
+ return /** @param {ItemUnit} item */ function (item) {
+ let tier = -1;
+
+ const updateTier = function (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.SoloList.list.length; i++) {
+ if (NTIP.SoloList.list[i].length !== 3) {
+ continue;
+ }
+
+ const [type, stat, wanted] = NTIP.SoloList.list[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 = NTIP.SoloList.strArray[i];
+ Misc.errorReport(
+ "ÿc1Pickit Tier (" + tierType + ") error! Line # ÿc2"
+ + info.line + " ÿc1Entry: ÿc0" + info.string
+ + " (" + info.file + ") Error message: " + e.message
+ + " Trigger item: " + item.fname.split("\n").reverse().join(" ")
+ );
+ }
+ }
+ }
+
+ return tier;
+ };
};
-/**@function
- * @param item */
+/**
+ * @function
+ * @param {ItemUnit} item
+ */
NTIP.GetTier = NTIP.generateTierFunc("Tier");
-/**@function
- * @param item */
+/**
+ * @function
+ * @param {ItemUnit} item
+ */
NTIP.GetMercTier = NTIP.generateTierFunc("Merctier");
-NTIP.GetCharmTier = NTIP.generateTierFunc("Charmtier");
-NTIP.GetSecondaryTier = NTIP.generateTierFunc("Secondarytier");
-
-NTIP.addLine = function (itemString) {
- const info = {
- line: NTIP.SoloCheckList.length + 1,
- file: "Kolbot-SoloPlay",
- string: itemString
- };
- const line = NTIP.ParseLineInt(itemString, info);
+/**
+ * @function
+ * @param {ItemUnit} item
+ */
+NTIP.GetCharmTier = NTIP.generateTierFunc("Charmtier");
- if (line) {
- if (!itemString.toLowerCase().includes("tier")) {
- NTIP.SoloCheckListNoTier.push(line);
- } else {
- NTIP.SoloCheckListNoTier.push([false, false]);
- }
+/**
+ * @function
+ * @param {ItemUnit} item
+ */
+NTIP.GetSecondaryTier = NTIP.generateTierFunc("Secondarytier");
- NTIP.SoloCheckList.push(line);
- NTIP.SoloStringArray.push(info);
- }
+NTIP.addLine = function (itemString, filename = "Kolbot-SoloPlay") {
+ const tierdItem = itemString.toLowerCase().includes("tier");
+ const info = {
+ line: tierdItem
+ ? NTIP.SoloList.list.length + 1
+ : NTIP.NoTier.list.length + 1,
+ file: filename,
+ string: itemString
+ };
+
+ const line = NTIP.ParseLineInt(itemString, info);
+
+ if (line) {
+ tierdItem
+ ? NTIP.SoloList.add(line, info)
+ : NTIP.NoTier.add(line, info);
+ }
+
+ return true;
+};
- return true;
+/**
+ * @param {string[]} arr
+ * @returns {boolean}
+ */
+NTIP.buildFinalGear = function (arr) {
+ for (let i = 0; i < arr.length; i++) {
+ const info = {
+ line: NTIP.FinalGear.list.length + 1,
+ file: "Kolbot-SoloPlay",
+ string: arr[i]
+ };
+
+ /** @type {string} */
+ const line = NTIP.ParseLineInt(arr[i], info);
+
+ if (line) {
+ let lineCheck = arr[i].toLowerCase();
+
+ switch (true) {
+ case !lineCheck.includes("tier"):
+ case lineCheck.includes("merctier"):
+ case lineCheck.includes("secondarytier"):
+ case lineCheck.includes("charmtier"):
+ continue;
+ }
+
+ NTIP.FinalGear.add(line, info);
+ }
+ }
+
+ return true;
};
// currently just using for quiver's but if that changes need to figure out way to seperate out sections
// so things can be deleted without affecting the entire list
NTIP.addToRuntime = function (itemString) {
- const info = {
- line: NTIP.RuntimeCheckList.length + 1,
- file: "Kolbot-SoloPlay-Runtime",
- string: itemString
- };
+ const info = {
+ line: NTIP.Runtime.list.length + 1,
+ file: "Kolbot-SoloPlay-Runtime",
+ string: itemString
+ };
- const line = NTIP.ParseLineInt(itemString, info);
+ const line = NTIP.ParseLineInt(itemString, info);
- if (line) {
- NTIP.RuntimeCheckList.push(line);
- NTIP.RuntimeStringArray.push(info);
- }
+ if (line) {
+ NTIP.Runtime.add(line, info);
+ }
- return true;
+ return true;
};
-NTIP.resetRuntimeList = () => {
- NTIP.RuntimeCheckList.length = 0;
- NTIP.RuntimeStringArray.length = 0;
+NTIP.buildList = function (...arraystoloop) {
+ const filename = (new Error()).stack.match(/[^\r\n]+/g).at(1).split("\\").last() || "";
+ for (let arr of arraystoloop) {
+ if (Array.isArray(arr)) {
+ for (let str of arr) {
+ NTIP.addLine(str, filename);
+ }
+ }
+ }
+
+ return true;
};
-NTIP.arrayLooping = function (...arraystoloop) {
- for (let arr of arraystoloop) {
- if (Array.isArray(arr)) {
- for (let i = 0; i < arr.length; i++) {
- NTIP.addLine(arr[i]);
- }
- }
- }
-
- return true;
-};
-
-NTIP.hasStats = function (item, entryList = [], verbose = false) {
- let hasStat = false, line = "", stats;
- const list = entryList.length ? entryList : NTIP.SoloCheckList;
- const stringArr = entryList.length ? stringArray : NTIP.SoloStringArray;
-
- for (let i = 0; i < list.length; i++) {
- try {
- // eslint-disable-next-line no-unused-vars
- let [type, stat, wanted] = list[i];
-
- if (typeof type === "function") {
- if (type(item)) {
- if (typeof stat === "function") {
- if (stat(item)) {
- hasStat = true;
- stats = stat;
- line = stringArr[i].file + " #" + stringArr[i].line + " " + stringArr[i].string;
-
- break;
- }
- } else {
- hasStat = false;
-
- break;
- }
- }
- }
- } catch (e) {
- console.log(e);
- hasStat = false;
-
- break;
- }
- }
-
- if (hasStat && verbose) {
- console.debug(stats);
- console.debug(line);
- }
-
- return hasStat;
+/**
+ * @param {ItemUnit} item
+ * @param {NTIPList} entryList
+ * @param {boolean} verbose
+ */
+NTIP.hasStats = function (item, entryList, verbose = false) {
+ let stats;
+ let hasStat = false;
+ let line = "";
+ const list = entryList && entryList.hasOwnProperty("list") && entryList.list.length
+ ? entryList.list
+ : NTIP.SoloList.list;
+ const stringArr = entryList && entryList.hasOwnProperty("strArray") && entryList.strArray.length
+ ? entryList.strArray
+ : NTIP.SoloList.strArray;
+
+ for (let i = 0; i < list.length; i++) {
+ try {
+ let [type, stat] = list[i];
+
+ if (typeof type !== "function" || typeof stat !== "function") {
+ continue;
+ }
+ if (type(item)) {
+ if (stat(item)) {
+ hasStat = true;
+ stats = stat;
+ line = stringArr[i].file + " #" + stringArr[i].line + " " + stringArr[i].string;
+
+ break;
+ }
+ }
+ } catch (e) {
+ console.log(e);
+ hasStat = false;
+
+ break;
+ }
+ }
+
+ if (hasStat && verbose) {
+ console.debug(stats);
+ console.debug(line);
+ }
+
+ return hasStat;
};
// this method for charms needs work
-NTIP.getInvoQuantity = function (item, entryList = []) {
- const list = entryList.length ? entryList : NTIP.SoloCheckList;
-
- for (let i = 0; i < list.length; i++) {
- try {
- const [type, stat, wanted] = list[i];
-
- if (typeof type === "function") {
- if (type(item)) {
- if (typeof stat === "function") {
- if (stat(item)) {
- if (wanted && wanted.InvoQuantity && !isNaN(wanted.InvoQuantity)) {
- return wanted.InvoQuantity;
- }
- }
- } else {
- if (wanted && wanted.InvoQuantity && !isNaN(wanted.InvoQuantity)) {
- return wanted.InvoQuantity;
- }
- }
- }
- } else if (typeof stat === "function") {
- if (stat(item)) {
- if (wanted && wanted.InvoQuantity && !isNaN(wanted.InvoQuantity)) {
- return wanted.InvoQuantity;
- }
- }
- }
- } catch (e) {
- return -1;
- }
- }
-
- return -1;
+/**
+ * @param {ItemUnit} item
+ * @param {NTIPList} entryList
+ */
+NTIP.getInvoQuantity = function (item, entryList) {
+ const list = entryList && entryList.hasOwnProperty("list") && entryList.list.length
+ ? entryList.list
+ : NTIP.SoloList.list;
+
+ for (let el of list) {
+ try {
+ const [type, stat, wanted] = el;
+
+ if (typeof type === "function") {
+ if (type(item)) {
+ if (typeof stat === "function") {
+ if (stat(item)) {
+ if (wanted && wanted.InvoQuantity && !isNaN(wanted.InvoQuantity)) {
+ return wanted.InvoQuantity;
+ }
+ }
+ } else {
+ if (wanted && wanted.InvoQuantity && !isNaN(wanted.InvoQuantity)) {
+ return wanted.InvoQuantity;
+ }
+ }
+ }
+ } else if (typeof stat === "function") {
+ if (stat(item)) {
+ if (wanted && wanted.InvoQuantity && !isNaN(wanted.InvoQuantity)) {
+ return wanted.InvoQuantity;
+ }
+ }
+ }
+ } catch (e) {
+ return -1;
+ }
+ }
+
+ return -1;
};
-NTIP.getMaxQuantity = function (item, entryList = []) {
- const list = entryList.length ? entryList : NTIP.SoloCheckList;
-
- for (let i = 0; i < list.length; i++) {
- try {
- let [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)) {
- return wanted.MaxQuantity;
- }
- }
- } else {
- if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) {
- return wanted.MaxQuantity;
- }
- }
- }
- } else if (typeof stat === "function") {
- if (stat(item)) {
- if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) {
- return wanted.MaxQuantity;
- }
- }
- }
- } catch (e) {
- return -1;
- }
- }
-
- return -1;
+/**
+ * @param {ItemUnit} item
+ * @param {NTIPList} entryList
+ */
+NTIP.getMaxQuantity = function (item, entryList) {
+ const list = entryList && entryList.hasOwnProperty("list") && entryList.list.length
+ ? entryList.list
+ : NTIP.SoloList.list;
+
+ for (let el of list) {
+ try {
+ let [type, stat, wanted] = el;
+
+ if (typeof type === "function") {
+ if (type(item)) {
+ if (typeof stat === "function") {
+ if (stat(item)) {
+ if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) {
+ return wanted.MaxQuantity;
+ }
+ }
+ } else {
+ if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) {
+ return wanted.MaxQuantity;
+ }
+ }
+ }
+ } else if (typeof stat === "function") {
+ if (stat(item)) {
+ if (wanted && wanted.MaxQuantity && !isNaN(wanted.MaxQuantity)) {
+ return wanted.MaxQuantity;
+ }
+ }
+ }
+ } catch (e) {
+ return -1;
+ }
+ }
+
+ return -1;
};
+/**
+ * @param {ItemUnit} item
+ * @param {NTIPList} entryList
+ * @param {boolean} verbose
+ */
NTIP.CheckItem = function (item, entryList, verbose = false) {
- let rval = {};
- let result = 0;
- const identified = item.getFlag(sdk.items.flags.Identified);
-
- const iterateList = (list, stringArr) => {
- let i, num;
-
- 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.mode === sdk.items.mode.inStorage && num === wanted.MaxQuantity) {
- result = 1;
-
- break;
- }
- }
- } else {
- result = 1;
-
- break;
- }
- } else if (!identified && result === 0 || !identified && result === 1) {
- result = -1;
-
- if (verbose) {
- rval.line = stringArr[i].file + " #" + stringArr[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.mode === sdk.items.mode.inStorage && 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.mode === sdk.items.mode.inStorage && num === wanted.MaxQuantity) {
- result = 1;
-
- break;
- }
- }
- } else {
- result = 1;
-
- break;
- }
- } else if (!identified && result === 0 || !identified && result === 1) {
- result = -1;
-
- if (verbose) {
- rval.line = stringArr[i].file + " #" + stringArr[i].line;
- }
- }
- }
- } catch (pickError) {
- showConsole();
-
- if (!entryList) {
- Misc.errorReport("ÿc1Pickit error! Line # ÿc2" + stringArr[i].line + " ÿc1Entry: ÿc0" + stringArr[i].string + " (" + stringArr[i].file + ") Error message: " + pickError.message + " Trigger item: " + item.fname.split("\n").reverse().join(" "));
-
- list.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 = stringArr[i].file + " #" + stringArr[i].line;
-
- break;
- default:
- rval.line = null;
-
- break;
- }
-
- rval.result = result;
-
- if (!identified && result === 1) {
- rval.result = -1;
- }
-
- return rval;
- }
-
- return result;
- };
-
- const listOfLists = [[NTIP.SoloCheckList, NTIP.SoloStringArray], [NTIP_CheckList, stringArray], [NTIP.RuntimeCheckList, NTIP.RuntimeStringArray]];
- if (Array.isArray(entryList)) return iterateList(entryList, stringArray);
-
- for (let i = 0; i < listOfLists.length; i++) {
- iterateList(listOfLists[i][0], listOfLists[i][1]);
- if ((verbose && rval.result !== 0) || (!verbose && result !== 0)) {
- break;
- }
- }
-
- return verbose ? rval : result;
+ let rval = {};
+ let result = 0;
+ const identified = item.getFlag(sdk.items.flags.Identified);
+
+ /**
+ * @param {any[]} list
+ * @param {string[]} stringArr
+ * @returns
+ */
+ const iterateList = function (list, stringArr) {
+ let i, num;
+
+ 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.mode === sdk.items.mode.inStorage
+ && num === wanted.MaxQuantity) {
+ result = 1;
+
+ break;
+ }
+ }
+ } else {
+ result = 1;
+
+ break;
+ }
+ } else if (!identified && result === 0 || !identified && result === 1) {
+ result = -1;
+
+ if (verbose && stringArr[i] !== undefined) {
+ rval.line = stringArr[i].file + " #" + stringArr[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.mode === sdk.items.mode.inStorage
+ && 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.mode === sdk.items.mode.inStorage
+ && num === wanted.MaxQuantity) {
+ result = 1;
+
+ break;
+ }
+ }
+ } else {
+ result = 1;
+
+ break;
+ }
+ } else if (!identified && result === 0 || !identified && result === 1) {
+ result = -1;
+
+ if (verbose && stringArr[i] !== undefined) {
+ rval.line = stringArr[i].file + " #" + stringArr[i].line;
+ }
+ }
+ }
+ } catch (pickError) {
+ showConsole();
+
+ if (!entryList) {
+ Misc.errorReport(
+ "ÿc1Pickit error! Line # ÿc2" + stringArr[i].line
+ + " ÿc1Entry: ÿc0" + stringArr[i].string + " (" + stringArr[i].file
+ + ") Error message: " + pickError.message
+ + " Trigger item: " + item.fname.split("\n").reverse().join(" ")
+ );
+ list.splice(i, 1); // Remove the element from the list
+ stringArr.splice(i, 1); // Remove the element from the list
+ } else {
+ Misc.errorReport("ÿc1Pickit error in runeword config!");
+ }
+
+ result = 0;
+ }
+ }
+
+ if (verbose) {
+ if (!identified && result === 1) {
+ result = -1;
+ }
+
+ rval.result = result;
+ rval.line = (function () {
+ if (stringArr[i] === undefined) return null;
+ return result === 1
+ ? stringArr[i].file + " #" + stringArr[i].line + " [" + stringArr[i].string + "]"
+ : null;
+ })();
+
+ return rval;
+ }
+
+ return result;
+ };
+
+ if (entryList && entryList.hasOwnProperty("list") && Array.isArray(entryList.list)) {
+ return iterateList(entryList.list, entryList.strArray);
+ }
+
+ const listOfLists = [
+ NTIP.SoloList,
+ NTIP.NoTier,
+ NTIP.Runtime,
+ NTIP.CheckList,
+ ];
+
+ for (let obj of listOfLists) {
+ iterateList(obj.list, obj.strArray);
+ if (verbose ? rval.result !== 0 : result !== 0) {
+ break;
+ }
+ }
+
+ return verbose ? rval : result;
};
+/**
+ * @param {string} filepath
+ * @param {boolean} notify
+ * @returns {boolean}
+ */
NTIP.OpenFile = function (filepath, notify) {
- if (!FileTools.exists(filepath)) {
- if (notify) {
- Misc.errorReport("ÿc1NIP file doesn't exist: ÿc0" + filepath);
- }
-
- return false;
- }
-
- let nipfile, tick = getTickCount(), entries = 0;
- let filename = filepath.substring(filepath.lastIndexOf("/") + 1, filepath.length);
-
- try {
- nipfile = File.open(filepath, 0);
- } catch (fileError) {
- 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 += 1) {
- const 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);
- } else {
- NTIP_CheckListNoTier.push([false, false]);
- }
-
- 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;
+ if (!FileTools.exists(filepath)) {
+ if (notify) {
+ Misc.errorReport("ÿc1NIP file doesn't exist: ÿc0" + filepath);
+ }
+
+ return false;
+ }
+
+ let nipfile, tick = getTickCount(), entries = 0;
+ const filename = filepath.substring(filepath.lastIndexOf("/") + 1, filepath.length);
+
+ try {
+ nipfile = File.open(filepath, 0);
+ } catch (fileError) {
+ notify && Misc.errorReport("ÿc1Failed to load NIP: ÿc0" + filename);
+ }
+
+ if (!nipfile) return false;
+
+ let lines = nipfile.readAllLines();
+ let lineNumber = 1;
+ nipfile.close();
+
+ /**
+ * @note removed tier'd check for normal pick files as soloplay handles that
+ */
+ for (let entry of lines) {
+ const info = {
+ index: NTIP.CheckList.list.length + 1,
+ line: lineNumber,
+ file: filename,
+ string: entry
+ };
+
+ let line = NTIP.ParseLineInt(entry, info);
+
+ if (line) {
+ NTIP.CheckList.add(line, info);
+ entries++;
+ }
+ lineNumber++;
+ }
+
+ if (notify) {
+ console.log(
+ "ÿc4Loaded NIP: ÿc2" + filename + "ÿc4. Lines: ÿc2" + lines.length
+ + "ÿc4. Valid entries: ÿc2" + entries
+ + ". ÿc4Time: ÿc2" + (getTickCount() - tick) + " ms"
+ );
+ }
+
+ return true;
};
NTIP.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 "wsm":
- case "weaponspeed":
- p_result[0] += 'getBaseStat("items", item.classid, "speed")';
-
- break;
- case "minimumsockets":
- p_result[0] += 'getBaseStat("items", item.classid, "gemsockets")';
-
- break;
- case "strreq":
- p_result[0] += "item.strreq";
-
- break;
- case "dexreq":
- p_result[0] += "item.dexreq";
-
- break;
- case "2handed":
- p_result[0] += 'getBaseStat("items", item.classid, "2handed")';
-
- break;
- 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) {
- // Charm equip specific
- case "invoquantity":
- let quantity = Number(p_section[i].split("==")[1].match(/\d+/g));
-
- if (!isNaN(quantity)) {
- p_result[2].InvoQuantity = quantity;
- }
-
- break;
- case "finalcharm":
- let check = Boolean(p_section[i].split("==")[1].match(/(\b(?!split\b)[^ $]+\b)/g));
-
- if (!isNaN(check)) {
- p_result[2].FinalCharm = check;
- }
-
- break;
- case "maxquantity":
- value = Number(p_section[i].split("==")[1].match(/\d+/g));
-
- if (!isNaN(value)) {
- p_result[2].MaxQuantity = value;
- }
-
- break;
- case "tier":
- case "secondarytier":
- case "charmtier":
- case "merctier":
- try {
- 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;
+ let i, property, p_start, p_section, p_keyword, value;
+
+ let 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;
+ }
+
+ let p_result = input.split("#");
+
+ 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)) {
+ Misc.errorReport("Unknown property: " + property + " File: " + info.file + " Line: " + info.line);
+
+ return false;
+ }
+ 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) === "=") {
+ 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)) {
+ try {
+ 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;
+ }
+ } catch (e) {
+ Misc.errorReport(e);
+
+ return false;
+ }
+ } 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);
+
+ /** @type {string} */
+ let keyword = p_keyword.toLowerCase();
+
+ switch (keyword) {
+ // Charm equip specific
+ case "invoquantity":
+ let quantity = Number(p_section[i].split("==")[1].match(/\d+/g));
+
+ if (!isNaN(quantity)) {
+ p_result[2].InvoQuantity = quantity;
+ }
+
+ break;
+ case "maxquantity":
+ value = Number(p_section[i].split("==")[1].match(/\d+/g));
+
+ if (!isNaN(value)) {
+ p_result[2].MaxQuantity = value;
+ }
+
+ break;
+ case "tier":
+ case "secondarytier":
+ case "charmtier":
+ case "merctier":
+ try {
+ p_result[2][keyword.capitalize()] = (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/libs/SoloPlay/Functions/PatherOverrides.js b/libs/SoloPlay/Functions/PatherOverrides.js
index 3c5e77ec..f0b721f3 100644
--- a/libs/SoloPlay/Functions/PatherOverrides.js
+++ b/libs/SoloPlay/Functions/PatherOverrides.js
@@ -6,123 +6,183 @@
*
*/
-includeIfNotIncluded("common/Pather.js");
+includeIfNotIncluded("core/Pather.js");
Developer.debugging.pathing && (PathDebug.enableHooks = true);
+/** @global */
+const AreaData = require("../Modules/GameData/AreaData");
+
+/**
+ * Easier way to check if you have a waypoint
+ * @param {number} area
+ * @returns {boolean}
+ */
+me.haveWaypoint = function (area) {
+ let checkArea = AreaData.get(area);
+ if (!checkArea || !checkArea.hasWaypoint()) return false;
+ return getWaypoint(AreaData.wps.get(area));
+};
+
Pather.inAnnoyingArea = function (currArea, includeArcane = false) {
- const areas = [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3];
- includeArcane && areas.push(sdk.areas.ArcaneSanctuary);
- return areas.includes(currArea);
+ const areas = [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3];
+ includeArcane && areas.push(sdk.areas.ArcaneSanctuary);
+ return areas.includes(currArea);
};
/**
- * @param {Object} arg
- * @param {boolean} arg.canTele
- * @param {boolean} arg.clearPath
- * @param {number} arg.range
- * @param {number} arg.specType
- * @param {boolean} [arg.allowClearing]
+ * @typedef {Object} clearSettings
+ * @property {boolean} canTele
+ * @property {boolean} clearPath
+ * @property {number} range
+ * @property {number} specType
+ * @property {boolean} [allowClearing]
+ */
+
+/**
+ * @param {clearSettings} arg
* @returns {void}
* @todo
* - clean this up
* - use effort level calculations to control clearing
*/
NodeAction.killMonsters = function (arg = {}) {
- if (Attack.stopClear || (arg.hasOwnProperty("allowClearing") && !arg.allowClearing)) return;
-
- const myArea = me.area;
- // I don't think this is even needed anymore, pretty sure I fixed wall hugging. todo - check it
- const pallyAnnoyingAreas = [
- sdk.areas.DenofEvil, sdk.areas.CaveLvl1, sdk.areas.UndergroundPassageLvl1, sdk.areas.HoleLvl1, sdk.areas.PitLvl1, sdk.areas.CaveLvl2,
- sdk.areas.UndergroundPassageLvl2, sdk.areas.PitLvl2, sdk.areas.HoleLvl2, sdk.areas.DisusedFane, sdk.areas.RuinedTemple,
- sdk.areas.ForgottenReliquary, sdk.areas.ForgottenTemple, sdk.areas.RuinedFane, sdk.areas.DisusedReliquary
- ];
- const summonerAreas = [
- sdk.areas.DenofEvil, sdk.areas.ColdPlains, sdk.areas.StonyField, sdk.areas.Tristram, sdk.areas.DarkWood, sdk.areas.BlackMarsh,
- sdk.areas.OuterCloister, sdk.areas.Barracks, sdk.areas.Cathedral, sdk.areas.CatacombsLvl4, sdk.areas.HallsoftheDeadLvl1, sdk.areas.HallsoftheDeadLvl2,
- sdk.areas.HallsoftheDeadLvl3, sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2,
- sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7
- ];
- // sanityCheck from isid0re - added paladin specific areas - theBGuy - a mess.. sigh
- if (Pather.inAnnoyingArea(myArea, true) || (me.paladin && pallyAnnoyingAreas.includes(myArea))) {
- arg.range = 7;
- }
-
- /**
- * @todo:
- * - we don't need this if we have a lightning chain based skill, e.g light sorc, light zon
- * - better monster sorting. If we are low level priortize killing easy targets like zombies/quill rats while ignoring fallens unless they are in our path
- */
- if (!arg.canTele && arg.clearPath !== false) {
- let monList = [];
- if (me.inArea(sdk.areas.BloodMoor)) {
- monList = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.attackable && mon.distance < 30 && !mon.isFallen
- && !checkCollision(me, mon, (sdk.collision.BlockWall | sdk.collision.LineOfSight | sdk.collision.Ranged)));
- monList.length > 0 && Attack.clearList(monList);
- }
-
- if (summonerAreas.includes(myArea)) {
- monList = getUnits(sdk.unittype.Monster)
- .filter(mon => mon.attackable && mon.distance < 30 && (mon.isUnraveler || mon.isShaman)
- && !checkCollision(me, mon, (sdk.collision.BlockWall | sdk.collision.LineOfSight | sdk.collision.Ranged)));
- monList.length > 0 && Attack.clearList(monList);
- }
-
- if ([sdk.areas.StonyField, sdk.areas.BlackMarsh, sdk.areas.FarOasis].includes(me.area)) {
- // monster nest's are good exp
- monList = getUnits(sdk.unittype.Monster).filter(mon => mon.attackable && mon.distance < 35 && mon.isMonsterNest);
- monList.length > 0 && Attack.clearList(monList);
- }
- }
-
- if (arg.clearPath !== false) {
- Attack.clear(arg.range, arg.specType);
- }
+ if (Attack.stopClear || (arg.hasOwnProperty("allowClearing") && !arg.allowClearing)) return;
+
+ const myArea = me.area;
+ // I don't think this is even needed anymore, pretty sure I fixed wall hugging. todo - check it
+ const pallyAnnoyingAreas = [
+ sdk.areas.DenofEvil, sdk.areas.CaveLvl1,
+ sdk.areas.UndergroundPassageLvl1, sdk.areas.HoleLvl1,
+ sdk.areas.PitLvl1, sdk.areas.CaveLvl2,
+ sdk.areas.UndergroundPassageLvl2, sdk.areas.PitLvl2,
+ sdk.areas.HoleLvl2, sdk.areas.DisusedFane,
+ sdk.areas.RuinedTemple, sdk.areas.ForgottenReliquary,
+ sdk.areas.ForgottenTemple, sdk.areas.RuinedFane, sdk.areas.DisusedReliquary
+ ];
+ const summonerAreas = [
+ sdk.areas.DenofEvil, sdk.areas.ColdPlains,
+ sdk.areas.StonyField, sdk.areas.Tristram,
+ sdk.areas.DarkWood, sdk.areas.BlackMarsh,
+ sdk.areas.OuterCloister, sdk.areas.Barracks,
+ sdk.areas.Cathedral, sdk.areas.CatacombsLvl4,
+ sdk.areas.HallsoftheDeadLvl1, sdk.areas.HallsoftheDeadLvl2,
+ sdk.areas.HallsoftheDeadLvl3, sdk.areas.ValleyofSnakes,
+ sdk.areas.ClawViperTempleLvl1, sdk.areas.TalRashasTomb1,
+ sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3,
+ sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5,
+ sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7
+ ];
+ // sanityCheck from isid0re - added paladin specific areas - theBGuy - a mess.. sigh
+ if (Pather.inAnnoyingArea(myArea, true) || (me.paladin && pallyAnnoyingAreas.includes(myArea))) {
+ arg.range = 7;
+ }
+
+ /**
+ * @todo:
+ * - we don't need this if we have a lightning chain based skill, e.g light sorc, light zon
+ * - better monster sorting. If we are low level priortize killing easy targets like zombies/quill rats while ignoring fallens unless they are in our path
+ * - ignore dolls when walking unless absolutely necessary because we are blocked
+ */
+ if (!arg.canTele && arg.clearPath !== false) {
+ /** @type {Array} */
+ const monList = [];
+ /** @param {Monster} mon */
+ const addToMonList = function (mon) {
+ monList.push(mon);
+ };
+ let _coll = (sdk.collision.BlockWall | sdk.collision.LineOfSight | sdk.collision.Ranged);
+
+ if (me.inArea(sdk.areas.BloodMoor)) {
+ getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && mon.distance < 30
+ && !mon.isFallen && !checkCollision(me, mon, _coll);
+ })
+ .forEach(addToMonList);
+ }
+
+ if (summonerAreas.includes(myArea)) {
+ getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && mon.distance < 30
+ && (mon.isUnraveler || mon.isShaman) && !checkCollision(me, mon, _coll);
+ })
+ .forEach(addToMonList);
+ }
+
+ if ([sdk.areas.StonyField, sdk.areas.BlackMarsh, sdk.areas.FarOasis].includes(me.area)) {
+ // monster nest's are good exp
+ getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && mon.distance < 35 && mon.isMonsterNest;
+ })
+ .forEach(addToMonList);
+ }
+ // need to write a way to consider current path
+ monList.length > 0 && Attack.clearList(monList);
+ }
+
+ if (arg.clearPath !== false) {
+ Attack.clear(arg.range, arg.specType);
+ }
};
-NodeAction.popChests = function () {
- const range = Pather.useTeleport() ? 25 : 15;
- Config.OpenChests.Enabled && Misc.openChests(range);
- Misc.useWell(range);
+/** @param {clearSettings} arg */
+NodeAction.popChests = function (arg = {}) {
+ const range = arg.canTele ? 25 : 15;
+ Config.OpenChests.Enabled && Misc.openChests(range);
+ Misc.useWell(range);
};
+/** @param {clearSettings} arg */
NodeAction.pickItems = function (arg = {}) {
- if (arg.hasOwnProperty("allowPicking") && !arg.allowPicking) return;
-
- let item = Game.getItem();
- const maxDist = Skill.haveTK ? 15 : 5;
- const regPickRange = Pather.canTeleport() ? Config.PickRange : 8;
- const maxRange = Math.max(maxDist, regPickRange);
- const totalList = [].concat(Pickit.essentialList, Pickit.pickList);
- const filterJunk = (item) => !!item && item.onGroundOrDropping;
-
- if (item) {
- do {
- if (item.onGroundOrDropping) {
- const itemDist = getDistance(me, item);
- if (itemDist > maxRange) continue;
- if (totalList.some(el => el.gid === item.gid)) continue;
- if (Pickit.essentials.includes(item.itemType)) {
- if (itemDist <= maxDist && (item.itemType !== sdk.items.type.Gold || itemDist < 5)
- && Pickit.checkItem(item).result && Pickit.canPick(item) && Pickit.canFit(item)) {
- Pickit.essentialList.push(copyUnit(item));
- }
- } else if (itemDist <= regPickRange && item.itemType === sdk.items.type.Key) {
- if (Pickit.canPick(item) && Pickit.checkItem(item).result) {
- Pickit.pickList.push(copyUnit(item));
- }
- } else if (itemDist <= regPickRange && Pickit.checkItem(item).result) {
- Pickit.pickList.push(copyUnit(item));
- }
- }
- } while (item.getNext());
- }
- Pickit.essentialList.length > 0 && (Pickit.essentialList = Pickit.essentialList.filter(filterJunk));
- Pickit.pickList.length > 0 && (Pickit.pickList = Pickit.pickList.filter(filterJunk));
- Pickit.essentialList.length > 0 && Pickit.essessntialsPick(false, false);
- Pickit.pickList.length > 0 && Pickit.pickItems(regPickRange);
+ if (arg.hasOwnProperty("allowPicking") && !arg.allowPicking) return;
+
+ let item = Game.getItem();
+
+ if (item) {
+ const maxDist = Skill.haveTK ? 15 : 5;
+ const regPickRange = arg.canTele ? Config.PickRange : 8;
+ const maxRange = Math.max(maxDist, regPickRange);
+ const totalList = [].concat(Pickit.essentialList, Pickit.pickList);
+ /** @param {ItemUnit} item */
+ const filterJunk = function (item) {
+ return !!item && item.onGroundOrDropping;
+ };
+
+ do {
+ if (item.onGroundOrDropping) {
+ const itemDist = getDistance(me, item);
+ if (itemDist > maxRange) continue;
+ if (totalList.some(el => el.gid === item.gid)) continue;
+ if (item.itemType === sdk.items.type.Gold && Pickit.canPick(item)) {
+ itemDist < 5
+ ? Pickit.pickItem(item)
+ : Pickit.essentialList.push(copyUnit(item));
+ } else if (Pickit.essentials.includes(item.itemType)) {
+ if (itemDist <= maxDist) {
+ if (Pickit.checkItem(item).result && Pickit.canPick(item) && Pickit.canFit(item)) {
+ itemDist < 5
+ ? Pickit.pickItem(item)
+ : Pickit.essentialList.push(copyUnit(item));
+ }
+ }
+ } else if (itemDist <= regPickRange && item.itemType === sdk.items.type.Key) {
+ if (Pickit.canPick(item) && Pickit.checkItem(item).result) {
+ Pickit.pickList.push(copyUnit(item));
+ }
+ } else if (itemDist <= regPickRange && Pickit.checkItem(item).result) {
+ Pickit.pickList.push(copyUnit(item));
+ }
+ }
+ } while (item.getNext());
+
+ Pickit.essentialList.length > 0 && (Pickit.essentialList = Pickit.essentialList.filter(filterJunk));
+ Pickit.pickList.length > 0 && (Pickit.pickList = Pickit.pickList.filter(filterJunk));
+ Pickit.essentialList.length > 0 && Pickit.essessntialsPick(false);
+ Pickit.pickList.length > 0 && Pickit.pickItems(regPickRange);
+ }
};
// todo - fast shrineing, if we are right next to a shrine then grab it even with mobs around
@@ -132,656 +192,941 @@ Pather.forceWalk = false;
Pather.forceRun = false;
{
- 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.defineProperty(Object.prototype, "mobCount", {
- writable: true,
- enumerable: false,
- configurable: true,
- value: function (givenSettings = {}) {
- let [x, y] = coords.apply(this);
- const settings = Object.assign({}, {
- range: 5,
- coll: (sdk.collision.BlockWall | sdk.collision.ClosedDoor | sdk.collision.LineOfSight | sdk.collision.BlockMissile),
- type: 0,
- ignoreClassids: [],
- }, givenSettings);
- return getUnits(sdk.unittype.Monster)
- .filter(function (mon) {
- return mon.attackable && getDistance(x, y, mon.x, mon.y) < settings.range
- && (!settings.type || (settings.type & mon.spectype))
- && (settings.ignoreClassids.indexOf(mon.classid) === -1)
- && !CollMap.checkColl({x: x, y: y}, mon, settings.coll, 1);
- }).length;
- }
- });
+ 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.defineProperty(Object.prototype, "mobCount", {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+ value: function (givenSettings = {}) {
+ let [x, y] = coords.apply(this);
+ const settings = Object.assign({}, {
+ range: 5,
+ coll: (
+ sdk.collision.BlockWall | sdk.collision.ClosedDoor | sdk.collision.LineOfSight | sdk.collision.BlockMissile
+ ),
+ type: 0,
+ ignoreClassids: [],
+ }, givenSettings);
+ return getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && getDistance(x, y, mon.x, mon.y) < settings.range
+ && (!settings.type || (settings.type & mon.spectype))
+ && (settings.ignoreClassids.indexOf(mon.classid) === -1)
+ && !CollMap.checkColl({ x: x, y: y }, mon, settings.coll, 1);
+ }).length;
+ }
+ });
}
Pather.checkForTeleCharges = function () {
- this.haveTeleCharges = Attack.getItemCharges(sdk.skills.Teleport);
+ this.haveTeleCharges = Attack.getItemCharges(sdk.skills.Teleport);
};
Pather.canUseTeleCharges = function () {
- if (me.classic || me.inTown || me.shapeshifted) return false;
- // Charges are costly so make sure we have enough gold to handle repairs unless we are in maggot lair since thats a pita and worth the gold spent
- if (me.gold < 500000 && !Pather.inAnnoyingArea(me.area)) return false;
+ if (me.classic || me.inTown || me.shapeshifted) return false;
+ // Charges are costly so make sure we have enough gold to handle repairs
+ // unless we are in maggot lair since thats a pita and worth the gold spent
+ if (me.gold < 500000 && !Pather.inAnnoyingArea(me.area)) return false;
- return this.haveTeleCharges;
+ return this.haveTeleCharges;
};
Pather.teleportTo = function (x, y, maxRange = 5) {
- Developer.debugging.pathing && console.log("Mob Count at next node: " + [x, y].mobCount());
-
- for (let i = 0; i < 3; i += 1) {
- Skill.setSkill(sdk.skills.Teleport, sdk.skills.hand.Right) && Packet.castSkill(sdk.skills.hand.Right, x, y);
- let tick = getTickCount();
- let pingDelay = i === 0 ? 250 : me.getPingDelay();
-
- while (getTickCount() - tick < Math.max(500, pingDelay * 2 + 200)) {
- if (getDistance(me.x, me.y, x, y) < maxRange) {
- return true;
- }
-
- delay(10);
- }
- }
-
- return false;
+ // Developer.debugging.pathing && console.log("Mob Count at next node: " + [x, y].mobCount());
+
+ for (let i = 0; i < 3; i += 1) {
+ if (!Packet.teleport(x, y)) continue;
+ let tick = getTickCount();
+ let pingDelay = i === 0 ? 250 : me.getPingDelay();
+
+ while (getTickCount() - tick < Math.max(500, pingDelay * 2 + 200)) {
+ if (getDistance(me.x, me.y, x, y) < maxRange) {
+ return true;
+ }
+
+ delay(10);
+ }
+ }
+
+ return false;
};
Pather.teleUsingCharges = function (x, y, maxRange = 5) {
- let orgSlot = me.weaponswitch;
-
- for (let i = 0; i < 3; i++) {
- me.castChargedSkill(sdk.skills.Teleport, x, y);
- let tick = getTickCount();
-
- while (getTickCount() - tick < Math.max(500, me.ping * 2 + 200)) {
- if (getDistance(me.x, me.y, x, y) < maxRange) {
- me.weaponswitch !== orgSlot && me.switchWeapons(orgSlot);
- return true;
- }
-
- delay(10);
- }
- }
-
- if (me.gold > me.getRepairCost() * 3 && me.canTpToTown()) {
- console.debug("Tele-Charge repair");
- Town.visitTown(true);
- } else {
- this.haveTeleCharges = false;
- }
-
- me.weaponswitch !== orgSlot && me.switchWeapons(orgSlot);
-
- return false;
+ let orgSlot = me.weaponswitch;
+
+ try {
+ for (let i = 0; i < 3; i++) {
+ me.castChargedSkillEx(sdk.skills.Teleport, x, y);
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < Math.max(500, me.ping * 2 + 200)) {
+ if (getDistance(me.x, me.y, x, y) < maxRange) {
+ return true;
+ }
+
+ delay(10);
+ }
+ }
+
+ if (CharData.skillData.haveChargedSkill(sdk.skills.Teleport)
+ && !Attack.getItemCharges(sdk.skills.Teleport)) {
+
+ if (me.gold > me.getRepairCost() * 3 && me.canTpToTown()) {
+ console.debug("Tele-Charge repair");
+ Town.visitTown(true);
+ } else {
+ this.haveTeleCharges = false;
+ }
+ }
+
+ return false;
+ } finally {
+ me.weaponswitch !== orgSlot && me.switchWeapons(orgSlot);
+ }
};
Pather.checkWP = function (area = 0, keepMenuOpen = false) {
- while (!me.gameReady) {
- delay(40);
- }
-
- // only do this if we haven't initialzed our wp data
- if (!getWaypoint(Pather.wpAreas.indexOf(area)) && !Pather.initialized) {
- me.inTown && !getUIFlag(sdk.uiflags.Waypoint) && Town.move("waypoint");
-
- for (let i = 0; i < 15; i++) {
- let wp = Game.getObject("waypoint");
- let useTK = (Skill.useTK(wp) && i < 5);
- let pingDelay = me.getPingDelay();
-
- if (wp && wp.area === me.area) {
- if (useTK) {
- wp.distance > 21 && Pather.moveNearUnit(wp, 20);
- Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, wp);
- } else {
- wp.distance > 7 && this.moveToUnit(wp);
- Misc.click(0, 0, wp);
- }
-
- let tick = getTickCount();
-
- while (getTickCount() - tick < Math.max(Math.round((i + 1) * 1000 / (i / 5 + 1)), (1 + pingDelay * 2))) {
- if (getUIFlag(sdk.uiflags.Waypoint)) {
- delay(500 + pingDelay);
- break;
- }
-
- delay(50 + pingDelay);
- }
- } else {
- me.inTown && Town.move("waypoint");
- }
-
- if (getUIFlag(sdk.uiflags.Waypoint)) {
- !keepMenuOpen && me.cancel();
- Pather.initialized = true;
- break;
- }
- }
- // go ahead and close out of wp menu if we don't have the wp
- !getWaypoint(Pather.wpAreas.indexOf(area)) && getUIFlag(sdk.uiflags.Waypoint) && me.cancel();
- }
-
- return getWaypoint(Pather.wpAreas.indexOf(area));
+ while (!me.gameReady) {
+ delay(40);
+ }
+
+ // only do this if we haven't initialzed our wp data
+ if (!me.haveWaypoint(area) && !Pather.initialized) {
+ me.inTown && !getUIFlag(sdk.uiflags.Waypoint) && Town.move("waypoint");
+
+ for (let i = 0; i < 15; i++) {
+ let wp = Game.getObject("waypoint");
+ let useTK = (Skill.useTK(wp) && i < 5);
+ let pingDelay = me.getPingDelay();
+
+ if (wp && wp.area === me.area) {
+ if (useTK) {
+ wp.distance > 21 && Pather.moveNearUnit(wp, 20);
+ Packet.telekinesis(wp);
+ } else {
+ wp.distance > 7 && this.moveToUnit(wp);
+ Misc.click(0, 0, wp);
+ }
+
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < Math.max(Math.round((i + 1) * 1000 / (i / 5 + 1)), (1 + pingDelay * 2))) {
+ if (getUIFlag(sdk.uiflags.Waypoint)) {
+ delay(500 + pingDelay);
+ break;
+ }
+
+ delay(50 + pingDelay);
+ }
+ } else {
+ me.inTown && Town.move("waypoint");
+ }
+
+ if (getUIFlag(sdk.uiflags.Waypoint)) {
+ !keepMenuOpen && me.cancel();
+ Pather.initialized = true;
+ break;
+ }
+ }
+ // go ahead and close out of wp menu if we don't have the wp
+ !me.haveWaypoint(area) && getUIFlag(sdk.uiflags.Waypoint) && me.cancel();
+ }
+
+ return me.haveWaypoint(area);
};
-Pather.changeAct = function () {
- let act = me.act + 1;
- let [npc, loc] = (() => {
- switch (act) {
- case 2:
- return ["Warriv", sdk.areas.LutGholein];
- case 3:
- return ["Meshif", sdk.areas.KurastDocktown];
- case 5:
- return ["Tyrael", sdk.areas.Harrogath];
- default:
- return ["", 0];
- }
- })();
- if (!npc) return false;
-
- !me.inTown && Town.goToTown();
- let npcUnit = Town.npcInteract(npc);
- let timeout = getTickCount() + 3000;
- let pingDelay = me.getPingDelay();
-
- if (!npcUnit) {
- while (!npcUnit && timeout < getTickCount()) {
- Town.move(NPC[npc]);
- Packet.flash(me.gid, pingDelay);
- delay(pingDelay * 2 + 100);
- npcUnit = Game.getNPC(npc);
- }
- }
-
- if (npcUnit) {
- for (let i = 0; i < 5; i++) {
- sendPacket(1, 56, 4, 0, 4, npcUnit.gid, 4, loc);
- delay(500 + pingDelay);
-
- if (me.act === act) {
- break;
- }
- }
- } else {
- myPrint("Failed to move to " + npc);
- }
-
- while (!me.gameReady) {
- delay(100);
- }
-
- return me.act === act;
+Pather.changeAct = function (act = me.act + 1) {
+ const npcTravel = new Map([
+ [1, ["Warriv", sdk.areas.RogueEncampment]],
+ [2, [(me.act === 1 ? "Warriv" : "Meshif"), sdk.areas.LutGholein]],
+ [3, ["Meshif", sdk.areas.KurastDocktown]],
+ [5, ["Tyrael", sdk.areas.Harrogath]],
+ ]);
+ let [npc, loc] = npcTravel.get(act);
+ if (!npc) return false;
+
+ !me.inTown && Town.goToTown();
+ let npcUnit = Town.npcInteract(npc);
+ let timeout = getTickCount() + 3000;
+ let pingDelay = me.getPingDelay();
+
+ if (!npcUnit) {
+ while (!npcUnit && timeout < getTickCount()) {
+ Town.move(NPC[npc]);
+ Packet.flash(me.gid, pingDelay);
+ delay(pingDelay * 2 + 100);
+ npcUnit = Game.getNPC(npc);
+ }
+ }
+
+ if (npcUnit) {
+ for (let i = 0; i < 5; i++) {
+ new PacketBuilder()
+ .byte(sdk.packets.send.EntityAction)
+ .dword(0)
+ .dword(npcUnit.gid)
+ .dword(loc)
+ .send();
+ delay(1000);
+
+ if (me.act === act) {
+ break;
+ }
+ }
+ } else {
+ myPrint("Failed to move to " + npc);
+ }
+
+ while (!me.gameReady) {
+ delay(100);
+ }
+
+ return me.act === act;
};
Pather.clearUIFlags = function () {
- Pather.cancelFlags.forEach(flag => {
- getUIFlag(flag) && me.cancel();
- });
+ while (!me.gameReady) delay(3);
+
+ for (let i = 0; i < Pather.cancelFlags.length; i++) {
+ if (getUIFlag(Pather.cancelFlags[i]) && me.cancel()) {
+ delay(250);
+ i = 0; // Reset
+ }
+ }
};
+/**
+ * @memberof Pather
+ * @type {PathNode[]}
+ */
Pather.currentWalkingPath = [];
+
+/**
+ * @param {PathNode | Unit | PresetUnit} target
+ * @param {PathSettings} givenSettings
+ * @returns {boolean}
+ */
Pather.move = function (target, givenSettings = {}) {
- // Abort if dead
- if (me.dead) return false;
- // assign settings
- const settings = Object.assign({}, {
- clearSettings: {
- },
- allowTeleport: true,
- allowPicking: true,
- allowClearing: true,
- allowTown: 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({
- canTele: false,
- clearPath: false,
- range: typeof Config.ClearPath.Range === "number" ? Config.ClearPath.Range : 10,
- specType: typeof Config.ClearPath.Spectype === "number" ? Config.ClearPath.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);
- !settings.allowPicking && (settings.clearSettings.allowPicking = false);
-
- (target instanceof PresetUnit) && (target = { x: target.roomx * 5 + target.x, y: target.roomy * 5 + target.y });
-
- if (settings.minDist > 3) {
- target = Pather.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];
-
- Pather.clearUIFlags();
-
- if (typeof target.x !== "number" || typeof target.y !== "number") return false;
- if (getDistance(me, target) < 2 && !CollMap.checkColl(me, target, Coords_1.Collision.BLOCK_MISSILE, 5)) return true;
-
- const useTeleport = settings.allowTeleport && (getDistance(me, target) > 15 || me.diff || me.act > 3) && Pather.useTeleport();
- settings.clearSettings.canTele = useTeleport;
- const useChargedTele = settings.allowTeleport && Pather.canUseTeleCharges();
- const usingTele = (useTeleport || useChargedTele);
- const tpMana = Skill.getManaCost(sdk.skills.Teleport);
- const annoyingArea = Pather.inAnnoyingArea(me.area);
- let path = getPath(me.area, target.x, target.y, me.x, me.y, usingTele ? 1 : 0, usingTele ? (annoyingArea ? 30 : Pather.teleDistance) : Pather.walkDistance);
- if (!path) throw new Error("move: Failed to generate path.");
-
- // need to work on a better force clearing method but for now just have all walkers clear unless we specifically are forcing them not to (like while repositioning)
- settings.allowClearing && !settings.clearSettings.clearPath && !useTeleport && (settings.clearSettings.clearPath = true);
-
- // for now only do this for teleporters
- if (useTeleport && !me.normal) {
- /** @type {Array} */
- let areaImmunities = GameData.areaImmunities(me.area);
- if (areaImmunities.length) {
- let mySkElems = Config.AttackSkill.filter(sk => sk > 0).map(sk => Attack.getSkillElement(sk));
- // this area has monsters that are immune to our elements. This is a basic check for now
- // a better way would probably be per list built to check the ratio of immunes to non?
- if (mySkElems.length && mySkElems.every(elem => areaImmunities.includes(elem))) {
- settings.clearSettings.clearPath = false;
- }
- } else if (AreaData[me.area].hasMonsterType(sdk.monsters.type.UndeadFetish)) {
- settings.clearSettings.clearPath = false;
- }
- }
-
- 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
- Pather.recursion && (Pather.currentWalkingPath = path);
-
- Pather.clearUIFlags();
-
- 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 = Pather.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.clearPath, settings.clearSettings.range] = [true, 5]);
- settings.retry <= 3 && !useTeleport && (settings.retry = 15);
- }
-
- if (useTeleport && tpMana <= me.mp
- ? Pather.teleportTo(node.x, node.y)
- : useChargedTele && (getDistance(me, node) >= 15 || me.inArea(sdk.areas.ThroneofDestruction))
- ? Pather.teleUsingCharges(node.x, node.y)
- : Pather.walkTo(node.x, node.y, (fail > 0 || me.inTown) ? 2 : 4)) {
- if (!me.inTown) {
- if (Pather.recursion) {
- try {
- Pather.recursion = false;
- NodeAction.go(settings.clearSettings);
- // need to determine if its worth going back to our orignal node (items maybe?)
- // vs our current proximity to our next node
- // need to export our main path so other functions that cause us to move can see it
- if (getDistance(me, node.x, node.y) > 5) {
- const lastNode = Pather.currentWalkingPath.last();
- // lets try and find the nearest node that brings us close to our goal
- let nearestNode = Pather.currentWalkingPath.length > 0 && Pather.currentWalkingPath
- .filter(el => !!el && el.x !== node.x && el.y !== node.y)
- .sort((a, b) => {
- if (a.distance < b.distance && getDistance(a, lastNode) < getDistance(b, lastNode)) return -1;
- if (a.distance > b.distance && getDistance(a, lastNode) > getDistance(b, lastNode)) return 1;
- return a.distance - b.distance;
- }).first();
- if (getDistance(me, node.x, node.y) < 40) {
- let goBack = false;
- // lets see if it's worth walking back to old node
- Pickit.checkSpotForItems(node, true) && (goBack = true);
- // @todo check shrines/chests in proximity to old node vs next node
- // let otherObjects = getUnits(sdk.unittype.Object).filter(el => getDistance());
- if (goBack) {
- console.debug("Going back to old node");
- } else if (nearestNode && nearestNode.distance > 5 && node.distance > 5 && 100 / node.distance * nearestNode.distance < 95) {
- console.debug("Moving to next node");
- let newIndex = path.findIndex(node => nearestNode.x === node.x && nearestNode.y === node.y);
- if (newIndex > -1) {
- console.debug("Found new path index: " + newIndex + " of currentPathLen: " + path.length);
- path = path.slice(newIndex);
- node = path.shift();
- } else {
- console.debug("Couldn't find new path index");
- }
- }
- node.distance > 5 && Pather.move(node, settings);
- } else {
- Pather.move(node, settings);
- }
- }
- } finally {
- Pather.recursion = true;
- }
- }
-
- settings.allowTown && Misc.townCheck();
- }
- } else {
- if (!me.inTown) {
- if (!useTeleport && settings.allowClearing
- && me.checkForMobs({range: (annoyingArea ? 5 : 10), coll: sdk.collision.BlockWalk}) && Attack.clear((annoyingArea ? 5 : 10))) {
- console.debug("Cleared Node");
- continue;
- }
- if (!useTeleport && (Pather.openDoors(node.x, node.y) || Pather.kickBarrels(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("moveTo: 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;
- }
- }
- if (fail > 100) {
- // why?
- console.debug(settings);
- }
- fail++;
- }
- }
-
- delay(5);
- }
-
- useTeleport && Config.TeleSwitch && me.switchWeapons(Attack.getPrimarySlot() ^ 1);
- PathDebug.removeHooks();
-
- return getDistance(me, node.x, node.y) < 5;
+ // 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({
+ canTele: false,
+ clearPath: false,
+ range: (
+ typeof Config.ClearPath.Range === "number" ? Config.ClearPath.Range : 10
+ ),
+ specType: (
+ typeof Config.ClearPath.Spectype === "number" ? Config.ClearPath.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);
+ !settings.allowPicking && (settings.clearSettings.allowPicking = false);
+
+ (target instanceof PresetUnit) && (target = target.realCoords());
+
+ if (settings.minDist > 3) {
+ target = Pather.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 cbCheck = false;
+ let node = { x: target.x, y: target.y };
+ const leaped = new PathAction();
+ const whirled = new PathAction();
+ const cleared = new PathAction();
+ const teleported = new PathAction();
+ const picked = new PathAction();
+
+ Pather.clearUIFlags();
+
+ if (typeof target.x !== "number" || typeof target.y !== "number") return false;
+ if (target.distance < 2 && !CollMap.checkColl(me, target, sdk.collision.BlockMissile, 5)) {
+ return true;
+ }
+
+ const useTeleport = (
+ settings.allowTeleport
+ && (target.distance > 15 || me.diff || me.act > 3)
+ && Pather.useTeleport()
+ );
+ settings.clearSettings.canTele = useTeleport;
+ const useChargedTele = settings.allowTeleport && Pather.canUseTeleCharges();
+ const usingTele = (useTeleport || useChargedTele);
+ const tpMana = Skill.getManaCost(sdk.skills.Teleport);
+ const annoyingArea = Pather.inAnnoyingArea(me.area);
+ let path = getPath(
+ me.area,
+ target.x, target.y,
+ me.x, me.y,
+ usingTele ? 1 : 0,
+ usingTele ? (annoyingArea ? 30 : Pather.teleDistance) : Pather.walkDistance
+ );
+ if (!path) throw new Error("move: Failed to generate path.");
+
+ // need to work on a better force clearing method but for now just have all walkers clear unless
+ // we specifically are forcing them not to (like while repositioning)
+ if (!useTeleport && settings.allowClearing && !settings.clearSettings.clearPath) {
+ settings.clearSettings.clearPath = true;
+ }
+
+ if (settings.retry <= 3 && target.distance > useTeleport ? 120 : 60) {
+ settings.retry = 10;
+ }
+
+ // for now only do this for teleporters
+ if (useTeleport && !me.normal) {
+ /** @type {Array} */
+ let areaImmunities = GameData.areaImmunities(me.area);
+ if (areaImmunities.length) {
+ let mySkElems = Config.AttackSkill
+ .filter(sk => sk > 0)
+ .map(sk => Attack.getSkillElement(sk));
+ // this area has monsters that are immune to our elements. This is a basic check for now
+ // a better way would probably be per list built to check the ratio of immunes to non?
+ if (mySkElems.length && mySkElems.every(elem => areaImmunities.includes(elem))) {
+ settings.clearSettings.clearPath = false;
+ }
+ } else if (AreaData.get(me.area).hasMonsterType(sdk.monsters.type.UndeadFetish)) {
+ settings.clearSettings.clearPath = false;
+ }
+ }
+
+ path.reverse();
+ settings.pop && path.pop();
+ PathDebug.drawPath(path);
+ if (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
+ if (Pather.recursion) {
+ Pather.currentWalkingPath = path;
+ PathDebug.drawPath(Pather.currentWalkingPath);
+ }
+ Pather.clearUIFlags();
+
+ /** @type {PathNode} */
+ node = path.shift();
+
+ if (typeof settings.callback === "function" && settings.callback()) {
+ cbCheck = true;
+ break;
+ }
+
+ if (getDistance(me, node) > 2) {
+ // Make life in Maggot Lair easier
+ if (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 = Pather.getNearestWalkable(node.x, node.y, 15, 3, sdk.collision.BlockWalk);
+
+ if (adjustedNode) {
+ [node.x, node.y] = adjustedNode;
+ invalidCheck && (invalidCheck = false);
+ }
+
+ annoyingArea && ([settings.clearSettings.clearPath, settings.clearSettings.range] = [true, 5]);
+ settings.retry <= 3 && !useTeleport && (settings.retry = 15);
+ }
+
+ if (useTeleport && tpMana <= me.mp
+ ? Pather.teleportTo(node.x, node.y)
+ : useChargedTele && (getDistance(me, node) >= 15 || me.inArea(sdk.areas.ThroneofDestruction))
+ ? Pather.teleUsingCharges(node.x, node.y)
+ : Pather.walkTo(node.x, node.y, (fail > 0 || me.inTown) ? 2 : 4)) {
+ if (settings.allowNodeActions && !me.inTown) {
+ if (Pather.recursion) {
+ try {
+ Pather.recursion = false;
+ /**
+ * @todo We need to pass our path in so we can fix the recursion issues of running forward on our path
+ * only to return to the old node and continue we should instead perform the actions in a way that moves
+ * us forward on our path ensuring we haven't skipped anything in the process as well
+ * for long paths maybe generate a coordinate list of shrines/chests and have action hooks for them
+ */
+ NodeAction.go(settings.clearSettings);
+ // need to determine if its worth going back to our orignal node (items maybe?)
+ // vs our current proximity to our next node
+ // need to export our main path so other functions that cause us to move can see it
+ if (node.distance > 5) {
+ const lastNode = Pather.currentWalkingPath.last();
+ // lets try and find the nearest node that brings us close to our goal
+ /** @type {PathNode} */
+ let nearestNode = Pather.currentWalkingPath.length > 0 && Pather.currentWalkingPath
+ .filter(function (el) {
+ return !!el && el.x !== node.x && el.y !== node.y;
+ })
+ .sort(function (a, b) {
+ const [aDist, bDist] = [a.distance, b.distance];
+ const [aLastDist, bLastDist] = [getDistance(a, lastNode), getDistance(b, lastNode)];
+ if (aDist < bDist && aLastDist < bLastDist) return -1;
+ if (aDist > bDist && aLastDist > bLastDist) return 1;
+ return aDist - bDist;
+ })
+ .find(function (pNode) {
+ return pNode.distance > 5;
+ });
+
+ if (node.distance < 40) {
+ let goBack = false;
+ // lets see if it's worth walking back to old node
+ Pickit.checkSpotForItems(node, true) && (goBack = true);
+ // @todo check shrines/chests in proximity to old node vs next node
+ if (goBack) {
+ // console.debug("Going back to old node. Distance: " + node.distance);
+ } else if (nearestNode && nearestNode.distance > 5 && node.distance > 5
+ && Math.percentDifference(node.distance, nearestNode.distance) > 5) {
+ let newIndex = path.findIndex(node => nearestNode.x === node.x && nearestNode.y === node.y);
+ if (newIndex > -1) {
+ path = path.slice(newIndex);
+ node = path.shift();
+ }
+ }
+
+ if (node.distance > 5) {
+ Pather.move(node, settings);
+ }
+ } else {
+ Pather.move(node, settings);
+ }
+ }
+ } finally {
+ Pather.recursion = true;
+ }
+ } else {
+ if (!me.inTown && settings.allowPicking) {
+ if (picked.node.distance > 10) {
+ Pickit.essessntialsPick(false);
+ picked.update(node);
+ }
+ }
+ }
+ }
+ } else {
+ if (!me.inTown) {
+ if (!useTeleport && (Pather.openDoors(node.x, node.y) || Pather.kickBarrels(node.x, node.y))) {
+ console.debug("Failed to walk to node, but opened door/barrel");
+ continue;
+ }
+
+ if (/* fail > 0 && */(!useTeleport || tpMana > me.mp)) {
+ // 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);
+ if (node.distance < 5) continue; // sucessfully cleared obstacle
+ }
+ }
+ }
+
+ /**
+ * 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);
+ if (node.distance < 5) continue; // sucessfully cleared obstacle
+ }
+ }
+ }
+
+ if (usingTele) {
+ if (teleported.at === 0 || getTickCount() - teleported.at > Time.seconds(3)
+ || teleported.node.distance > 5 || me.checkForMobs({ range: 6 })) {
+ // alright now if we have actually casted it set the values so we know
+ if (useTeleport ? Pather.teleportTo(node.x, node.y) : Pather.teleUsingCharges(node.x, node.y)) {
+ teleported.update(node);
+ if (node.distance < 5) continue; // sucessfully cleared obstacle
+ }
+ }
+ }
+
+ // 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.clearPos(
+ node.x, node.y, 10,
+ settings.allowPicking,
+ function () {
+ return node.distance < 5;
+ })) {
+ cleared.update(node);
+ if (node.distance < 5) continue; // sucessfully cleared obstacle
+ }
+ }
+ }
+ }
+ } else if (fail > 0 && me.inArea(sdk.areas.LutGholein) && me.x > 5122 && me.y <= 5049) {
+ // dislike have this here but handle atma blocking us from inside the tavern
+ if (me.inArea(sdk.areas.LutGholein) && 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]);
+ });
+ }
+ }
+ }
+
+ // 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("moveTo: 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;
+ }
+ }
+ if (fail > 100) {
+ // why?
+ console.debug(settings);
+ throw new Error("Retry limit excessivly exceeded");
+ }
+ fail++;
+ }
+ }
+
+ delay(5);
+ }
+
+ me.switchToPrimary();
+ PathDebug.removeHooks();
+
+ return cbCheck || getDistance(me, node.x, node.y) < 5;
};
Pather.moveNear = function (x, y, minDist, givenSettings = {}) {
- return Pather.move({ x: x, y: y }, Object.assign({ minDist: minDist }, givenSettings));
+ return Pather.move(
+ { x: x, y: y },
+ Object.assign({ minDist: minDist }, givenSettings)
+ );
};
-Pather.moveTo = function (x = undefined, y = undefined, retry = undefined, clearPath = true, pop = false) {
- return Pather.move({ x: x, y: y }, { retry: retry, pop: pop, clearSettings: { clearPath: clearPath } });
+Pather.moveTo = function (x, y, retry, clearPath = true, pop = false) {
+ return Pather.move(
+ { x: x, y: y },
+ { retry: retry, pop: pop, clearSettings: { clearPath: clearPath } }
+ );
};
Pather.moveToLoc = function (target, givenSettings = {}) {
- return Pather.move(target, givenSettings);
+ return Pather.move(target, givenSettings);
};
Pather.moveToEx = function (x, y, givenSettings = {}) {
- return Pather.move({ x: x, y: y }, givenSettings);
+ return Pather.move({ x: x, y: y }, givenSettings);
};
-// Add check in case "random" to return false if bot doesn't have cold plains wp yet
-Pather.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.includes(targetArea)) throw new Error("useWaypoint: Invalid area");
-
- break;
- }
-
- console.log("ÿc7Start ÿc8(useWaypoint) ÿc0:: ÿ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);
- i > 1 && checkCollision(me, wp, sdk.collision.Ranged) && Attack.getIntoPosition(wp, 20, sdk.collision.Ranged);
- 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)) {
- if (getUIFlag(sdk.uiflags.Waypoint)) {
- delay(500);
-
- switch (targetArea) {
- case "random":
- let retry = 0;
-
- while (true) {
- targetArea = this.nonTownWpAreas[rand(0, this.nonTownWpAreas.length - 1)];
-
- // get a valid wp, avoid towns
- if (getWaypoint(this.wpAreas.indexOf(targetArea))) {
- break;
- }
-
- // no valid areas, get the cold plains wp
- // maybe just walk out of town instead?
- if (retry >= 10) {
- if (!getWaypoint(this.wpAreas.indexOf(sdk.areas.ColdPlains)) && me.cancel()) {
- me.overhead("Trying to get the waypoint");
- if (this.getWP(sdk.areas.ColdPlains)) return true;
-
- throw new Error("Pather.useWaypoint: Failed to go to waypoint " + targetArea);
- }
- }
-
- retry++;
- delay(25);
- }
-
- break;
- case null:
- me.cancel();
-
- return true;
- }
-
- if (!getWaypoint(this.wpAreas.indexOf(targetArea)) && me.cancel()) {
- me.overhead("Trying to get the waypoint");
- if (this.getWP(targetArea)) return true;
-
- throw new Error("Pather.useWaypoint: Failed to go to waypoint " + 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);
- i > 1 && (i % 3) === 0 && Packet.flash(me.gid, pingDelay);
-
- continue;
- }
- }
-
- if (!check || getUIFlag(sdk.uiflags.Waypoint)) {
- delay(250);
- wp.interact(targetArea);
- let tick = getTickCount();
-
- while (getTickCount() - tick < Math.max(Math.round((i + 1) * 1000 / (i / 5 + 1)), pingDelay * 4)) {
- if (me.area === targetArea) {
- delay(1500);
- console.log("ÿc7End ÿc8(useWaypoint) ÿc0:: ÿc7targetArea: ÿc0" + this.getAreaName(targetArea) + " ÿc7myArea: ÿc0" + this.getAreaName(me.area) + "ÿc0 - ÿc7Duration: ÿc0" + (Time.format(getTickCount() - wpTick)));
-
- return true;
- }
-
- delay(30);
- }
-
- while (!me.gameReady) {
- delay(1000);
- }
-
- // In case lag causes the wp menu to stay open
- getUIFlag(sdk.uiflags.Waypoint) && me.cancel();
- }
-
- i > 1 && (i % 3) === 0 && 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(250);
- }
-
- if (me.area === targetArea) {
- delay(500);
- console.log("ÿc7End ÿc8(useWaypoint) ÿc0:: ÿc7targetArea: ÿc0" + this.getAreaName(targetArea) + " ÿc7myArea: ÿc0" + this.getAreaName(me.area) + "ÿc0 - ÿc7Duration: ÿc0" + (Time.format(getTickCount() - wpTick)));
-
- return true;
- }
+/**
+ * @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
+ */
+Pather.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));
+
+ /** @type {Array} */
+ const exits = AreaData.get(me.area).getExits();
+ // 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;
+ const node = { x: dest[0], y: dest[1] };
+
+ for (let retry = 0; retry < 3; retry++) {
+ if (this.move(node, 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.move({ x: targetRoom[0], y: 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);
+};
- throw new Error("useWaypoint: Failed to use waypoint to " + targetArea);
+// Add check in case "random" to return false if bot doesn't have cold plains wp yet
+Pather.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 (!AreaData.wps.has(targetArea)) throw new Error("useWaypoint: Invalid area");
+
+ break;
+ }
+
+ console.time("useWaypoint");
+
+ MainLoop:
+ for (let i = 0; i < 12; i++) {
+ if (me.area === targetArea || me.dead) {
+ break;
+ }
+
+ if (me.inTown) {
+ if (me.inArea(sdk.areas.LutGholein) && targetArea === sdk.areas.KurastDocktown) {
+ let npc = Game.getNPC(NPC.Meshif);
+
+ if (!!npc && npc.distance < 50) {
+ if (!Pather.changeAct(3)) throw new Error("Failed to go to act 3 using Meshif");
+ break;
+ }
+ } else if (me.inArea(sdk.areas.LutGholein)) {
+ let npc = Game.getNPC(NPC.Warriv);
+
+ if (!!npc && npc.distance < 50) {
+ if (!Pather.changeAct(1)) throw new Error("Failed to go to act 1 using Warriv");
+ }
+ } else if (me.inArea(sdk.areas.KurastDocktown) && targetArea === sdk.areas.LutGholein) {
+ let npc = Game.getNPC(NPC.Meshif);
+
+ if (!!npc && npc.distance < 50) {
+ if (!Pather.changeAct(2)) throw new Error("Failed to go to act 2 using Meshif");
+ break;
+ }
+ }
+
+ 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);
+ if (i > 1 && checkCollision(me, wp, sdk.collision.Ranged)) {
+ Attack.getIntoPosition(wp, 20, sdk.collision.Ranged);
+ }
+ 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)) {
+ if (getUIFlag(sdk.uiflags.Waypoint)) {
+ delay(500);
+
+ switch (targetArea) {
+ case "random":
+ let validWps = this.nonTownWpAreas
+ .filter(area => getWaypoint(this.wpAreas.indexOf(area)));
+ if (!validWps.length) {
+ if (me.inTown && Pather.moveToExit(me.area + 1, true)) {
+ break;
+ }
+ throw new Error("Pather.useWaypoint: Failed to go to waypoint " + targetArea);
+ }
+ targetArea = validWps.random();
+
+ break;
+ case null:
+ me.cancel();
+
+ return true;
+ }
+
+ if (!me.haveWaypoint(targetArea) && me.cancel()) {
+ me.overhead("Trying to get the waypoint");
+ if (this.getWP(targetArea)) return true;
+
+ throw new Error("Pather.useWaypoint: Failed to go to waypoint " + 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);
+ i > 1 && (i % 3) === 0 && Packet.flash(me.gid, pingDelay);
+
+ continue;
+ }
+ }
+
+ if (!check || getUIFlag(sdk.uiflags.Waypoint)) {
+ delay(250);
+ wp.interact(targetArea);
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < Math.max(Math.round((i + 1) * 1000 / (i / 5 + 1)), pingDelay * 4)) {
+ if (me.area === targetArea) {
+ delay(1500);
+
+ break MainLoop;
+ }
+
+ delay(30);
+ }
+
+ while (!me.gameReady) {
+ delay(1000);
+ }
+
+ // In case lag causes the wp menu to stay open
+ getUIFlag(sdk.uiflags.Waypoint) && me.cancel();
+ }
+
+ i > 1 && (i % 3) === 0 && 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(250);
+ }
+
+ if (me.area === targetArea) {
+ 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 to " + targetArea);
};
-// credit - Legacy Autosmurf
-Pather.clearToExit = function (currentarea, targetarea, cleartype = true) {
- let tick = getTickCount();
- let retry = 0;
- console.log("ÿc8Kolbot-SoloPlayÿc0: Start clearToExit. ÿc8Currently in: ÿc0" + Pather.getAreaName(me.area) + "ÿc8Clearing to: ÿc0" + Pather.getAreaName(targetarea));
-
- me.area !== currentarea && Pather.journeyTo(currentarea);
-
- if (me.area !== targetarea) {
- do {
- try {
- Pather.moveToExit(targetarea, true, cleartype);
- } catch (e) {
- console.debug("Caught Error: ", e.message ? e.message : e);
- }
-
- delay(500);
- Misc.poll(() => me.gameReady, 1000, 100);
-
- if (retry > 5) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: clearToExit. ÿc2Failed to move to: ÿc0" + Pather.getAreaName(targetarea));
-
- break;
- }
-
- retry++;
- } while (me.area !== targetarea);
- }
-
- console.log("ÿc8Kolbot-SoloPlayÿc0: End clearToExit. Time elapsed: " + Developer.formatTime(getTickCount() - tick));
- return (me.area === targetarea);
+/**
+ * @param {number} currentarea
+ * @param {number} targetarea
+ * @param {pathSettings} givenSettings
+ * @returns {boolean}
+ */
+Pather.clearToExit = function (currentarea, targetarea, givenSettings = {}) {
+ let retry = 0;
+ const targetName = getAreaName(targetarea);
+ console.info(true, getAreaName(me.area) + "ÿc8 --> ÿc0" + targetName, "clearToExit");
+
+ me.area !== currentarea && Pather.journeyTo(currentarea);
+
+ if (typeof givenSettings === "boolean") {
+ givenSettings = { allowClearing: givenSettings };
+ }
+
+ while (me.area !== targetarea) {
+ try {
+ Pather.moveToExit(targetarea, true, givenSettings);
+ } catch (e) {
+ console.error(e);
+ }
+
+ delay(500);
+ Misc.poll(function () {
+ return me.gameReady;
+ }, 1000, 100);
+
+ if (retry > 5) {
+ console.error("ÿc2Failed to move to: ÿc0" + targetName);
+
+ break;
+ }
+
+ retry++;
+ }
+
+ console.info(false, "", "clearToExit");
+ return (me.area === targetarea);
};
-Pather.getWalkDistance = function (x, y, area = me.area, xx = me.x, yy = me.y, reductionType = 2, radius = 5) {
- // distance between node x and x-1
- return (getPath(area, x, y, xx, yy, reductionType, radius) || [])
- .map((e, i, s) => i && getDistance(s[i - 1], e) || 0)
- .reduce((acc, cur) => acc + cur, 0) || Infinity;
+Pather.getWalkDistance = function (x, y, area, xx, yy, reductionType, radius) {
+ area === undefined && (area = me.area);
+ xx === undefined && (xx = me.x);
+ yy === undefined && (yy = me.y);
+ reductionType === undefined && (reductionType = 2);
+ radius === undefined && (radius = 5);
+ // distance between node x and x-1
+ return (getPath(area, x, y, xx, yy, reductionType, radius) || [])
+ .map(function (e, i, s) {
+ return i && getDistance(s[i - 1], e) || 0;
+ })
+ .reduce(function (acc, cur) {
+ return acc + cur;
+ }, 0) || Infinity;
};
diff --git a/libs/SoloPlay/Functions/PickitOverrides.js b/libs/SoloPlay/Functions/PickitOverrides.js
index 3d6df1ae..b4ba6bf6 100644
--- a/libs/SoloPlay/Functions/PickitOverrides.js
+++ b/libs/SoloPlay/Functions/PickitOverrides.js
@@ -6,7 +6,7 @@
*
*/
-includeIfNotIncluded("common/Pickit.js");
+includeIfNotIncluded("core/Pickit.js");
includeIfNotIncluded("SoloPlay/Functions/PrototypeOverrides.js");
includeIfNotIncluded("SoloPlay/Functions/NTIPOverrides.js");
includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
@@ -16,736 +16,862 @@ Pickit.Result.SOLOWANTS = 8;
Pickit.Result.SOLOSYSTEM = 9;
Pickit.minItemKeepGoldValue = function () {
- const myGold = me.gold;
- const cLvl = me.charlvl;
- switch (true) {
- case myGold < Math.min(Math.floor(500 + (cLvl * 100 * Math.sqrt(cLvl - 1))), 250000):
- return 10;
- case myGold < Math.min(Math.floor(500 + (cLvl * 250 * Math.sqrt(cLvl - 1))), 250000):
- return 50;
- case myGold < Math.min(Math.floor(500 + (cLvl * 500 * Math.sqrt(cLvl - 1))), 250000):
- return 500;
- default:
- return 1000;
- }
+ const myGold = me.gold;
+ const cLvl = me.charlvl;
+ switch (true) {
+ case myGold < Math.min(Math.floor(500 + (cLvl * 100 * Math.sqrt(cLvl - 1))), 250000):
+ return 10;
+ case myGold < Math.min(Math.floor(500 + (cLvl * 250 * Math.sqrt(cLvl - 1))), 250000):
+ return 50;
+ case myGold < Math.min(Math.floor(500 + (cLvl * 500 * Math.sqrt(cLvl - 1))), 250000):
+ return 500;
+ default:
+ return 1000;
+ }
};
+/**
+ * @constant
+ * This value never changes throughout the game
+ */
+Pickit.classicMode = me.classic;
+
+/**
+ * @param {ItemUnit} unit
+ */
Pickit.checkItem = function (unit) {
- const rval = NTIP.CheckItem(unit, false, true);
- const resultObj = (result, line = null) => ({
- result: result,
- line: line
- });
-
- // quick return on essentials - we know they aren't going to be in the other checks
- if (Pickit.essentials.includes(unit.itemType)) return rval;
-
- if ((unit.classid === sdk.items.runes.Ral || unit.classid === sdk.items.runes.Ort) && Town.repairIngredientCheck(unit)) {
- return resultObj(Pickit.Result.UTILITY);
- }
-
- if (CharData.skillData.bowData.bowOnSwitch) {
- if ([sdk.items.type.Bow, sdk.items.type.AmazonBow].includes(CharData.skillData.bowData.bowType) && unit.itemType === sdk.items.type.BowQuiver && Item.getQuantityOwned(unit, true) < 1) {
- return resultObj(Pickit.Result.SOLOWANTS, "Switch-Arrows");
- } else if (CharData.skillData.bowData.bowType === sdk.items.type.Crossbow && unit.itemType === sdk.items.type.CrossbowQuiver && Item.getQuantityOwned(unit, true) < 1) {
- return resultObj(Pickit.Result.SOLOWANTS, "Switch-Bolts");
- }
- }
-
- if (unit.classid === sdk.items.StaminaPotion && (me.charlvl < 18 || me.staminaPercent <= 85 || me.walking) && Item.getQuantityOwned(unit, true) < 2) {
- return resultObj(Pickit.Result.WANTED, "LowStamina");
- }
-
- if (unit.classid === sdk.items.AntidotePotion && me.getState(sdk.states.Poison) && Item.getQuantityOwned(unit, true) < 2) {
- return resultObj(Pickit.Result.WANTED, "Poisoned");
- }
-
- if (unit.classid === sdk.items.ThawingPotion && [sdk.states.Frozen, sdk.states.FrozenSolid].some(state => me.getState(state)) && Item.getQuantityOwned(unit, true) < 2) {
- return resultObj(Pickit.Result.WANTED, "Frozen");
- }
-
- if (rval.result === Pickit.Result.WANTED) {
- let durability = unit.getStat(sdk.stats.Durability);
-
- if (typeof durability === "number" && unit.getStat(sdk.stats.MaxDurability) > 0 && durability * 100 / unit.getStat(sdk.stats.MaxDurability) <= 0) {
- return resultObj(Pickit.Result.TRASH);
- }
- }
-
- if (SoloWants.checkItem(unit)) return resultObj(Pickit.Result.SOLOSYSTEM);
- 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 (AutoEquip.hasTier(unit) && !unit.identified) return resultObj(Pickit.Result.UNID);
-
- if (unit.isCharm/* && NTIP.GetCharmTier(unit) > 0 && unit.identified */) {
- if (Item.autoEquipCharmCheck(unit)) {
- return resultObj(Pickit.Result.SOLOWANTS, "Autoequip charm Tier: " + NTIP.GetCharmTier(unit));
- }
-
- return NTIP.CheckItem(unit, NTIP_CheckListNoTier, true);
- }
-
- if ((NTIP.GetMercTier(unit) > 0 || NTIP.GetTier(unit) > 0 || NTIP.GetSecondaryTier(unit) > 0) && unit.identified) {
- if (Item.autoEquipCheck(unit)) {
- return resultObj(Pickit.Result.SOLOWANTS, "Autoequip Tier: " + NTIP.GetTier(unit));
- }
-
- if (Item.autoEquipCheckMerc(unit)) {
- return resultObj(Pickit.Result.SOLOWANTS, "Autoequip MercTier: " + NTIP.GetMercTier(unit));
- }
-
- if (Item.autoEquipCheckSecondary(unit)) {
- return resultObj(Pickit.Result.SOLOWANTS, "Autoequip Secondary Tier: " + NTIP.GetSecondaryTier(unit));
- }
-
- return NTIP.CheckItem(unit, NTIP_CheckListNoTier, true);
- }
-
- if (rval.result === Pickit.Result.WANTED && unit.isBaseType) {
- if (NTIP.CheckItem(unit, NTIP.SoloCheckListNoTier)) {
- return resultObj(Pickit.Result.SOLOWANTS, "Base Type Item");
- }
- }
-
- // LowGold
- if (rval.result === Pickit.Result.UNWANTED && !Town.ignoredItemTypes.includes(unit.itemType) && !unit.questItem
- && (unit.isInInventory || (me.gold < Config.LowGold || me.gold < 500000))) {
- // Gold doesn't take up room, just pick it up
- if (unit.classid === sdk.items.Gold) return resultObj(Pickit.Result.TRASH);
-
- 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 Item: " + itemValue);
- } else if (itemValuePerSquare >= Pickit.minItemKeepGoldValue() && (me.gold < Config.LowGold || unit.isInInventory)) {
- // 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;
+ const rval = NTIP.CheckItem(unit, false, true);
+ const resultObj = function (result, line = null) {
+ return {
+ result: result,
+ line: line
+ };
+ };
+
+ // quick return on essentials - we know they aren't going to be in the other checks
+ if (Pickit.essentials.includes(unit.itemType)) return rval;
+
+ if (!Pickit.classicMode) {
+ if ([sdk.items.runes.Ral, sdk.items.runes.Ort].includes(unit.classid)
+ && Town.repairIngredientCheck(unit)) {
+ return resultObj(Pickit.Result.UTILITY);
+ }
+
+ /**
+ * Need to redo this
+ */
+ if (CharData.skillData.bow.onSwitch
+ && [sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver].includes(unit.itemType)
+ && rval === Pickit.Result.WANTED) {
+ if ([sdk.items.type.Bow, sdk.items.type.AmazonBow].includes(CharData.skillData.bow.bowType)
+ && unit.itemType === sdk.items.type.BowQuiver) {
+ return resultObj(Pickit.Result.SOLOWANTS, "Switch-Arrows");
+ } else if (CharData.skillData.bow.bowType === sdk.items.type.Crossbow
+ && unit.itemType === sdk.items.type.CrossbowQuiver) {
+ return resultObj(Pickit.Result.SOLOWANTS, "Switch-Bolts");
+ }
+ }
+ }
+
+ if (unit.classid === sdk.items.StaminaPotion
+ && (me.charlvl < 18 || me.staminaPercent <= 85 || me.walking)
+ && me.getOwned(unit, true).length < 2) {
+ return resultObj(Pickit.Result.WANTED, "LowStamina");
+ }
+
+ if (unit.classid === sdk.items.AntidotePotion
+ && me.getState(sdk.states.Poison)
+ && me.getOwned(unit, true).length < 2) {
+ return resultObj(Pickit.Result.WANTED, "Poisoned");
+ }
+
+ if (unit.classid === sdk.items.ThawingPotion
+ && (me.getState(sdk.states.Frozen) || me.getState(sdk.states.FrozenSolid))
+ && me.getOwned(unit, true).length < 2) {
+ return resultObj(Pickit.Result.WANTED, "Frozen");
+ }
+
+ if (rval.result === Pickit.Result.WANTED) {
+ if (unit.isBroken) {
+ return resultObj(Pickit.Result.TRASH);
+ }
+ }
+
+ if (SoloWants.checkItem(unit)) return resultObj(Pickit.Result.SOLOSYSTEM);
+ 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 (AutoEquip.hasTier(unit) && !unit.identified) return resultObj(Pickit.Result.UNID);
+
+ if (unit.isCharm/* && NTIP.GetCharmTier(unit) > 0 && unit.identified */) {
+ if (CharmEquip.check(unit)) {
+ return resultObj(Pickit.Result.SOLOWANTS, "Autoequip charm Tier: " + NTIP.GetCharmTier(unit));
+ }
+
+ return NTIP.CheckItem(unit, NTIP.CheckList, true);
+ }
+
+ if (AutoEquip.hasTier(unit) && unit.identified) {
+ if (Item.autoEquipCheck(unit)) {
+ return resultObj(Pickit.Result.SOLOWANTS, "Autoequip Tier: " + NTIP.GetTier(unit));
+ }
+
+ if (Item.autoEquipCheckMerc(unit)) {
+ return resultObj(Pickit.Result.SOLOWANTS, "Autoequip MercTier: " + NTIP.GetMercTier(unit));
+ }
+
+ if (Item.autoEquipCheckSecondary(unit)) {
+ return resultObj(Pickit.Result.SOLOWANTS, "Autoequip Secondary Tier: " + NTIP.GetSecondaryTier(unit));
+ }
+
+ return NTIP.CheckItem(unit, NTIP.CheckList, true);
+ }
+
+ if (rval.result === Pickit.Result.WANTED && unit.isBaseType) {
+ if (NTIP.CheckItem(unit, NTIP.NoTier)) {
+ return resultObj(Pickit.Result.SOLOWANTS, "Base Type Item");
+ }
+ }
+
+ // LowGold
+ if (rval.result === Pickit.Result.UNWANTED
+ && (!Town.ignoreType(unit.itemType) || me.data.level <= 3)
+ && !unit.questItem
+ && (unit.isInInventory || (me.gold < Config.LowGold || me.gold < 500000))) {
+ // Gold doesn't take up room, just pick it up
+ if (unit.classid === sdk.items.Gold) return resultObj(Pickit.Result.TRASH);
+
+ 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 Item: " + itemValue);
+ } else if (itemValuePerSquare >= Pickit.minItemKeepGoldValue()
+ && (me.gold < Config.LowGold || unit.isInInventory)) {
+ // 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;
};
// @jaenster
Pickit.amountOfPotsNeeded = function () {
- let _a, _b, _c, _d;
- let potTypes = [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion];
- let hpMax = (Array.isArray(Config.HPBuffer) ? Config.HPBuffer[1] : Config.HPBuffer);
- let mpMax = (Array.isArray(Config.MPBuffer) ? Config.MPBuffer[1] : Config.MPBuffer);
- let rvMax = (Array.isArray(Config.RejuvBuffer) ? Config.RejuvBuffer[1] : Config.RejuvBuffer);
- let needed = (_a = {},
- _a[sdk.items.type.HealingPotion] = (_b = {},
- _b[sdk.storage.Belt] = 0,
- _b[sdk.storage.Inventory] = hpMax,
- _b),
- _a[sdk.items.type.ManaPotion] = (_c = {},
- _c[sdk.storage.Belt] = 0,
- _c[sdk.storage.Inventory] = mpMax,
- _c),
- _a[sdk.items.type.RejuvPotion] = (_d = {},
- _d[sdk.storage.Belt] = 0,
- _d[sdk.storage.Inventory] = rvMax,
- _d),
- _a);
- if (hpMax > 0 || mpMax > 0 || rvMax > 0) {
- me.getItemsEx()
- .filter((pot) => potTypes.includes(pot.itemType) && (pot.isInBelt || pot.isInInventory))
- .forEach(function (pot) {
- needed[pot.itemType][pot.location] -= 1;
- });
- }
- let missing = Town.checkColumns(Pickit.beltSize);
- Config.BeltColumn.forEach(function (column, index) {
- if (column === "hp") {needed[sdk.items.type.HealingPotion][sdk.storage.Belt] = missing[index];}
- if (column === "mp") {needed[sdk.items.type.ManaPotion][sdk.storage.Belt] = missing[index];}
- if (column === "rv") {needed[sdk.items.type.RejuvPotion][sdk.storage.Belt] = missing[index];}
- });
- return needed;
+ /**
+ * @constructor
+ * @param {number} max
+ */
+ function NeededPots (max) {
+ this[sdk.storage.Belt] = 0;
+ this[sdk.storage.Inventory] = max;
+ }
+ let potTypes = [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion];
+ let hpMax = (Array.isArray(Config.HPBuffer) ? Config.HPBuffer[1] : Config.HPBuffer);
+ let mpMax = (Array.isArray(Config.MPBuffer) ? Config.MPBuffer[1] : Config.MPBuffer);
+ let rvMax = (Array.isArray(Config.RejuvBuffer) ? Config.RejuvBuffer[1] : Config.RejuvBuffer);
+ const needed = {};
+ needed[sdk.items.type.HealingPotion] = new NeededPots(hpMax);
+ needed[sdk.items.type.ManaPotion] = new NeededPots(mpMax);
+ needed[sdk.items.type.RejuvPotion] = new NeededPots(rvMax);
+ if (hpMax > 0 || mpMax > 0 || rvMax > 0) {
+ me.getItemsEx()
+ .filter(function (pot) {
+ return potTypes.includes(pot.itemType) && (pot.isInBelt || pot.isInInventory);
+ })
+ .forEach(function (pot) {
+ needed[pot.itemType][pot.location] -= 1;
+ });
+ }
+ let missing = Storage.Belt.checkColumns(Pickit.beltSize);
+ Config.BeltColumn.forEach(function (column, index) {
+ if (column === "hp") {
+ needed[sdk.items.type.HealingPotion][sdk.storage.Belt] = missing[index];
+ } else if (column === "mp") {
+ needed[sdk.items.type.ManaPotion][sdk.storage.Belt] = missing[index];
+ } else if (column === "rv") {
+ needed[sdk.items.type.RejuvPotion][sdk.storage.Belt] = missing[index];
+ }
+ });
+ return needed;
};
+/**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
Pickit.canFit = function (item) {
- switch (item.itemType) {
- case sdk.items.type.Gold:
- return true;
- case sdk.items.type.Scroll:
- {
- let tome = me.findItem(item.classid - 11, 0, sdk.storage.Inventory);
- return (tome && tome.getStat(sdk.stats.Quantity) < 20) || Storage.Inventory.CanFit(item);
- }
- case sdk.items.type.HealingPotion:
- case sdk.items.type.ManaPotion:
- case sdk.items.type.RejuvPotion:
- {
- let pots = this.amountOfPotsNeeded();
- if (pots[item.itemType][sdk.storage.Belt] > 0) {
- // this potion can go in belt
- return true;
- }
- }
- return Storage.Inventory.CanFit(item);
- default:
- return Storage.Inventory.CanFit(item);
- }
+ switch (item.itemType) {
+ case sdk.items.type.Gold:
+ return true;
+ case sdk.items.type.Scroll:
+ {
+ let tome = me.findItem(item.classid - 11, 0, sdk.storage.Inventory);
+ return (tome && tome.getStat(sdk.stats.Quantity) < 20) || Storage.Inventory.CanFit(item);
+ }
+ case sdk.items.type.HealingPotion:
+ case sdk.items.type.ManaPotion:
+ case sdk.items.type.RejuvPotion:
+ {
+ let pots = this.amountOfPotsNeeded();
+ if (pots[item.itemType][sdk.storage.Belt] > 0) {
+ // this potion can go in belt
+ return true;
+ }
+ }
+ return Storage.Inventory.CanFit(item);
+ default:
+ return Storage.Inventory.CanFit(item);
+ }
};
+/**
+ * @param {ItemUnit} unit
+ * @returns {boolean}
+ */
Pickit.canPick = function (unit) {
- if (!unit) return false;
- if (sdk.quest.items.includes(unit.classid) && me.getItem(unit.classid)) return false;
-
- // TODO: clean this up
-
- let tome, charm, i, potion, needPots, buffers, pottype, myKey, key;
-
- switch (unit.itemType) {
- case sdk.items.type.Gold:
- // Check current gold vs max capacity (cLvl*10000) and skip if full
- return (me.getStat(sdk.stats.Gold) < me.getStat(sdk.stats.Level) * 10000);
- case sdk.items.type.Scroll:
- // 518 - Tome of Town Portal or 519 - Tome of Identify, mode 0 - inventory/stash
- tome = me.getItem(unit.classid - 11, sdk.items.mode.inStorage);
-
- if (tome) {
- do {
- if (tome.isInInventory && tome.getStat(sdk.stats.Quantity) === 20) {
- return false; // Skip a scroll if its tome is full
- }
- } while (tome.getNext());
- } else {
- // If we don't have a tome, go ahead and keep 2 scrolls
- return unit.classid === sdk.items.ScrollofIdentify && me.charlvl > 5 ? false : me.getItemsEx(unit.classid).filter(el => el.isInInventory).length < 2;
- }
-
- 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) {
- 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 (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());
- }
-
- // re-do this to pick items to cursor if we don't want them in our belt then place them in invo
- let beltCheck = this.checkBelt();
- if (needPots < 1) {
- buffers = ["HPBuffer", "MPBuffer", "RejuvBuffer"];
-
- for (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());
- }
- }
-
- needPots > 0 && !beltCheck && Pickit.toCursorPick.push(unit.gid);
- }
- }
- }
-
- 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;
+ if (!unit) return false;
+ if (sdk.quest.items.includes(unit.classid) && me.getItem(unit.classid)) return false;
+
+ // TODO: clean this up
+
+ let tome, charm, myKey, key;
+
+ switch (unit.itemType) {
+ case sdk.items.type.Gold:
+ // Check current gold vs max capacity (cLvl*10000) and skip if full
+ return (me.getStat(sdk.stats.Gold) < me.getStat(sdk.stats.Level) * 10000);
+ case sdk.items.type.Scroll:
+ // 518 - Tome of Town Portal or 519 - Tome of Identify, mode 0 - inventory/stash
+ tome = me.getItem(unit.classid - 11, sdk.items.mode.inStorage);
+
+ if (tome) {
+ do {
+ if (tome.isInInventory && tome.getStat(sdk.stats.Quantity) === 20) {
+ return false; // Skip a scroll if its tome is full
+ }
+ } while (tome.getNext());
+ } else {
+ // If we don't have a tome, go ahead and keep 2 scrolls
+ return unit.classid === sdk.items.ScrollofIdentify && me.charlvl > 5
+ ? false
+ : me.getItemsEx(unit.classid).filter(el => el.isInInventory).length < 2;
+ }
+
+ 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) {
+ 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.includes(column)) {
+ needPots += this.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());
+ }
+
+ // re-do this to pick items to cursor if we don't want them in our belt then place them in invo
+ let beltCheck = this.checkBelt();
+ if (needPots < 1) {
+ 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) {
+ if (!Storage.Inventory.CanFit(unit)) return false;
+ 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 > 0) {
+ !beltCheck && _toCursorPick.add(unit.gid);
+ } else {
+ if (_pots.get(unit.itemType).count < 8
+ && Storage.Inventory.CanFit(unit)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ 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) || (me.charlvl < 10 && Storage.Inventory.CanFit(unit));
+ case undefined: // Yes, it does happen
+ console.warn("undefined item (!?)");
+
+ return false;
+ }
+
+ return true;
};
-Pickit.toCursorPick = [];
-
-Pickit.pickItem = function (unit, status, keptLine, clearBeforePick = true) {
- function ItemStats (unit) {
- let self = this;
- self.x = unit.x;
- self.y = unit.y;
- self.ilvl = unit.ilvl;
- self.sockets = unit.sockets;
- self.type = unit.itemType;
- self.classid = unit.classid;
- self.name = unit.name;
- self.color = Pickit.itemColor(unit);
- self.gold = unit.getStat(sdk.stats.Gold);
- self.dist = (unit.distance || Infinity);
- let canTk = (Skill.haveTK && Pickit.tkable.includes(self.type) && Pickit.toCursorPick.indexOf(unit.gid) === -1
- && self.dist > 5 && self.dist < 20 && !checkCollision(me, unit, sdk.collision.WallOrRanged));
- self.useTk = canTk && (me.mpPercent > 50);
- self.picked = false;
- }
-
- let item, tick, gid, retry = false;
- const itemCount = me.itemcount;
- const cancelFlags = [sdk.uiflags.Inventory, sdk.uiflags.NPCMenu, sdk.uiflags.Waypoint, sdk.uiflags.Shop, sdk.uiflags.Stash, sdk.uiflags.Cube];
-
- if (!unit || unit === undefined) return false;
-
- if (unit.gid) {
- gid = unit.gid;
- item = Game.getItem(-1, -1, gid);
- }
-
- if (!item) return false;
-
- for (let i = 0; i < cancelFlags.length; i += 1) {
- if (getUIFlag(cancelFlags[i])) {
- delay(500);
- me.cancel(0);
-
- break;
- }
- }
-
- const stats = new ItemStats(item);
- const tkMana = stats.useTk ? Skill.getManaCost(sdk.skills.Telekinesis) * 2 : Infinity;
-
- MainLoop:
- for (let i = 0; i < 3; i += 1) {
- if (me.dead) return false;
- if (!Game.getItem(-1, -1, gid)) {
- break;
- }
-
- while (!me.idle) {
- delay(40);
- }
-
- if (!item.onGroundOrDropping) {
- break;
- }
-
- let itemDist = item.distance;
- // todo - allow picking near potions/scrolls while attacking distance < 5
- if (stats.useTk && me.mp > tkMana) {
- Packet.telekinesis(item);
- } else {
- let checkItem = false;
- const maxDist = (Config.FastPick || i < 1) ? 8 : 5;
- if (item.distance > maxDist || checkCollision(me, item, sdk.collision.BlockWall)) {
- if (!clearBeforePick && me.checkForMobs({range: 5, coll: (sdk.collision.BlockWall | sdk.collision.Objects | sdk.collision.ClosedDoor)})) {
- continue;
- }
-
- if (clearBeforePick && item.checkForMobs({range: 8, coll: (sdk.collision.BlockWall | sdk.collision.Objects | sdk.collision.ClosedDoor)})) {
- try {
- console.log("ÿc8PickItemÿc0 :: Clearing area around item I want to pick");
- Pickit.enabled = false; // Don't pick while trying to clear
- Attack.clearPos(item.x, item.y, 10, false);
- } finally {
- Pickit.enabled = true; // Reset value
- }
- }
- checkItem = true;
- }
-
- if (checkItem || i > 0) {
- if (copyUnit(item).x === undefined || !item.onGroundOrDropping) {
- break;
- }
- if (!Pather.moveNearUnit({ x: stats.x, y: stats.y }, 5)) continue;
- }
-
- let cursorUnit;
- itemDist = item.distance;
- // use packet first, if we fail and not using fast pick use click
- Pickit.toCursorPick.includes(item.gid)
- ? Packet.click(item, true) && (cursorUnit = Misc.poll(() => Game.getCursorUnit(), (itemDist > 10 ? 1000 : 250), 50)) && Storage.Inventory.MoveTo(cursorUnit)
- : (Config.FastPick || i < 1) ? Packet.click(item) : Misc.click(0, 0, item);
- }
-
- tick = getTickCount();
-
- while (getTickCount() - tick < (itemDist > 10 ? 2000 : 1000)) {
- item = copyUnit(item);
- Pickit.toCursorPick.includes(item.gid) && Pickit.toCursorPick.remove(item.gid);
-
- 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;
- case sdk.items.Arrows:
- case sdk.items.Bolts:
- me.needRepair();
-
- break;
- }
-
- me.itemoncursor && Storage.Inventory.MoveTo(Game.getCursorUnit());
-
- break MainLoop;
- }
-
- delay(20);
- }
-
- // TK failed, disable it
- stats.useTk = false;
-
- //console.log("pick retry");
- }
-
- if (retry) return this.pickItem(unit, status, keptLine);
-
- stats.picked = me.itemcount > itemCount || !!me.getItem(-1, -1, gid);
-
- if (stats.picked) {
- DataFile.updateStats("lastArea");
-
- switch (status) {
- case Pickit.Result.WANTED:
- case Pickit.Result.SOLOWANTS:
- console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + (stats.sockets > 0 ? ") (sockets " + stats.sockets : "") + (keptLine ? ") (" + keptLine + ")" : ")"));
-
- if (this.ignoreLog.indexOf(stats.type) === -1) {
- Misc.itemLogger("Kept", item);
- Misc.logItem("Kept", item, keptLine);
- }
-
- if (item.identified && item.isInInventory && AutoEquip.wanted(item)) {
- ((Item.autoEquipCheck(item) && Item.autoEquip("Field")) || (Item.autoEquipCheckSecondary(item) && Item.autoEquipSecondary("Field")));
- }
-
- 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;
- case Pickit.Result.SOLOSYSTEM:
- console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + ")" + " (SoloWants System)");
- SoloWants.update(item);
-
- break;
- default:
- console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + (keptLine ? ") (" + keptLine + ")" : ")"));
-
- break;
- }
- }
+/** @type {Set} */
+const _toCursorPick = new Set();
- return true;
+/**
+ * @override
+ * @param {ItemUnit} unit
+ * @param {PickitResult} status
+ * @param {string} keptLine
+ * @param {{ allowClear: boolean, allowMove: boolean }} givenSettings
+ */
+Pickit.pickItem = function (unit, status, keptLine, givenSettings) {
+ if (!unit || unit === undefined) return false;
+ const _pickSettings = Object.assign({
+ allowClear: true,
+ allowMove: true
+ }, givenSettings);
+ /**
+ * @constructor
+ * @param {ItemUnit} unit
+ */
+ function ItemStats (unit) {
+ let self = this;
+ self.x = unit.x;
+ self.y = unit.y;
+ self.ilvl = unit.ilvl;
+ self.sockets = unit.sockets;
+ self.type = unit.itemType;
+ self.classid = unit.classid;
+ self.name = unit.name;
+ self.color = Item.color(unit);
+ self.gold = unit.getStat(sdk.stats.Gold);
+ self.dist = (unit.distance || Infinity);
+ let canTk = (
+ Skill.haveTK && Pickit.tkable.includes(self.type)
+ && !_toCursorPick.has(unit.gid)
+ && self.dist > 5 && self.dist < 20
+ && !checkCollision(me, unit, sdk.collision.WallOrRanged)
+ );
+ self.useTk = canTk && (me.mpPercent > 50);
+ self.picked = false;
+ }
+
+ const itemCount = me.itemcount;
+ const cancelFlags = [
+ sdk.uiflags.Inventory, sdk.uiflags.NPCMenu,
+ sdk.uiflags.Waypoint, sdk.uiflags.Shop,
+ sdk.uiflags.Stash, sdk.uiflags.Cube
+ ];
+ const gid = unit.gid;
+
+ let item = Game.getItem(-1, -1, gid);
+ if (!item) return false;
+
+ if (cancelFlags.some(function (flag) { return getUIFlag(flag); })) {
+ delay(500);
+ me.cancel(0);
+ }
+
+ let retry = false;
+ const stats = new ItemStats(item);
+ const tkMana = stats.useTk
+ ? Skill.getManaCost(sdk.skills.Telekinesis) * 2
+ : Infinity;
+
+ MainLoop:
+ for (let i = 0; i < 3; i += 1) {
+ if (me.dead) return false;
+ if (!Game.getItem(-1, -1, gid)) {
+ break;
+ }
+
+ while (!me.idle) {
+ delay(40);
+ }
+
+ if (!item.onGroundOrDropping) {
+ break;
+ }
+
+ let itemDist = item.distance;
+ // todo - allow picking near potions/scrolls while attacking distance < 5
+ if (stats.useTk && me.mp > tkMana) {
+ Packet.telekinesis(item);
+ } else {
+ let checkItem = false;
+ const maxDist = (Config.FastPick || i < 1) ? 8 : 5;
+ if (_pickSettings.allowMove
+ && item.distance > maxDist || checkCollision(me, item, sdk.collision.BlockWall)) {
+ let coll = (sdk.collision.BlockWall | sdk.collision.Objects | sdk.collision.ClosedDoor);
+
+ if (!_pickSettings.allowClear && me.checkForMobs({ range: 5, coll: coll })) {
+ continue;
+ }
+
+ if (_pickSettings.allowClear && item.checkForMobs({ range: 8, coll: coll })) {
+ try {
+ console.log("ÿc8PickItemÿc0 :: Clearing area around item I want to pick");
+ Pickit.enabled = false; // Don't pick while trying to clear
+ Attack.clearPos(item.x, item.y, 10, false, function () {
+ if (!copyUnit(item).x) return true;
+ if (!item.onGroundOrDropping || me.getItem(-1, -1, gid)) return true;
+ return !item.checkForMobs({ range: 8, coll: coll });
+ });
+ } finally {
+ Pickit.enabled = true; // Reset value
+ }
+ }
+ checkItem = true;
+ }
+
+ if (checkItem || i > 0) {
+ if (copyUnit(item).x === undefined || !item.onGroundOrDropping) {
+ break;
+ }
+ if (!Pather.moveNearUnit({ x: stats.x, y: stats.y }, 5)) continue;
+ }
+
+ let cursorUnit;
+ itemDist = item.distance;
+ // use packet first, if we fail and not using fast pick use click
+ _toCursorPick.has(item.gid)
+ ? Packet.click(item, true)
+ && (cursorUnit = Misc.poll(function () {
+ return Game.getCursorUnit();
+ }, (itemDist > 10 ? 1000 : 250), 50))
+ && Storage.Inventory.MoveTo(cursorUnit)
+ : (Config.FastPick || i < 1)
+ ? Packet.click(item)
+ : Misc.click(0, 0, item);
+ }
+
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < (itemDist > 10 ? 2000 : 1000)) {
+ item = copyUnit(item);
+ _toCursorPick.has(item.gid) && _toCursorPick.delete(item.gid);
+
+ 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;
+ case sdk.items.Arrows:
+ case sdk.items.Bolts:
+ me.needRepair();
+
+ break;
+ }
+
+ me.itemoncursor && Storage.Inventory.MoveTo(Game.getCursorUnit());
+
+ break MainLoop;
+ }
+
+ delay(20);
+ }
+
+ // TK failed, disable it
+ stats.useTk = false;
+
+ //console.log("pick retry");
+ }
+
+ if (retry) return this.pickItem(unit, status, keptLine);
+
+ stats.picked = me.itemcount > itemCount || !!me.getItem(-1, -1, gid);
+
+ if (stats.picked) {
+ DataFile.updateStats("lastArea");
+
+ switch (status) {
+ case Pickit.Result.WANTED:
+ case Pickit.Result.SOLOWANTS:
+ console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + (stats.sockets > 0 ? ") (sockets " + stats.sockets : "") + (keptLine ? ") (" + keptLine + ")" : ")"));
+
+ if (this.ignoreLog.indexOf(stats.type) === -1) {
+ Item.logger("Kept", item);
+ Item.logItem("Kept", item, keptLine);
+ }
+
+ if (item.identified && item.isInInventory && AutoEquip.wanted(item)) {
+ ((Item.autoEquipCheck(item) && Item.autoEquip("Field")) || (Item.autoEquipCheckSecondary(item) && Item.autoEquipSecondary("Field")));
+ }
+
+ break;
+ case Pickit.Result.CUBING:
+ console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + ")" + " (Cubing)");
+ Item.logger("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)");
+ Item.logger("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;
+ case Pickit.Result.SOLOSYSTEM:
+ console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + ")" + " (SoloWants System)");
+ SoloWants.update(item);
+
+ break;
+ default:
+ console.log("ÿc7Picked up " + stats.color + stats.name + " ÿc0(ilvl " + stats.ilvl + (keptLine ? ") (" + keptLine + ")" : ")"));
+
+ break;
+ }
+ }
+
+ return true;
};
Pickit.checkSpotForItems = function (spot, checkVsMyDist = false, range = Config.PickRange) {
- if (spot.x === undefined) return false;
- let itemList = [];
- let item = Game.getItem();
-
- if (item) {
- do {
- if (item.onGroundOrDropping && getDistance(spot, item) <= range) {
- const spotDist = getDistance(spot, item);
- const itemDistFromMe = item.distance;
- if (Pickit.essentials.includes(item.itemType)) {
- if (Pickit.checkItem(item).result && Pickit.canPick(item) && Pickit.canFit(item)) {
- checkVsMyDist && itemDistFromMe < spotDist ? Pickit.essentials.push(copyUnit(item)) : itemList.push(copyUnit(item));
- }
- } else if (item.itemType === sdk.items.type.Key) {
- if (Pickit.canPick(item) && Pickit.checkItem(item).result) {
- checkVsMyDist && itemDistFromMe < spotDist ? Pickit.pickList.push(copyUnit(item)) : itemList.push(copyUnit(item));
- }
- } else if (Pickit.checkItem(item).result) {
- if (checkVsMyDist && itemDistFromMe < spotDist) {
- Pickit.pickList.push(copyUnit(item));
- } else {
- return true;
- }
- }
- }
- } while (item.getNext());
- }
-
- return itemList.length > 3;
+ if (spot.x === undefined) return false;
+ let itemList = [];
+ let item = Game.getItem();
+
+ if (item) {
+ do {
+ if (item.onGroundOrDropping && getDistance(spot, item) <= range) {
+ const spotDist = getDistance(spot, item);
+ const itemDistFromMe = item.distance;
+ if (Pickit.essentials.includes(item.itemType)) {
+ if (Pickit.checkItem(item).result && Pickit.canPick(item) && Pickit.canFit(item)) {
+ checkVsMyDist && itemDistFromMe < spotDist
+ ? Pickit.essentials.push(copyUnit(item))
+ : itemList.push(copyUnit(item));
+ }
+ } else if (item.itemType === sdk.items.type.Key) {
+ if (Pickit.canPick(item) && Pickit.checkItem(item).result) {
+ checkVsMyDist && itemDistFromMe < spotDist
+ ? Pickit.pickList.push(copyUnit(item))
+ : itemList.push(copyUnit(item));
+ }
+ } else if (Pickit.checkItem(item).result) {
+ if (checkVsMyDist && itemDistFromMe < spotDist) {
+ Pickit.pickList.push(copyUnit(item));
+ } else {
+ return true;
+ }
+ }
+ }
+ } while (item.getNext());
+ }
+
+ return itemList.length > 3;
};
+/** @type {ItemUnit[]} */
Pickit.pickList = [];
Pickit.essentialList = [];
// Might need to do a global list so this function and pickItems see the same items to prevent an item from being in both
-Pickit.essessntialsPick = function (clearBeforePick = false, ignoreGold = false, builtList = [], once = false) {
- if (me.dead || me.inTown || (!Pickit.enabled && !clearBeforePick)) return false;
-
- Pickit.essentialList.concat(builtList, Pickit.pickList).filter(i => !!i && Pickit.essentials.includes(i.itemType));
- let item = Game.getItem();
- const maxDist = Skill.haveTK ? 15 : 5;
-
- if (item) {
- do {
- if (item.onGroundOrDropping && getDistance(me, item) <= maxDist && Pickit.essentials.includes(item.itemType)) {
- if (Pickit.essentialList.some(el => el.gid === item.gid)) continue;
- if (item.itemType !== sdk.items.type.Gold || getDistance(me, item) < 5) {
- Pickit.essentialList.push(copyUnit(item));
- }
- }
- } while (item.getNext());
- }
-
- if (!Pickit.essentialList.length) return true;
-
- while (!me.idle) {
- delay(40);
- }
-
- while (Pickit.essentialList.length > 0) {
- if (me.dead || !Pickit.enabled) return false;
-
- Pickit.essentialList.sort(this.sortItems);
- const currItem = Pickit.essentialList[0];
-
- // 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(currItem).x !== undefined && currItem.onGroundOrDropping
- && (Pather.useTeleport() || !checkCollision(me, currItem, sdk.collision.BlockWall))) {
- // Check if the item should be picked
- let status = this.checkItem(currItem);
-
- if (status.result && Pickit.canPick(currItem)) {
- let canFit = (Storage.Inventory.CanFit(currItem) || Pickit.canFit(currItem));
-
- // 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 = (currItem.gid !== undefined && Storage.Inventory.CanFit(currItem)));
- }
-
- // 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 " + this.itemColor(currItem) + currItem.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.essessntialsPick(clearBeforePick, ignoreGold, builtList, once);
- }
-
- // Town visit failed - abort
- console.log("ÿc7Not enough room for " + this.itemColor(currItem) + currItem.name);
-
- return false;
- }
-
- // Can't make room
- Misc.itemLogger("No room for", currItem);
- console.log("ÿc7Not enough room for " + this.itemColor(currItem) + currItem.name);
- }
-
- // Item can fit - pick it up
- if (canFit) {
- let picked = this.pickItem(currItem, status.result, status.line, clearBeforePick);
- if (picked && once) return true;
- }
- }
- }
-
- Pickit.essentialList.shift();
- }
-
- return true;
+Pickit.essessntialsPick = function (clearBeforePick = false, builtList = [], once = false) {
+ if (me.dead || me.inTown || (!Pickit.enabled && !clearBeforePick)) return false;
+
+ Pickit.essentialList
+ .concat(builtList, Pickit.pickList)
+ .filter(function (i) {
+ return !!i && Pickit.essentials.includes(i.itemType);
+ });
+ let item = Game.getItem();
+ const maxDist = Skill.haveTK ? 15 : 5;
+
+ if (item) {
+ do {
+ if (item.onGroundOrDropping
+ && item.distance <= maxDist
+ && Pickit.essentials.includes(item.itemType)) {
+ if (Pickit.essentialList.some(el => el.gid === item.gid)) continue;
+ if (item.itemType !== sdk.items.type.Gold || item.distance < 5) {
+ Pickit.essentialList.push(copyUnit(item));
+ }
+ }
+ } while (item.getNext());
+ }
+
+ if (!Pickit.essentialList.length) return true;
+
+ while (Pickit.essentialList.length > 0) {
+ if (me.dead || !Pickit.enabled) return false;
+
+ Pickit.essentialList.sort(this.sortItems);
+ const currItem = Pickit.essentialList[0];
+
+ // 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(currItem).x !== undefined && currItem.onGroundOrDropping
+ && (Pather.useTeleport() || !checkCollision(me, currItem, sdk.collision.BlockWall))) {
+ // Check if the item should be picked
+ let status = this.checkItem(currItem);
+
+ if (status.result && Pickit.canPick(currItem)) {
+ let canFit = (Storage.Inventory.CanFit(currItem) || Pickit.canFit(currItem));
+
+ // Field id when our used space is above a certain percent or if we are full try to make room with FieldID
+ // or if we have an exp shrine so we don't waste it
+ if ((Config.FieldID.Enabled || me.getState(sdk.states.ShrineExperience))
+ && (!canFit || Storage.Inventory.UsedSpacePercent() > Config.FieldID.UsedSpace)) {
+ me.fieldID() && (canFit = (currItem.gid !== undefined && Storage.Inventory.CanFit(currItem)));
+ }
+
+ // Item can fit - pick it up
+ if (canFit) {
+ let picked = this.pickItem(
+ currItem,
+ status.result,
+ status.line + "(essentials)",
+ { allowClear: clearBeforePick }
+ );
+ if (picked && once) return true;
+ }
+ }
+ }
+
+ Pickit.essentialList.shift();
+ }
+
+ return true;
};
Pickit.pickItems = function (range = Config.PickRange, once = false) {
- if (me.dead || range < 0 || !Pickit.enabled) return false;
-
- let status, canFit;
- let needMule = false;
-
- while (!me.idle) {
- delay(40);
- }
-
- let item = Game.getItem();
-
- if (item) {
- do {
- if (Pickit.pickList.some(el => el.gid === item.gid)) continue;
- if (item.onGroundOrDropping && getDistance(me, item) <= range) {
- Pickit.pickList.push(copyUnit(item));
- }
- } while (item.getNext());
- }
-
- if (Pickit.pickList.some(i => [sdk.items.type.HealingPotion, sdk.items.type.ManaPotion, sdk.items.type.RejuvPotion].includes(i.itemType))) {
- Town.clearBelt();
- Pickit.beltSize = Storage.BeltSize();
- }
-
- while (Pickit.pickList.length > 0) {
- if (me.dead || !Pickit.enabled) return false;
-
- Pickit.pickList.sort(this.sortItems);
- const currItem = Pickit.pickList[0];
-
- // 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(currItem).x !== undefined && currItem.onGroundOrDropping
- && (Pather.useTeleport() || me.inTown || !checkCollision(me, currItem, sdk.collision.BlockWall))) {
- // Check if the item should be picked
- status = this.checkItem(currItem);
-
- if (status.result && Pickit.canPick(currItem)) {
- canFit = (Storage.Inventory.CanFit(currItem) || Pickit.canFit(currItem));
-
- // 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 = (currItem.gid !== undefined && Storage.Inventory.CanFit(currItem)));
- }
-
- // 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 " + this.itemColor(currItem) + currItem.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(range, once);
- }
-
- // Town visit failed - abort
- console.log("ÿc7Not enough room for " + this.itemColor(currItem) + currItem.name);
-
- return false;
- }
-
- // Can't make room - trigger automule
- Misc.itemLogger("No room for", currItem);
- console.log("ÿc7Not enough room for " + this.itemColor(currItem) + currItem.name);
-
- needMule = true;
- }
-
- // Item can fit - pick it up
- if (canFit) {
- let picked = this.pickItem(currItem, status.result, status.line);
- if (picked && once) return true;
- }
- }
- }
-
- Pickit.pickList.shift();
- }
-
- // 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;
+ if (me.dead || range < 0 || !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];
+
+ let item = Game.getItem();
+
+ if (item) {
+ do {
+ if (Pickit.ignoreList.has(item.gid)) continue;
+ if (Pickit.pickList.some(el => el.gid === item.gid)) continue;
+ if (item.onGroundOrDropping && item.distance <= range) {
+ Pickit.pickList.push(copyUnit(item));
+ }
+ } while (item.getNext());
+ }
+
+ if (Pickit.pickList.some(function (el) {
+ return _pots.includes(el.itemType);
+ })) {
+ me.clearBelt();
+ Pickit.beltSize = Storage.BeltSize();
+ }
+
+ while (Pickit.pickList.length > 0) {
+ if (me.dead || !Pickit.enabled) 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(-1, -1, currItem.gid);
+ if (!_item || copyUnit(_item).x === undefined) {
+ Pickit.pickList.shift();
+
+ continue;
+ }
+
+ // 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 && Pickit.canPick(_item)) {
+ let canFit = (Storage.Inventory.CanFit(_item) || Pickit.canFit(_item));
+
+ // 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 (!canFit && !me.checkForMobs({ range: 10 })) {
+ me.sortInventory();
+ canFit = (Storage.Inventory.CanFit(_item) || Pickit.canFit(_item));
+ }
+
+ // 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()) {
+ // if we are going to have to go to town anyway then grab what is near us
+ Pickit.pickList
+ .filter(function (el) {
+ return el.distance <= 5 && (Storage.Inventory.CanFit(el) || Pickit.canFit(el));
+ }).forEach(function (el) {
+ let _result = Pickit.checkItem(el);
+ return Pickit.pickItem(el, _result.result, _result.line + "(quick)", { allowMove: false });
+ });
+ console.log("ÿc7Trying to make room for " + Item.color(_item) + _item.name);
+
+ /**
+ * @todo
+ * - Try to sort inventory if it is safe to do so
+ * - Check to see if we can clear up enough buffer potions (exlcuding rejuvs) in order to pick the item
+ * - If all this fails after visiting town to clear inventory and stash items then globally ignore this item
+ * and potentially any other items it's size until our used space changes. Only way we should get to this point
+ * is the use of an additonal pickit file without muling setup.
+ */
+
+ // 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.
+ Pickit.ignoreList.clear(); // reset the list of ignored gids
+ return this.pickItems(range, once);
+ }
+
+ // 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);
+ // ignore the item now
+ Pickit.ignoreList.add(_item.gid);
+ needMule = true;
+
+ break;
+ }
+ }
+
+ // Item can fit - pick it up
+ if (canFit) {
+ let picked = this.pickItem(_item, status.result, status.line);
+ if (picked && once) return true;
+ }
+ }
+ }
+
+ Pickit.pickList.shift();
+ }
+
+ // Quit current game and transfer the items to mule
+ if (needMule && canUseMule && AutoMule.getMuleItems().length > 0) {
+ console.log(
+ "ÿc7Muling items :: \n"
+ + "- ÿc7UsedStashSpacePercentÿc0: " + Storage.Stash.UsedSpacePercent() + "\n"
+ + "- ÿc7UsedInventorySpacePercentÿc0: " + Storage.Inventory.UsedSpacePercent()
+ );
+ scriptBroadcast("mule");
+ scriptBroadcast("quit");
+
+ return false;
+ }
+
+ return true;
};
diff --git a/libs/SoloPlay/Functions/Polyfills.js b/libs/SoloPlay/Functions/Polyfills.js
index 94cdb54f..a9ae7597 100644
--- a/libs/SoloPlay/Functions/Polyfills.js
+++ b/libs/SoloPlay/Functions/Polyfills.js
@@ -7,13 +7,13 @@
*/
if (!Array.prototype.at) {
- Array.prototype.at = function (pos) {
- if (pos < 0) {
- pos += this.length;
- }
- if (pos < 0 || pos >= this.length) return undefined;
- return this[pos];
- };
+ Array.prototype.at = function (pos) {
+ if (pos < 0) {
+ pos += this.length;
+ }
+ if (pos < 0 || pos >= this.length) return undefined;
+ return this[pos];
+ };
}
/**
@@ -22,195 +22,172 @@ if (!Array.prototype.at) {
*/
(function (global, _original) {
- let __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
- }) : (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- o[k2] = m[k];
- }));
- let __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
- Object.defineProperty(o, "default", { enumerable: true, value: v });
- }) : function(o, v) {
- o.default = v;
- });
- let __importStar = (this && this.__importStar) || function (mod) {
- if (mod && mod.__esModule) return mod;
- let result = {};
- if (mod != null) for (let k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
- __setModuleDefault(result, mod);
- return result;
- };
- let Worker = __importStar(require("../../modules/Worker"));
- global._setTimeout = _original;
- /**
- * @param {function} cb
- * @param {number} time
- * @param args
- * @constructor
- */
- function Timer(cb, time, args) {
- let _this = this;
- if (time === void 0) { time = 0; }
- if (args === void 0) { args = []; }
- Timer.instances.push(this);
- Worker.runInBackground["__setTimeout__" + (Timer.counter++)] = (function (startTick) {
- return function () {
- let finished = getTickCount() - startTick >= time;
- if (finished) {
- let index = Timer.instances.indexOf(_this);
- // only if not removed from the time list
- if (index > -1) {
- Timer.instances.splice(index, 1);
- cb.apply(undefined, args);
- }
- }
- return !finished;
- };
- })(getTickCount());
- }
- Timer.instances = [];
- Timer.counter = 0;
- global.setTimeout = function (cb, time) {
- if (time === void 0) { time = 0; }
- let args = [];
- for (let _i = 2; _i < arguments.length; _i++) {
- args[_i - 2] = arguments[_i];
- }
- if (typeof cb === "string") {
- console.debug("Warning: Do not use raw code @ setTimeout and does not support lexical scoping");
- cb = [].filter.constructor(cb);
- }
- if (typeof cb !== "function") {
- throw new TypeError("setTimeout callback needs to be a function");
- }
- return new Timer(cb, time, args);
- };
- /**
- *
- * @param {Timer} timer
- */
- global.clearTimeout = function (timer) {
- let index = Timer.instances.indexOf(timer);
- if (index > -1) {
- Timer.instances.splice(index, 1);
- }
- };
- // getScript(true).name.toString() !== 'default.dbj' && setTimeout(function () {/* test code*/}, 1000)
+ let __createBinding = (this && this.__createBinding) || (Object.create ? (function (o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
+ }) : (function (o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+ }));
+ let __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function (o, v) {
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
+ }) : function (o, v) {
+ o.default = v;
+ });
+ let __importStar = (this && this.__importStar) || function (mod) {
+ if (mod && mod.__esModule) return mod;
+ let result = {};
+ if (mod != null) for (let k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+ };
+ let Worker = __importStar(require("../../modules/Worker"));
+ global._setTimeout = _original;
+ /**
+ * @param {function} cb
+ * @param {number} time
+ * @param args
+ * @constructor
+ */
+ function Timer (cb, time, args) {
+ let _this = this;
+ if (time === void 0) { time = 0; }
+ if (args === void 0) { args = []; }
+ Timer.instances.push(this);
+ Worker.runInBackground["__setTimeout__" + (Timer.counter++)] = (function (startTick) {
+ return function () {
+ let finished = getTickCount() - startTick >= time;
+ if (finished) {
+ let index = Timer.instances.indexOf(_this);
+ // only if not removed from the time list
+ if (index > -1) {
+ Timer.instances.splice(index, 1);
+ cb.apply(undefined, args);
+ }
+ }
+ return !finished;
+ };
+ })(getTickCount());
+ }
+ Timer.instances = [];
+ Timer.counter = 0;
+ global.setTimeout = function (cb, time) {
+ if (time === void 0) { time = 0; }
+ let args = [];
+ for (let _i = 2; _i < arguments.length; _i++) {
+ args[_i - 2] = arguments[_i];
+ }
+ if (typeof cb === "string") {
+ console.debug("Warning: Do not use raw code @ setTimeout and does not support lexical scoping");
+ cb = [].filter.constructor(cb);
+ }
+ if (typeof cb !== "function") {
+ throw new TypeError("setTimeout callback needs to be a function");
+ }
+ return new Timer(cb, time, args);
+ };
+ /**
+ *
+ * @param {Timer} timer
+ */
+ global.clearTimeout = function (timer) {
+ let index = Timer.instances.indexOf(timer);
+ if (index > -1) {
+ Timer.instances.splice(index, 1);
+ }
+ };
+ // getScript(true).name.toString() !== 'default.dbj' && setTimeout(function () {/* test code*/}, 1000)
})([].filter.constructor("return this")(), setTimeout);
if (!Object.setPrototypeOf) {
- // Only works in Chrome and FireFox, does not work in IE:
- Object.defineProperty(Object.prototype, "setPrototypeOf", {
- value: function (obj, proto) {
- // @ts-ignore
- if (obj.__proto__) {
- // @ts-ignore
- obj.__proto__ = proto;
- return obj;
- } else {
- // If you want to return prototype of Object.create(null):
- let Fn = function () {
- for (let key in obj) {
- Object.defineProperty(this, key, {
- value: obj[key],
- });
- }
- };
- Fn.prototype = proto;
- return new Fn();
- }
- },
- enumerable: false,
- });
+ // Only works in Chrome and FireFox, does not work in IE:
+ Object.defineProperty(Object.prototype, "setPrototypeOf", {
+ value: function (obj, proto) {
+ // @ts-ignore
+ if (obj.__proto__) {
+ // @ts-ignore
+ obj.__proto__ = proto;
+ return obj;
+ } else {
+ // If you want to return prototype of Object.create(null):
+ let Fn = function () {
+ for (let key in obj) {
+ Object.defineProperty(this, key, {
+ value: obj[key],
+ });
+ }
+ };
+ Fn.prototype = proto;
+ return new Fn();
+ }
+ },
+ enumerable: false,
+ });
}
if (!Object.values) {
- Object.values = function (source) {
- return Object.keys(source).map(function (k) { return source[k]; });
- };
+ Object.values = function (source) {
+ return Object.keys(source).map(function (k) { return source[k]; });
+ };
}
if (!Object.entries) {
- Object.entries = function (source) {
- return Object.keys(source).map(function (k) { return [k, source[k]]; });
- };
+ Object.entries = function (source) {
+ return Object.keys(source).map(function (k) { return [k, source[k]]; });
+ };
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#polyfill
// @ts-ignore
if (!Object.is) {
- Object.defineProperty(Object, "is", {
- value: function (x, y) {
- // SameValue algorithm
- if (x === y) {
- // return true if x and y are not 0, OR
- // if x and y are both 0 of the same sign.
- // This checks for cases 1 and 2 above.
- return x !== 0 || 1 / x === 1 / y;
- } else {
- // return true if both x AND y evaluate to NaN.
- // The only possibility for a variable to not be strictly equal to itself
- // is when that variable evaluates to NaN (example: Number.NaN, 0/0, NaN).
- // This checks for case 3.
- // eslint-disable-next-line no-self-compare
- return x !== x && y !== y;
- }
- }
- });
+ Object.defineProperty(Object, "is", {
+ value: function (x, y) {
+ // SameValue algorithm
+ if (x === y) {
+ // return true if x and y are not 0, OR
+ // if x and y are both 0 of the same sign.
+ // This checks for cases 1 and 2 above.
+ return x !== 0 || 1 / x === 1 / y;
+ } else {
+ // return true if both x AND y evaluate to NaN.
+ // The only possibility for a variable to not be strictly equal to itself
+ // is when that variable evaluates to NaN (example: Number.NaN, 0/0, NaN).
+ // This checks for case 3.
+ // eslint-disable-next-line no-self-compare
+ return x !== x && y !== y;
+ }
+ }
+ });
}
-// if (!Object.fromEntries) {
-// Object.defineProperty(Object, 'fromEntries', {
-// value: function (entries) {
-// if (!entries /*|| !entries[Symbol.iterator]*/) { throw new Error('Object.fromEntries() requires a single iterable argument'); }
-
-// const o = {};
-
-// Object.keys(entries).forEach((key) => {
-// const [k, v] = entries[key];
-
-// o[k] = v;
-// });
-
-// return o;
-// },
-// });
-// }
-
-// // filter an object
-// Object.filter = function (obj, predicate) {
-// return Object.fromEntries(Object.entries(obj).filter(predicate));
-// };
-
// https://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript
if (!Array.prototype.equals) {
- // Warn if overriding existing method
- !!Array.prototype.equals && console.warn("Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code.");
- // attach the .equals method to Array's prototype to call it on any array
- Array.prototype.equals = function (array) {
- // if the other array is a falsy value, return
- if (!array) return false;
-
- // compare lengths - can save a lot of time
- if (this.length !== array.length) return false;
-
- // call basic sort method, (my addition as I don't care if its the same order just if it contains the same values)
- this.sort();
- array.sort();
-
- for (let i = 0, l = this.length; i < l; i++) {
- // Check if we have nested arrays
- if (this[i] instanceof Array && array[i] instanceof Array) {
- // recurse into the nested arrays
- if (!this[i].equals(array[i])) return false;
- } else if (this[i] !== array[i]) {
- // Warning - two different object instances will never be equal: {x:20} != {x:20}
- return false;
- }
- }
- return true;
- };
- // Hide method from for-in loops
- Object.defineProperty(Array.prototype, "equals", {enumerable: false});
+ // Warn if overriding existing method
+ !!Array.prototype.equals && console.warn("Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code.");
+ // attach the .equals method to Array's prototype to call it on any array
+ Array.prototype.equals = function (array) {
+ // if the other array is a falsy value, return
+ if (!array) return false;
+
+ // compare lengths - can save a lot of time
+ if (this.length !== array.length) return false;
+
+ // call basic sort method, (my addition as I don't care if its the same order just if it contains the same values)
+ this.sort();
+ array.sort();
+
+ for (let i = 0, l = this.length; i < l; i++) {
+ // Check if we have nested arrays
+ if (this[i] instanceof Array && array[i] instanceof Array) {
+ // recurse into the nested arrays
+ if (!this[i].equals(array[i])) return false;
+ } else if (this[i] !== array[i]) {
+ // Warning - two different object instances will never be equal: {x:20} != {x:20}
+ return false;
+ }
+ }
+ return true;
+ };
+ // Hide method from for-in loops
+ Object.defineProperty(Array.prototype, "equals", { enumerable: false });
}
diff --git a/libs/SoloPlay/Functions/PrecastOverrides.js b/libs/SoloPlay/Functions/PrecastOverrides.js
index 452910ae..b8ee23fd 100644
--- a/libs/SoloPlay/Functions/PrecastOverrides.js
+++ b/libs/SoloPlay/Functions/PrecastOverrides.js
@@ -5,7 +5,7 @@
*
*/
-includeIfNotIncluded("common/Precast.js");
+includeIfNotIncluded("core/Precast.js");
Precast.enabled = true;
@@ -13,135 +13,150 @@ Precast.enabled = true;
// Can't be on a weapon due to consistent switching but
// Clay Goldem from Stone RW, Iron Golem from Metalgrid, Posion Creeper from Carrior Wind ring, Oak, HoW, or SoB from wisp
-new Overrides.Override(Precast, Precast.doPrecast, function (orignal, force) {
- if (!Precast.enabled) return false;
-
- switch (me.classid) {
- case sdk.player.class.Paladin:
- // Force BO 30 seconds before it expires
- if (this.haveCTA > -1) {
- let forceBo = (force
- || (getTickCount() - this.skills.battleOrders.tick >= this.skills.battleOrders.duration - 30000)
- || !me.getState(sdk.states.BattleCommand));
- forceBo && this.precastCTA(forceBo);
- }
-
- if (Skill.canUse(sdk.skills.HolyShield)
- && Math.round(Skill.getManaCost(sdk.skills.HolyShield) * 100 / me.mpmax) < 35
- && (!me.getState(sdk.states.HolyShield) || force)) {
- Precast.cast(sdk.skills.HolyShield);
- }
-
- break;
- case sdk.player.class.Barbarian:
- let needShout = (Skill.canUse(sdk.skills.Shout) && (force || !me.getState(sdk.states.Shout)));
- let needBo = (Skill.canUse(sdk.skills.BattleOrders) && (force || !me.getState(sdk.states.BattleOrders)));
- let needBc = (Skill.canUse(sdk.skills.BattleCommand) && (force || !me.getState(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 && Precast.cast(sdk.skills.BattleCommand, x, y, true);
- needBo && Precast.cast(sdk.skills.BattleOrders, x, y, true);
- needShout && Precast.cast(sdk.skills.Shout, x, y, true);
- needBc && Precast.cast(sdk.skills.BattleCommand, x, y, true);
-
- me.weaponswitch !== primary && me.switchWeapons(primary);
- }
-
- if (CharData.skillData.haveChargedSkill(sdk.skills.Enchant) && !me.getState(sdk.states.Enchant) && me.gold > 500000) {
- // Cast enchant
- Attack.castCharges(sdk.skills.Enchant, me);
- }
-
- break;
- default:
- return orignal(force);
- }
-
- me.switchWeapons(Attack.getPrimarySlot());
-
- return true;
+new Overrides.Override(Precast, Precast.doPrecast, function (orignal, force, partial) {
+ if (!Precast.enabled) return false;
+
+ switch (me.classid) {
+ case sdk.player.class.Paladin:
+ // Force BO 30 seconds before it expires
+ if (this.haveCTA > -1) {
+ let forceBo = (force
+ || Precast.skills.get(sdk.skills.BattleOrders).remaining() < 25
+ || !me.getState(sdk.states.BattleCommand));
+ forceBo && this.precastCTA(forceBo);
+ }
+
+ if (Precast.skills.get(sdk.skills.HolyShield).needToCast(force || partial, 15)
+ && Math.round(Skill.getManaCost(sdk.skills.HolyShield) * 100 / me.mpmax) < 35) {
+ 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:
+ 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 && Precast.cast(sdk.skills.BattleCommand, x, y, false);
+ needBo && Precast.cast(sdk.skills.BattleOrders, x, y, false);
+ needShout && Precast.cast(sdk.skills.Shout, x, y, false);
+ needBc && Precast.cast(sdk.skills.BattleCommand, x, y, false);
+
+ me.weaponswitch !== primary && me.switchWeapons(primary);
+ }
+
+ if (CharData.skillData.haveChargedSkill(sdk.skills.Enchant)
+ && !me.getState(sdk.states.Enchant)
+ && me.gold > 500000) {
+ // Cast enchant
+ Attack.castCharges(sdk.skills.Enchant, me);
+ }
+
+ break;
+ default:
+ return orignal(force);
+ }
+
+ me.switchWeapons(Attack.getPrimarySlot());
+
+ return true;
}).apply();
Precast.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;
- let coord = CollMap.getRandCoordinate(me.x, -3, 3, me.y, -3, 3); // Get a random coordinate to summon using
- let unit = Attack.getNearestMonster({skipImmune: false});
-
- if (unit && [sdk.summons.type.Golem, sdk.summons.type.Grizzly, sdk.summons.type.Shadow].includes(minionType) && unit.distance < 20 && !checkCollision(me, unit, sdk.collision.Ranged)) {
- try {
- if (Skill.cast(skillId, sdk.skills.hand.Right, unit)) {
- if (me.getMinionCount(minionType) === count) {
- continue;
- } else {
- retry++;
- }
- } else if (Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y)) {
- if (me.getMinionCount(minionType) === count) {
- continue;
- } else {
- retry++;
- }
- }
- } catch (e) {
- console.log(e);
- }
- }
-
- if (coord && Attack.castableSpot(coord.x, coord.y)) {
- Skill.cast(skillId, sdk.skills.hand.Right, coord.x, coord.y);
-
- if (me.getMinionCount(minionType) === count) {
- continue;
- } else {
- retry++;
- }
- } else if (Attack.castableSpot(me.x, me.y)) {
- Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y);
-
- if (me.getMinionCount(minionType) === count) {
- continue;
- } else {
- retry++;
- }
- }
-
- if (Skill.getManaCost(skillId) > me.mp && me.getMobCount(15) === 0) {
- delay(1000);
- retry++;
- }
-
- if (retry > count * 2) {
- if (me.inTown) {
- if (Town.heal()) {
- delay(100 + me.ping);
- me.cancel();
- }
-
- Town.move("portalspot");
- Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y);
- } else {
- 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);
- }
- }
-
- retry = 0;
- }
- }
-
- return !!rv;
+ if (!Skill.canUse(skillId)) return false;
+
+ let rv, retry = 0;
+ let count = Skill.getMaxSummonCount(skillId);
+
+ while (me.getMinionCount(minionType) < count) {
+ rv = true;
+ let coord = CollMap.getRandCoordinate(me.x, -3, 3, me.y, -3, 3); // Get a random coordinate to summon using
+ let unit = Attack.getNearestMonster({ skipImmune: false });
+
+ if (unit && [sdk.summons.type.Golem, sdk.summons.type.Grizzly, sdk.summons.type.Shadow].includes(minionType)
+ && unit.distance < 20 && !checkCollision(me, unit, sdk.collision.Ranged)) {
+ try {
+ if (Skill.cast(skillId, sdk.skills.hand.Right, unit)) {
+ if (me.getMinionCount(minionType) === count) {
+ continue;
+ } else {
+ retry++;
+ }
+ } else if (Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y)) {
+ if (me.getMinionCount(minionType) === count) {
+ continue;
+ } else {
+ retry++;
+ }
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ if (coord && Attack.castableSpot(coord.x, coord.y)) {
+ Skill.cast(skillId, sdk.skills.hand.Right, coord.x, coord.y);
+
+ if (me.getMinionCount(minionType) === count) {
+ continue;
+ } else {
+ retry++;
+ }
+ } else if (Attack.castableSpot(me.x, me.y)) {
+ Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y);
+
+ if (me.getMinionCount(minionType) === count) {
+ continue;
+ } else {
+ retry++;
+ }
+ }
+
+ if (Skill.getManaCost(skillId) > me.mp && me.getMobCount(15) === 0) {
+ delay(1000);
+ retry++;
+ }
+
+ if (retry > count * 2) {
+ if (me.inTown) {
+ if (Town.heal()) {
+ delay(100 + me.ping);
+ me.cancel();
+ }
+
+ Town.move("portalspot");
+ Skill.cast(skillId, sdk.skills.hand.Right, me.x, me.y);
+ } else {
+ 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);
+ }
+ }
+
+ retry = 0;
+ }
+ }
+
+ return !!rv;
};
diff --git a/libs/SoloPlay/Functions/PrototypeOverrides.js b/libs/SoloPlay/Functions/PrototypeOverrides.js
index bcd6b0f1..27eedf50 100644
--- a/libs/SoloPlay/Functions/PrototypeOverrides.js
+++ b/libs/SoloPlay/Functions/PrototypeOverrides.js
@@ -6,7 +6,7 @@
*
*/
-includeIfNotIncluded("common/Prototypes.js");
+includeIfNotIncluded("core/Prototypes.js");
includeIfNotIncluded("SoloPlay/Functions/Me.js");
includeIfNotIncluded("SoloPlay/Functions/Polyfills.js");
@@ -14,818 +14,870 @@ includeIfNotIncluded("SoloPlay/Functions/Polyfills.js");
* @description Unit prototypes for soloplay with checks to ensure forwards compatibility
*/
if (!Unit.prototype.hasOwnProperty("isCharm")) {
- Object.defineProperty(Unit.prototype, "isCharm", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- return [sdk.items.SmallCharm, sdk.items.LargeCharm, sdk.items.GrandCharm].includes(this.classid);
- },
- });
+ Object.defineProperty(Unit.prototype, "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);
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("isGem")) {
- Object.defineProperty(Unit.prototype, "isGem", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- return (this.itemType >= sdk.items.type.Amethyst && this.itemType <= sdk.items.type.Skull);
- },
- });
+ Object.defineProperty(Unit.prototype, "isGem", {
+ /** @this {ItemUnit} */
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return false;
+ return (
+ this.itemType >= sdk.items.type.Amethyst
+ && this.itemType <= sdk.items.type.Skull
+ );
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("isInsertable")) {
- Object.defineProperty(Unit.prototype, "isInsertable", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- return [sdk.items.type.Jewel, sdk.items.type.Rune].includes(this.itemType) || this.isGem;
- },
- });
+ Object.defineProperty(Unit.prototype, "isInsertable", {
+ /** @this {ItemUnit} */
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return false;
+ return [sdk.items.type.Jewel, sdk.items.type.Rune].includes(this.itemType) || this.isGem;
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("isRuneword")) {
- Object.defineProperty(Unit.prototype, "isRuneword", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- return !!this.getFlag(sdk.items.flags.Runeword);
- },
- });
+ Object.defineProperty(Unit.prototype, "isRuneword", {
+ /** @this {ItemUnit} */
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return false;
+ return !!this.getFlag(sdk.items.flags.Runeword);
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("isBroken")) {
- Object.defineProperty(Unit.prototype, "isBroken", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- if (this.getStat(sdk.stats.Indestructible)) return false;
- return !!this.getFlag(sdk.items.flags.Broken);
- },
- });
+ Object.defineProperty(Unit.prototype, "isBroken", {
+ /** @this {ItemUnit} */
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return false;
+ if (this.getStat(sdk.stats.Indestructible)) return false;
+ return !!this.getFlag(sdk.items.flags.Broken);
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("isStunned")) {
- Object.defineProperty(Unit.prototype, "isStunned", {
- get: function () {
- return this.getState(sdk.states.Stunned);
- },
- });
+ Object.defineProperty(Unit.prototype, "isStunned", {
+ /** @this {Monster | Player} */
+ get: function () {
+ return this.getState(sdk.states.Stunned);
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("isUnderCoS")) {
- Object.defineProperty(Unit.prototype, "isUnderCoS", {
- get: function () {
- return this.getState(sdk.states.Cloaked);
- },
- });
+ Object.defineProperty(Unit.prototype, "isUnderCoS", {
+ /** @this {Monster | Player} */
+ get: function () {
+ return this.getState(sdk.states.Cloaked);
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("isUnderLowerRes")) {
- Object.defineProperty(Unit.prototype, "isUnderLowerRes", {
- get: function () {
- return this.getState(sdk.states.LowerResist);
- },
- });
+ Object.defineProperty(Unit.prototype, "isUnderLowerRes", {
+ /** @this {Monster | Player} */
+ get: function () {
+ return this.getState(sdk.states.LowerResist);
+ },
+ });
+}
+
+if (!Unit.prototype.hasOwnProperty("size")) {
+ Object.defineProperty(Unit.prototype, "size", {
+ /** @this {Monster} */
+ get: function () {
+ if (this.type !== sdk.unittype.Monster) return 0;
+ const baseId = getBaseStat("monstats", this.classid, "baseid");
+ const size = getBaseStat("monstats2", baseId, "sizex");
+ return (typeof size !== "number" || size < 1 || size > 3) ? 3 : size;
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("isBaseType")) {
- Object.defineProperty(Unit.prototype, "isBaseType", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- return [sdk.items.quality.Normal, sdk.items.quality.Superior].includes(this.quality) && !this.questItem && !this.isRuneword
- && getBaseStat("items", this.classid, "gemsockets") > 0 && [sdk.items.type.Ring, sdk.items.type.Amulet].indexOf(this.itemType) === -1;
- }
- });
+ Object.defineProperty(Unit.prototype, "isBaseType", {
+ /** @this {ItemUnit} */
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return false;
+ return [sdk.items.quality.Normal, sdk.items.quality.Superior].includes(this.quality)
+ && !this.questItem && !this.isRuneword
+ && getBaseStat("items", this.classid, "gemsockets") > 0
+ && [sdk.items.type.Ring, sdk.items.type.Amulet].indexOf(this.itemType) === -1;
+ }
+ });
}
if (!Unit.prototype.hasOwnProperty("rawStrength")) {
- Object.defineProperty(Unit.prototype, "rawStrength", {
- get: function () {
- const lvl = this.getStat(sdk.stats.Level);
- const rawBonus = (i) => i.getStat(sdk.stats.Strength);
- const perLvlBonus = (i) => lvl * i.getStat(sdk.stats.PerLevelStrength) / 8;
- const bonus = ~~(this.getItemsEx()
- .filter((i) => i.isEquipped || i.isEquippedCharm)
- .map((i) => rawBonus(i) + perLvlBonus(i))
- .reduce((acc, v) => acc + v, 0));
- return this.getStat(sdk.stats.Strength) - bonus;
- },
- });
+ Object.defineProperty(Unit.prototype, "rawStrength", {
+ get: function () {
+ const lvl = this.getStat(sdk.stats.Level);
+ const rawBonus = function (i) {
+ return i.getStat(sdk.stats.Strength);
+ };
+ const perLvlBonus = function (i) {
+ return lvl * i.getStat(sdk.stats.PerLevelStrength) / 8;
+ };
+ const bonus = ~~(this.getItemsEx()
+ .filter((i) => i.isEquipped || i.isEquippedCharm)
+ .map((i) => rawBonus(i) + perLvlBonus(i))
+ .reduce((acc, v) => acc + v, 0));
+ return this.getStat(sdk.stats.Strength) - bonus;
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("rawDexterity")) {
- Object.defineProperty(Unit.prototype, "rawDexterity", {
- get: function () {
- const lvl = this.getStat(sdk.stats.Level);
- const rawBonus = (i) => i.getStat(sdk.stats.Dexterity);
- const perLvlBonus = (i) => lvl * i.getStat(sdk.stats.PerLevelDexterity) / 8;
- const bonus = ~~(this.getItemsEx()
- .filter((i) => i.isEquipped || i.isEquippedCharm)
- .map((i) => rawBonus(i) + perLvlBonus(i))
- .reduce((acc, v) => acc + v, 0));
- return this.getStat(sdk.stats.Dexterity) - bonus;
- },
- });
+ Object.defineProperty(Unit.prototype, "rawDexterity", {
+ get: function () {
+ const lvl = this.getStat(sdk.stats.Level);
+ const rawBonus = function (i) {
+ return i.getStat(sdk.stats.Dexterity);
+ };
+ const perLvlBonus = function (i) {
+ return lvl * i.getStat(sdk.stats.PerLevelDexterity) / 8;
+ };
+ const bonus = ~~(this.getItemsEx()
+ .filter((i) => i.isEquipped || i.isEquippedCharm)
+ .map((i) => rawBonus(i) + perLvlBonus(i))
+ .reduce((acc, v) => acc + v, 0));
+ return this.getStat(sdk.stats.Dexterity) - bonus;
+ },
+ });
}
if (!Unit.prototype.hasOwnProperty("upgradedStrReq")) {
- Object.defineProperty(Unit.prototype, "upgradedStrReq", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- let code, id, baseReq, finalReq, ethereal = this.getFlag(sdk.items.flags.Ethereal);
- let reqModifier = this.getStat(sdk.stats.ReqPercent);
-
- switch (this.itemclass) {
- case sdk.items.class.Normal:
- code = getBaseStat("items", this.classid, "ubercode").trim();
-
- break;
- case sdk.items.class.Exceptional:
- code = getBaseStat("items", this.classid, "ultracode").trim();
-
- break;
- case sdk.items.class.Elite:
- return this.strreq;
- }
-
- id = NTIPAliasClassID[code];
- baseReq = getBaseStat("items", id, "reqstr");
- finalReq = baseReq + Math.floor(baseReq * reqModifier / 100);
- ethereal && (finalReq -= 10);
- return Math.max(finalReq, 0);
- }
- });
+ Object.defineProperty(Unit.prototype, "upgradedStrReq", {
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return false;
+ let code, id, baseReq, finalReq, ethereal = this.getFlag(sdk.items.flags.Ethereal);
+ let reqModifier = this.getStat(sdk.stats.ReqPercent);
+
+ switch (this.itemclass) {
+ case sdk.items.class.Normal:
+ code = getBaseStat("items", this.classid, "ubercode").trim();
+
+ break;
+ case sdk.items.class.Exceptional:
+ code = getBaseStat("items", this.classid, "ultracode").trim();
+
+ break;
+ case sdk.items.class.Elite:
+ return this.strreq;
+ }
+
+ id = NTIPAliasClassID[code];
+ baseReq = getBaseStat("items", id, "reqstr");
+ finalReq = baseReq + Math.floor(baseReq * reqModifier / 100);
+ ethereal && (finalReq -= 10);
+ return Math.max(finalReq, 0);
+ }
+ });
}
if (!Unit.prototype.hasOwnProperty("upgradedDexReq")) {
- Object.defineProperty(Unit.prototype, "upgradedDexReq", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- let code, id, baseReq, finalReq, ethereal = this.getFlag(sdk.items.flags.Ethereal);
- let reqModifier = this.getStat(sdk.stats.ReqPercent);
-
- switch (this.itemclass) {
- case sdk.items.class.Normal:
- code = getBaseStat("items", this.classid, "ubercode").trim();
-
- break;
- case sdk.items.class.Exceptional:
- code = getBaseStat("items", this.classid, "ultracode").trim();
-
- break;
- case sdk.items.class.Elite:
- return this.dexreq;
- }
-
- id = NTIPAliasClassID[code];
- baseReq = getBaseStat("items", id, "reqdex");
- finalReq = baseReq + Math.floor(baseReq * reqModifier / 100);
- ethereal && (finalReq -= 10);
- return Math.max(finalReq, 0);
- }
- });
+ Object.defineProperty(Unit.prototype, "upgradedDexReq", {
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return false;
+ let code, id, baseReq, finalReq, ethereal = this.getFlag(sdk.items.flags.Ethereal);
+ let reqModifier = this.getStat(sdk.stats.ReqPercent);
+
+ switch (this.itemclass) {
+ case sdk.items.class.Normal:
+ code = getBaseStat("items", this.classid, "ubercode").trim();
+
+ break;
+ case sdk.items.class.Exceptional:
+ code = getBaseStat("items", this.classid, "ultracode").trim();
+
+ break;
+ case sdk.items.class.Elite:
+ return this.dexreq;
+ }
+
+ id = NTIPAliasClassID[code];
+ baseReq = getBaseStat("items", id, "reqdex");
+ finalReq = baseReq + Math.floor(baseReq * reqModifier / 100);
+ ethereal && (finalReq -= 10);
+ return Math.max(finalReq, 0);
+ }
+ });
}
if (!Unit.prototype.hasOwnProperty("upgradedLvlReq")) {
- Object.defineProperty(Unit.prototype, "upgradedLvlReq", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return false;
- let code, id;
-
- switch (this.itemclass) {
- case sdk.items.class.Normal:
- code = getBaseStat("items", this.classid, "ubercode").trim();
-
- break;
- case sdk.items.class.Exceptional:
- code = getBaseStat("items", this.classid, "ultracode").trim();
-
- break;
- case sdk.items.class.Elite:
- return this.lvlreq;
- }
-
- id = NTIPAliasClassID[code];
- return Math.max(getBaseStat("items", id, "levelreq"), 0);
- }
- });
+ Object.defineProperty(Unit.prototype, "upgradedLvlReq", {
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return false;
+ let code, id;
+
+ switch (this.itemclass) {
+ case sdk.items.class.Normal:
+ code = getBaseStat("items", this.classid, "ubercode").trim();
+
+ break;
+ case sdk.items.class.Exceptional:
+ code = getBaseStat("items", this.classid, "ultracode").trim();
+
+ break;
+ case sdk.items.class.Elite:
+ return this.lvlreq;
+ }
+
+ id = NTIPAliasClassID[code];
+ return Math.max(getBaseStat("items", id, "levelreq"), 0);
+ }
+ });
}
if (!Unit.prototype.hasOwnProperty("allRes")) {
- Object.defineProperty(Unit.prototype, "allRes", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return 0;
- let fr = this.getStat(sdk.stats.FireResist);
- let cr = this.getStat(sdk.stats.ColdResist);
- let lr = this.getStat(sdk.stats.LightningResist);
- let pr = this.getStat(sdk.stats.PoisonResist);
- return (fr && cr && lr && pr) ? fr : 0;
- }
- });
+ Object.defineProperty(Unit.prototype, "allRes", {
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return 0;
+ let fr = this.getStat(sdk.stats.FireResist);
+ let cr = this.getStat(sdk.stats.ColdResist);
+ let lr = this.getStat(sdk.stats.LightningResist);
+ let pr = this.getStat(sdk.stats.PoisonResist);
+ return (fr && cr && lr && pr) ? fr : 0;
+ }
+ });
}
if (!Unit.prototype.hasOwnProperty("prettyPrint")) {
- Object.defineProperty(Unit.prototype, "prettyPrint", {
- get: function () {
- if (this.type !== sdk.unittype.Item) return this.name;
- return this.fname.split("\n").reverse().join(" ");
- }
- });
+ Object.defineProperty(Unit.prototype, "prettyPrint", {
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return this.name;
+ return this.fname.split("\n").reverse().join(" ");
+ }
+ });
+}
+
+if (!Unit.prototype.hasOwnProperty("quantityPercent")) {
+ Object.defineProperty(Unit.prototype, "quantityPercent", {
+ get: function () {
+ if (this.type !== sdk.unittype.Item) return 0;
+ let quantity = this.getStat(sdk.stats.Quantity);
+ if (!quantity) return 0;
+ let extraStack = this.getStat(sdk.stats.ExtraStack) || 0;
+ return ((quantity * 100) / (getBaseStat("items", this.classid, "maxstack") + extraStack));
+ }
+ });
}
/**
* @param {number} difficulty
*/
Unit.prototype.getResPenalty = function (difficulty) {
- difficulty > 2 && (difficulty = 2);
- return me.gametype === sdk.game.gametype.Classic ? [0, 20, 50][difficulty] : [0, 40, 100][difficulty];
+ difficulty > 2 && (difficulty = sdk.difficulty.Hell);
+ return me.gametype === sdk.game.gametype.Classic
+ ? [0, 20, 50][difficulty]
+ : [0, 40, 100][difficulty];
};
Unit.prototype.getItemType = function () {
- switch (this.itemType) {
- case sdk.items.type.Shield:
- case sdk.items.type.AuricShields:
- case sdk.items.type.VoodooHeads:
- return "Shield";
- case sdk.items.type.Armor:
- return "Armor";
- case sdk.items.type.Helm:
- case sdk.items.type.PrimalHelm:
- case sdk.items.type.Circlet:
- case sdk.items.type.Pelt:
- return "Helmet";
- 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:
- return "Weapon";
- // currently only use this function for socket related things so might as well make non-socketable things return false
- // case sdk.items.type.BowQuiver:
- // case sdk.items.type.CrossbowQuiver:
- // //return "Quiver";
- // case sdk.items.type.Ring:
- // //return "Ring";
- // case sdk.items.type.Amulet:
- // //return "Amulet";
- // case sdk.items.type.Boots:
- // //return "Boots";
- // case sdk.items.type.Gloves:
- // //return "Gloves";
- // case sdk.items.type.Belt:
- // //return "Belt";
- // default:
- // return "";
- }
-
- return "";
+ switch (this.itemType) {
+ case sdk.items.type.Shield:
+ case sdk.items.type.AuricShields:
+ case sdk.items.type.VoodooHeads:
+ return "Shield";
+ case sdk.items.type.Armor:
+ return "Armor";
+ case sdk.items.type.Helm:
+ case sdk.items.type.PrimalHelm:
+ case sdk.items.type.Circlet:
+ case sdk.items.type.Pelt:
+ return "Helmet";
+ 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:
+ return "Weapon";
+ // currently only use this function for socket related things so might as well make non-socketable things return false
+ // case sdk.items.type.BowQuiver:
+ // case sdk.items.type.CrossbowQuiver:
+ // //return "Quiver";
+ // case sdk.items.type.Ring:
+ // //return "Ring";
+ // case sdk.items.type.Amulet:
+ // //return "Amulet";
+ // case sdk.items.type.Boots:
+ // //return "Boots";
+ // case sdk.items.type.Gloves:
+ // //return "Gloves";
+ // case sdk.items.type.Belt:
+ // //return "Belt";
+ // default:
+ // return "";
+ }
+
+ return "";
};
Unit.prototype.castChargedSkillEx = function (...args) {
- let skillId, x, y, unit, chargedItem, charge;
- let chargedItems = [];
-
- 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) {
- Developer.debugging.skills && console.log("ÿc9CastChargedSkillÿc0 :: Wierd Error, invalid arguments, expected 'me' object or 'item' unit" + " unit type : " + this.type);
- return false;
- }
-
- // Called the function the unit, me.
- if (this === me) {
- if (!skillId) throw Error("Must supply skillId on me.castChargedSkill");
-
- chargedItems = [];
-
- CharData.skillData.chargedSkills.forEach(chargeSkill => {
- if (chargeSkill.skill === skillId) {
- console.debug(chargeSkill);
- let item = me.getItem(-1, sdk.items.mode.Equipped, chargeSkill.gid);
- !!item && chargedItems.push({
- charge: chargeSkill.skill,
- level: chargeSkill.level,
- item: item
- });
- }
- });
-
- if (chargedItems.length === 0) {
- console.log("ÿc9CastChargedSkillÿc0 :: Don't have the charged skill (" + skillId + "), or not enough charges");
- return false;
- }
-
- chargedItem = chargedItems.sort((a, b) => a.charge.level - b.charge.level).first().item;
-
- // Check if item with charges is equipped on the switch spot
- me.weaponswitch === 0 && chargedItem.isOnSwap && me.switchWeapons(1);
-
- return chargedItem.castChargedSkillEx.apply(chargedItem, args);
- } else if (this.type === sdk.unittype.Item) {
- charge = this.getStat(-2)[sdk.stats.ChargedSkill]; // WARNING. Somehow this gives duplicates
-
- if (!charge) {
- console.warn("ÿc9CastChargedSkillÿc0 :: No charged skill on this item");
- return false;
- }
-
- if (skillId) {
- if (charge instanceof Array) {
- charge = charge.filter(item => (item && item.skill === skillId) && !!item.charges); // Filter out all other charged skills
- charge = charge.first();
- } else {
- if (charge.skill !== skillId || !charge.charges) {
- console.warn("No charges matching skillId");
- charge = false;
- }
- }
- } else if (charge.length > 1) {
- throw new Error("multiple charges on this item without a given skillId");
- }
-
- if (charge) {
- 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);
-
- if (!usePacket) {
- return Skill.cast(skillId, sdk.skills.hand.Right, x || me.x, y || me.y, this); // Non packet casting
- }
-
- // Packet casting
- // Setting skill on hand
- sendPacket(1, sdk.packets.send.SelectSkill, 2, charge.skill, 1, 0x0, 1, 0x00, 4, this.gid);
- console.log("Set charge skill " + charge.skill + " on hand");
- // 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
- sendPacket(1, sdk.packets.send.RightSkillOnLocation, 2, x || me.x, 2, y || me.y);
- console.log("Cast charge skill " + charge.skill);
- // The result of "successfully" casted is different, so we cant wait for it here. We have to assume it worked
-
- return true;
- }
- }
-
- return false;
+ let skillId, x, y, unit;
+
+ 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) {
+ if (Developer.debugging.skills) {
+ console.debug(
+ "ÿc9CastChargedSkillÿc0 :: Wierd Error, invalid arguments, expected 'me' object or 'item' unit" + " unit type : " + this.type
+ );
+ }
+ return false;
+ }
+
+ // Called the function the unit, me.
+ if (this === me) {
+ if (!skillId) throw Error("Must supply skillId on me.castChargedSkill");
+
+ let chargedItems = [];
+
+ CharData.skillData.chargedSkills
+ .forEach(function (chargeSkill) {
+ if (chargeSkill.skill !== skillId) return;
+ let item = me.getItem(-1, sdk.items.mode.Equipped, chargeSkill.gid);
+ !!item && chargedItems.push({
+ charge: chargeSkill.skill,
+ level: chargeSkill.level,
+ item: item
+ });
+ });
+
+ if (chargedItems.length === 0) {
+ console.error(
+ "ÿc9CastChargedSkillÿc0 :: Don't have the charged skill (" + skillId + "), or not enough charges"
+ );
+ return false;
+ }
+
+ /** @type {ItemUnit} */
+ let chargedItem = chargedItems
+ .sort(function (a, b) {
+ return b.charge.level - a.charge.level;
+ })
+ .first().item;
+
+ // Check if item with charges is equipped on the switch spot
+ me.weaponswitch === 0 && chargedItem.isOnSwap && me.switchWeapons(1);
+
+ return chargedItem.castChargedSkillEx.apply(chargedItem, args);
+ } else if (this.type === sdk.unittype.Item) {
+ let charge = this.getStat(-2)[sdk.stats.ChargedSkill]; // WARNING. Somehow this gives duplicates
+
+ if (!charge) {
+ console.warn("ÿc9CastChargedSkillÿc0 :: No charged skill on this item");
+ return false;
+ }
+
+ if (skillId) {
+ if (charge instanceof Array) {
+ // Filter out all other charged skills
+ charge = charge
+ .filter(function (item) {
+ return (item && item.skill === skillId) && !!item.charges;
+ })
+ .first();
+ } else {
+ if (charge.skill !== skillId || !charge.charges) {
+ console.warn("No charges matching skillId");
+ charge = false;
+ }
+ }
+ } else if (charge.length > 1) {
+ throw new Error("multiple charges on this item without a given skillId");
+ }
+
+ if (charge) {
+ const usePacket = ([
+ sdk.skills.Teleport, sdk.skills.Valkyrie, sdk.skills.Decoy,
+ sdk.skills.RaiseSkeleton, sdk.skills.ClayGolem,
+ sdk.skills.RaiseSkeletalMage, sdk.skills.BloodGolem,
+ sdk.skills.IronGolem, sdk.skills.Revive,
+ sdk.skills.Werewolf, sdk.skills.Werebear,
+ sdk.skills.OakSage, sdk.skills.SpiritWolf, sdk.skills.PoisonCreeper,
+ sdk.skills.SummonDireWolf, sdk.skills.Grizzly,
+ sdk.skills.HeartofWolverine, sdk.skills.SpiritofBarbs,
+ sdk.skills.ShadowMaster, sdk.skills.ShadowWarrior
+ ].indexOf(skillId) === -1);
+
+ if (!usePacket) {
+ return Skill.cast(skillId, sdk.skills.hand.Right, x || me.x, y || me.y, this); // Non packet casting
+ }
+
+ // Packet casting
+ // Setting skill on hand
+ new PacketBuilder()
+ .byte(sdk.packets.send.SelectSkill)
+ .word(charge.skill)
+ .byte(0x00)
+ .byte(0x00)
+ .dword(this.gid)
+ .send();
+ // console.log("Set charge skill " + charge.skill + " on hand");
+ // 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();
+ console.log("Cast charge skill " + charge.skill);
+ // The result of "successfully" casted is different, so we cant wait for it here. We have to assume it worked
+
+ return true;
+ }
+ }
+
+ return false;
};
Unit.prototype.castSwitchChargedSkill = function (...args) {
- let skillId, x, y, unit, chargedItem;
- let chargedItems = [];
-
- switch (args.length) {
- case 0: // item.castChargedSkill()
- case 1: // hellfire.castChargedSkill(monster);
- 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");
- }
-
- if (this !== me) throw Error("invalid arguments, expected 'me' object");
-
- // Charged skills can only be casted on x, y coordinates
- unit && ([x, y] = [unit.x, unit.y]);
-
- if (x === undefined || y === undefined) return false;
-
- // Called the function the unit, me.
- if (this === me) {
- if (!skillId) throw Error("Must supply skillId on me.castChargedSkill");
-
- chargedItems = [];
-
- CharData.skillData.chargedSkillsOnSwitch.forEach(chargeSkill => {
- if (chargeSkill.skill === skillId) {
- console.debug(chargeSkill);
- let item = me.getItem(-1, sdk.items.mode.Equipped, chargeSkill.gid);
- !!item && chargedItems.push({
- charge: chargeSkill.skill,
- level: chargeSkill.level,
- item: item
- });
- }
- });
-
- if (chargedItems.length === 0) {
- console.log("ÿc9SwitchCastChargedSkillÿc0 :: Don't have the charged skill (" + skillId + "), or not enough charges");
- return false;
- }
-
- me.weaponswitch === 0 && me.switchWeapons(1);
-
- chargedItem = chargedItems.sort((a, b) => a.charge.level - b.charge.level).first().item;
-
- return chargedItem.castChargedSkillEx.apply(chargedItem, args);
- }
-
- return false;
+ let skillId, x, y, unit;
+
+ switch (args.length) {
+ case 0: // item.castChargedSkill()
+ case 1: // hellfire.castChargedSkill(monster);
+ 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");
+ }
+
+ if (this !== me) throw Error("invalid arguments, expected 'me' object");
+
+ // Charged skills can only be casted on x, y coordinates
+ unit && ([x, y] = [unit.x, unit.y]);
+
+ if (x === undefined || y === undefined) return false;
+
+ // Called the function the unit, me.
+ if (this === me) {
+ if (!skillId) throw Error("Must supply skillId on me.castChargedSkill");
+
+ /** @type {{ charge: number, level: number, item: ItemUnit }[]} */
+ let chargedItems = [];
+
+ CharData.skillData.chargedSkillsOnSwitch.forEach(function (chargeSkill) {
+ if (chargeSkill.skill === skillId) {
+ let item = me.getItem(-1, sdk.items.mode.Equipped, chargeSkill.gid);
+ !!item && chargedItems.push({
+ charge: chargeSkill.skill,
+ level: chargeSkill.level,
+ item: item
+ });
+ }
+ });
+
+ if (chargedItems.length === 0) {
+ console.error(
+ "ÿc9SwitchCastChargedSkillÿc0 :: Don't have the charged skill (" + skillId + "), or not enough charges"
+ );
+ return false;
+ }
+
+ me.weaponswitch === 0 && me.switchWeapons(1);
+
+ let chargedItem = chargedItems
+ .sort(function (a, b) {
+ return b.charge.level - a.charge.level;
+ })
+ .first().item;
+ return chargedItem.castChargedSkillEx.apply(chargedItem, args);
+ }
+
+ return false;
};
Unit.prototype.getStatEx = function (id, subid) {
- let i, temp, rval, regex;
-
- switch (id) {
- case sdk.stats.AllRes: //calculates all res, doesnt exists trough
- { // 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.MaxMana:
- rval = this.getStat(sdk.stats.MaxMana);
-
- if (rval > 446) {
- return rval - 16777216; // Fix for negative values (Gull knife)
- }
-
- return rval;
- 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 (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);
- }
-
- !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 (i = 0; i < temp[id].length; i += 1) {
- // fix reference to undefined property temp[id][i].skill.
- if (temp[id][i] !== undefined && temp[id][i].skill !== 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 (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 === undefined && (this.desc = this.description);
-
- if (this.desc) {
- temp = !!this.desc ? 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 === undefined && (this.desc = this.description);
-
- if (this.desc) {
- temp = !!this.desc ? 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;
- }
- }
-
- if (subid === undefined) {
- return this.getStat(id);
- }
-
- return this.getStat(id, subid);
-};
-
-/**
- * @description Returns boolean if we have all the runes given by itemInfo array
- * @param {number[]} itemInfo - Array of rune classids
- * @returns Boolean
- */
-Unit.prototype.haveRunes = function (itemInfo = []) {
- if (this === undefined || this.type > 1) return false;
- if (!Array.isArray(itemInfo) || typeof itemInfo[0] !== "number") return false;
- let itemList = this.getItemsEx().filter(i => i.isInStorage && i.itemType === sdk.items.type.Rune);
- if (!itemList.length || itemList.length < itemInfo.length) return false;
- let checkedGids = [];
-
- for (let i = 0; i < itemInfo.length; i++) {
- let rCheck = itemInfo[i];
-
- if (!itemList.some(i => {
- if (i.classid === rCheck && checkedGids.indexOf(i.gid) === -1) {
- checkedGids.push(i.gid);
- return true;
- }
- return false;
- })) {
- return false;
- }
- }
-
- return true;
+ let i, temp, rval, regex;
+
+ switch (id) {
+ case sdk.stats.AllRes: //calculates all res, doesnt exists trough
+ { // 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.MaxMana:
+ rval = this.getStat(sdk.stats.MaxMana);
+
+ if (rval > 446) {
+ return rval - 16777216; // Fix for negative values (Gull knife)
+ }
+
+ return rval;
+ 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 (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);
+ }
+
+ !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 (i = 0; i < temp[id].length; i += 1) {
+ // fix reference to undefined property temp[id][i].skill.
+ if (temp[id][i] !== undefined && temp[id][i].skill !== 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 (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 === undefined && (this.desc = this.description);
+
+ if (this.desc) {
+ temp = !!this.desc ? 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 === undefined && (this.desc = this.description);
+
+ if (this.desc) {
+ temp = !!this.desc ? 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;
+ }
+ }
+
+ if (subid === undefined) {
+ return this.getStat(id);
+ }
+
+ return this.getStat(id, subid);
};
Unit.prototype.getMobs = function ({ range, coll, type }) {
- if (this === undefined) return [];
- const _this = this;
- return getUnits(sdk.unittype.Monster)
- .filter(function (mon) {
- return mon.attackable && getDistance(_this, mon) < range
- && (!type || ((type & mon.spectype)))
- && (!coll || !checkCollision(_this, mon, coll));
- });
+ if (this === undefined) return [];
+ const _this = this;
+ return getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && getDistance(_this, mon) < range
+ && (!type || ((type & mon.spectype)))
+ && (!coll || !checkCollision(_this, mon, coll));
+ });
};
diff --git a/libs/SoloPlay/Functions/Quest.js b/libs/SoloPlay/Functions/Quest.js
index 57e2cdc1..e0611568 100644
--- a/libs/SoloPlay/Functions/Quest.js
+++ b/libs/SoloPlay/Functions/Quest.js
@@ -1,660 +1,701 @@
/**
* @filename Quest.js
* @author theBGuy
-* @credit Dark-f, JeanMax, https://github.com/SetupSonic/clean-sonic/blob/master/libs/sonic/common/Quest.js
+* @credit Dark-f, JeanMax, https://github.com/SetupSonic/clean-sonic/blob/master/libs/sonic/core/Quest.js
* @desc Miscellaneous quest tasks for leveling
*
*/
const Quest = {
- preReqs: function () {
- // horadric staff
- if (Pather.accessToAct(2) && !me.staff && !me.horadricstaff) {
- if (!me.amulet) {
- for (let getAmmy = 0; !me.amulet && getAmmy < 5; getAmmy++) {
- Loader.runScript("amulet");
- }
- }
-
- if (!me.shaft) {
- for (let getStaff = 0; !me.shaft && getStaff < 5; getStaff++) {
- Loader.runScript("staff");
- }
- }
- }
-
- if (Pather.accessToAct(3) && !me.travincal && !me.khalimswill) {
- if (!me.eye) {
- for (let getEye = 0; !me.eye && getEye < 5; getEye++) {
- Loader.runScript("eye");
- }
- }
-
- if (!me.heart) {
- for (let getHeart = 0; !me.heart && getHeart < 5; getHeart++) {
- Loader.runScript("heart");
- }
- }
-
- if (!me.brain) {
- for (let getBrain = 0; !me.brain && getBrain < 5; getBrain++) {
- Loader.runScript("brain");
- }
- }
- }
- },
-
- cubeItems: function (outcome, ...classids) {
- if (me.getItem(outcome)
- || outcome === sdk.quest.item.HoradricStaff && me.horadricstaff
- || outcome === sdk.quest.item.KhalimsWill && me.travincal) {
- return true;
- }
-
- !me.inTown && Town.goToTown();
- outcome === sdk.quest.item.HoradricStaff
- ? me.overhead("cubing staff")
- : outcome === sdk.quest.item.KhalimsWill
- ? me.overhead("cubing flail")
- : me.overhead("cubing " + outcome);
-
- Town.doChores();
- Town.openStash();
- me.findItems(-1, -1, sdk.storage.Cube) && Cubing.emptyCube();
-
- let cubingItem;
-
- for (let classid of classids) {
- cubingItem = me.getItem(classid);
-
- if (!cubingItem || !Storage.Cube.MoveTo(cubingItem)) {
- return false;
- }
- }
-
- Misc.poll(() => Cubing.openCube(), 5000, 1000);
-
- let wantedItem;
- let tick = getTickCount();
-
- while (getTickCount() - tick < 5000) {
- if (Cubing.openCube()) {
- transmute();
- delay(750 + me.ping);
-
- wantedItem = me.getItem(outcome);
-
- if (wantedItem) {
- Storage.Inventory.MoveTo(wantedItem);
- me.cancel();
-
- break;
- }
- }
- }
-
- me.cancel();
-
- outcome === sdk.items.quest.HoradricStaff && Town.npcInteract("cain");
-
- return me.getItem(outcome);
- },
-
- placeStaff: function () {
- if (me.horadricstaff) return true;
-
- let tick = getTickCount();
- let orifice = Misc.poll(() => Game.getObject(sdk.objects.HoradricStaffHolder));
- if (!orifice) return false;
-
- let hstaff = (me.getItem(sdk.items.quest.HoradricStaff) || Quest.cubeItems(sdk.items.quest.HoradricStaff, sdk.items.quest.ShaftoftheHoradricStaff, sdk.items.quest.ViperAmulet));
-
- if (hstaff) {
- if (hstaff.location !== sdk.storage.Inventory) {
- !me.inTown && Town.goToTown();
-
- if (!Storage.Inventory.CanFit(hstaff)) {
- Town.clearJunk();
- Town.sortInventory();
- }
-
- hstaff.isInStash && Town.openStash();
- hstaff.isInCube && Cubing.openCube();
- Storage.Inventory.MoveTo(hstaff);
- me.cancelUIFlags();
- Town.move("portalspot") && Pather.usePortal(null, me.name);
- }
- }
-
- Pather.moveToPreset(me.area, sdk.unittype.Object, 152);
- Misc.openChest(orifice);
-
- if (!hstaff) {
- if (getTickCount() - tick < 500) {
- delay(500 + me.ping);
- }
-
- return false;
- }
-
- clickItemAndWait(sdk.clicktypes.click.item.Left, hstaff);
- submitItem();
- delay(750 + me.ping);
-
- // Clear cursor of staff - credit @Jaenster
- let item = me.getItemsEx().filter((el) => el.isInInventory).first();
- let _b = [item.x, item.y, item.location], x = _b[0], y = _b[1], loc = _b[2];
- clickItemAndWait(sdk.clicktypes.click.item.Left, item);
- clickItemAndWait(sdk.clicktypes.click.item.Left, x, y, loc);
- delay(750 + me.ping);
-
- return true;
- },
-
- tyraelTomb: function () {
- Pather.moveTo(22629, 15714);
- Pather.moveTo(22609, 15707);
- Pather.moveTo(22579, 15704);
- Pather.moveTo(22577, 15649, 10);
- Pather.moveTo(22577, 15609, 10);
-
- let tyrael = Game.getNPC(NPC.Tyrael);
- if (!tyrael) return false;
-
- for (let talk = 0; talk < 3; talk += 1) {
- tyrael.distance > 3 && Pather.moveToUnit(tyrael);
-
- tyrael.interact();
- delay(1000 + me.ping);
- me.cancel();
-
- if (Pather.getPortal(null)) {
- me.cancel();
- break;
- }
- }
-
- !me.inTown && Town.goToTown();
-
- return true;
- },
-
- stashItem: function (classid) {
- let questItem = typeof classid === "object" ? classid : me.getItem(classid);
- if (!questItem) return false;
- myPrint("Stashing: " + questItem.fname.split("\n").reverse().join(" "));
-
- !me.inTown && Town.goToTown();
- Town.openStash();
-
- if (!Storage.Stash.CanFit(questItem)) {
- Town.sortStash(true);
-
- if (!Storage.Stash.CanFit(questItem)) return false;
- }
-
- Storage.Stash.MoveTo(questItem);
-
- return questItem.isInStash;
- },
-
- collectItem: function (classid, chestID) {
- if (me.getItem(classid)) return true;
-
- if (chestID !== undefined) {
- let chest = Game.getObject(chestID);
- if (!chest || !Misc.openChest(chest)) return false;
- }
-
- let questItem = Misc.poll(() => Game.getItem(classid), 3000, 100 + me.ping);
-
- if (Storage.Inventory.CanFit(questItem)) {
- Pickit.pickItem(questItem);
- } else {
- Town.visitTown();
- Pickit.pickItem(questItem);
- Pickit.pickItems();
- }
-
- return me.getItem(classid);
- },
-
- equipItem: function (classid, loc) {
- let questItem = me.getItem(classid);
- !getUIFlag(sdk.uiflags.Stash) && me.cancel();
-
- if (questItem) {
- me.dualWielding && Item.removeItem(sdk.body.LeftArm);
-
- if (!Item.equip(questItem, loc)) {
- Pickit.pickItems();
- console.log("ÿc8Kolbot-SoloPlayÿc0: failed to equip " + classid + " .(Quest.equipItem)");
- }
- } else {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Lost " + classid + " before trying to equip it. (Quest.equipItem)");
- return false;
- }
-
- if (me.itemoncursor) {
- let olditem = Game.getCursorUnit();
-
- if (olditem) {
- if (Storage.Inventory.CanFit(olditem)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Keeping weapon");
-
- Storage.Inventory.MoveTo(olditem);
- } else {
- me.cancel();
- console.log("ÿc8Kolbot-SoloPlayÿc0: No room to keep weapon");
-
- olditem.drop();
- }
- }
- }
-
- me.cancelUIFlags();
-
- return questItem.bodylocation === loc;
- },
-
- smashSomething: function (classid) {
- let tool = classid === sdk.objects.CompellingOrb
- ? sdk.items.quest.KhalimsWill
- : classid === sdk.quest.chest.HellForge
- ? sdk.items.quest.HellForgeHammer
- : null;
- let smashable = Game.getObject(classid);
-
- if (Item.getEquippedItem(sdk.body.RightArm).classid !== tool || !me.getItem(tool)) return false;
- if (!smashable) return false;
- let tick = getTickCount();
- let questTool = me.getItem(tool);
-
- while (me.getItem(tool)) {
- smashable.distance > 4 && Pather.moveToEx(smashable.x, smashable.y, {clearSettings: {allowClearing: false}});
- Skill.cast(sdk.skills.Attack, sdk.skills.hand.Right, smashable);
- smashable.interact();
-
- if (getTickCount() - tick > Time.seconds(30)) {
- console.warn("Timed out trying to smash quest object");
-
- return false;
- }
-
- if (!questTool.isEquipped) {
- break;
- }
-
- delay(750 + me.ping);
- }
-
- return !me.getItem(tool);
- },
-
- /**
- * @param {string} npcName
- * @param {number | number[]} action
- * @returns {boolean}
- */
- npcAction: function (npcName, action) {
- if (!npcName || !action) return false;
- !Array.isArray(action) && (action = [action]);
-
- !me.inTown && Town.goToTown();
- npcName = npcName.capitalize(true);
- Town.move(NPC[npcName]);
- let npc = Misc.poll(() => Game.getNPC(NPC[npcName]));
-
- Packet.flash(me.gid);
- delay(1 + me.ping * 2);
-
- if (npc && npc.openMenu()) {
- action.forEach(menuOption => Misc.useMenu(menuOption) && delay(100 + me.ping));
- return true;
- }
-
- return false;
- },
-
- // Akara reset for build change
- characterRespec: function () {
- if (me.respec || SetUp.currentBuild === SetUp.finalBuild) return;
-
- switch (true) {
- case me.charlvl >= CharInfo.respecOne && SetUp.currentBuild === "Start":
- case CharInfo.respecTwo > 0 && me.charlvl >= CharInfo.respecTwo && SetUp.currentBuild === "Stepping":
- case me.charlvl === SetUp.finalRespec() && SetUp.currentBuild === "Leveling":
- if (!me.den) {
- myPrint("time to respec, but den is incomplete");
- return;
- }
-
- let preSkillAmount = me.getStat(sdk.stats.NewSkills);
- let preStatAmount = me.getStat(sdk.stats.StatPts);
- let npc;
-
- Town.goToTown(1);
- myPrint("time to respec");
-
- for (let i = 0; i < 2; i++) {
- // attempt packet respec on first try
- if (i === 0) {
- npc = Town.npcInteract("akara");
- me.cancelUIFlags();
- delay(100 + me.ping);
- npc && sendPacket(1, sdk.packets.send.EntityAction, 4, 0, 4, npc.gid, 4, 0);
- } else {
- this.npcAction("akara", [sdk.menu.Respec, sdk.menu.Ok]);
- }
-
- Misc.checkQuest(sdk.quest.id.Respec, sdk.quest.states.Completed);
- delay(10 + me.ping * 2);
-
- if (me.respec || (me.getStat(sdk.stats.NewSkills) > preSkillAmount && me.getStat(sdk.stats.StatPts) > preStatAmount)) {
- myData.me.currentBuild = CharInfo.getActiveBuild();
- myData[sdk.difficulty.nameOf(me.diff).toLowerCase()].respecUsed = true;
- CharData.updateData("me", myData);
- delay(750 + me.ping * 2);
- Town.clearBelt();
- myPrint("respec done, restarting");
- delay(1000 + me.ping);
- scriptBroadcast("quit");
- }
- }
-
- break;
- }
- },
-
- // Credit dzik or laz unsure who for this
- useSocketQuest: function (item = undefined) {
- if (SetUp.finalBuild === "Socketmule") return false;
-
- try {
- if (!item || item.mode === sdk.items.mode.onGround) throw new Error("Couldn't find item");
- if (!me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.ReqComplete)) throw new Error("Quest unavailable");
- if (item.sockets > 0 || getBaseStat("items", item.classid, "gemsockets") === 0) throw new Error("Item cannot be socketed");
- if (!Storage.Inventory.CanFit(item)) throw new Error("(useSocketQuest) No space to get item back");
- if (me.act !== 5 || !me.inTown) {
- if (!Town.goToTown(5)) throw new Error("Failed to go to act 5");
- }
-
- if (item.isInStash && (!Town.openStash() || !Storage.Inventory.MoveTo(item))) {
- throw new Error("Failed to move item from stash to inventory");
- }
-
- let invo = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory);
- let slot = item.bodylocation;
-
- // Take note of all the items in the invo minus the item to socket
- for (let i = 0; i < invo.length; i++) {
- if (item.gid !== invo[i].gid) {
- invo[i] = invo[i].x + "/" + invo[i].y;
- }
- }
-
- if (!this.npcAction("larzuk", sdk.menu.AddSockets)) throw new Error("Failed to interact with Lazruk");
- if (!getUIFlag(sdk.uiflags.SubmitItem)) throw new Error("Failed to open SubmitItem screen");
- if (!item.toCursor()) throw new Error("Couldn't get item");
-
- submitItem();
- delay(500 + me.ping);
- Packet.questRefresh();
-
- item = false; // Delete item reference, it's not longer valid anyway
- let items = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory);
-
- for (let i = 0; i < items.length; i++) {
- if (invo.indexOf(items[i].x + "/" + items[i].y) === -1) {
- item = items[i];
- }
- }
-
- if (!item || item.sockets === 0) {
- me.itemoncursor && Storage.Stash.MoveTo(item);
- throw new Error("Failed to socket item");
- }
-
- Misc.logItem("Used my " + sdk.difficulty.nameOf(me.diff) + " socket quest on : ", item, null, true);
- D2Bot.printToConsole("Kolbot-SoloPlay :: Used my " + sdk.difficulty.nameOf(me.diff) + " socket quest on : " + item.name, sdk.colors.D2Bot.Gold);
- CharData.updateData(sdk.difficulty.nameOf(me.diff), "socketUsed", true);
- myData[sdk.difficulty.nameOf(me.diff).toLowerCase()].socketUsed = true;
- updateMyData();
-
- if (!slot && !item.isInStash) {
- // Move item back to stash
- if (Storage.Stash.CanFit(item)) {
- Town.move("stash");
- Storage.Stash.MoveTo(item);
- me.cancel();
- }
- }
-
- slot && Item.equip(item, slot);
- } catch (e) {
- myPrint(e);
- me.itemoncursor && Storage.Inventory.MoveTo(Game.getCursorUnit());
- me.cancelUIFlags();
-
- return false;
- }
-
- return true;
- },
-
- // Credit whoever did useSocketQuest, I modified that to come up with this
- useImbueQuest: function (item = undefined) {
- if (SetUp.finalBuild === "Imbuemule") return false;
-
- try {
- if (!item || item.mode === sdk.items.mode.onGround) throw new Error("Couldn't find item");
- if (!Misc.checkQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete)) throw new Error("Quest unavailable");
- if (item.sockets > 0 || item.quality > sdk.items.quality.Superior) throw new Error("Item cannot be imbued");
- if (!Storage.Inventory.CanFit(item)) throw new Error("(useImbueQuest) No space to get item back");
- if (me.act !== 1 || !me.inTown) {
- if (!Town.goToTown(1)) throw new Error("Failed to go to act 1");
- }
-
- if (item.isInStash && (!Town.openStash() || !Storage.Inventory.MoveTo(item))) {
- throw new Error("Failed to move item from stash to inventory");
- }
-
- let invo = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory);
- let slot = item.bodylocation;
-
- // Take note of all the items in the invo minus the item to socket
- for (let i = 0; i < invo.length; i++) {
- if (item.gid !== invo[i].gid) {
- invo[i] = invo[i].x + "/" + invo[i].y;
- }
- }
-
- if (!this.npcAction("charsi", sdk.menu.Imbue)) throw new Error("Failed to interact with Charsi");
- if (!getUIFlag(sdk.uiflags.SubmitItem)) throw new Error("Failed to open SubmitItem screen");
- if (!item.toCursor()) throw new Error("Couldn't get item");
-
- submitItem();
- delay(500 + me.ping);
- Packet.questRefresh();
-
- item = false; // Delete item reference, it's not longer valid anyway
- let items = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory);
-
- for (let i = 0; i < items.length; i++) {
- if (invo.indexOf(items[i].x + "/" + items[i].y) === -1) {
- item = items[i];
- }
- }
-
- if (!item || !item.rare) {
- me.itemoncursor && Storage.Stash.MoveTo(item);
- throw new Error("Failed to imbue item");
- }
-
- Misc.logItem("Used my " + sdk.difficulty.nameOf(me.diff) + " imbue quest on : ", item, null, true);
- D2Bot.printToConsole("Kolbot-SoloPlay :: Used my " + sdk.difficulty.nameOf(me.diff) + " imbue quest on : " + item.name, sdk.colors.D2Bot.Gold);
- CharData.updateData(sdk.difficulty.nameOf(me.diff), "imbueUsed", true);
- myData[sdk.difficulty.nameOf(me.diff).toLowerCase()].imbueUsed = true;
- updateMyData();
-
- if (!slot && !item.isInStash) {
- // Move item back to stash
- if (Storage.Stash.CanFit(item)) {
- Town.move("stash");
- Storage.Stash.MoveTo(item);
- me.cancel();
- }
- }
-
- slot && Item.equip(item, slot);
- } catch (e) {
- myPrint(e);
- me.itemoncursor && Storage.Inventory.MoveTo(Game.getCursorUnit());
- me.cancelUIFlags();
-
- return false;
- }
-
- return true;
- },
-
- unfinishedQuests: function () {
- const highestAct = me.highestAct;
- // Act 1
- // Tools of the trade
- let malus = me.getItem(sdk.items.quest.HoradricMalus);
- !!malus && Town.goToTown(1) && Town.npcInteract("charsi");
-
- let imbueItem = Misc.checkItemsForImbueing();
- (imbueItem) && Quest.useImbueQuest(imbueItem) && Item.autoEquip();
-
- // Drop wirts leg at startup
- let leg = me.getItem(sdk.items.quest.WirtsLeg);
- if (leg) {
- !me.inTown && Town.goToTown();
- leg.isInStash && Town.openStash() && Storage.Inventory.MoveTo(leg) && delay(300);
- getUIFlag(sdk.uiflags.Stash) && me.cancel();
- leg.drop();
- }
-
- // Act 2
- if (highestAct >= 2) {
- // Radament skill book
- let book = me.getItem(sdk.quest.item.BookofSkill);
- if (book) {
- book.isInStash && Town.openStash() && delay(300);
- Misc.poll(() => {
- book.use();
- if (me.getStat(sdk.stats.NewSkills) > 0) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: used Radament skill book");
- AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
- return true;
- }
- return false;
- }, 1000, 100);
- }
- }
-
- // Act 3
- if (highestAct >= 3) {
- // Figurine -> Golden Bird
- if (me.getItem(sdk.items.quest.AJadeFigurine)) {
- myPrint("starting jade figurine");
- Town.goToTown(3) && Town.npcInteract("meshif");
- }
-
- // Golden Bird -> Ashes
- (me.getItem(sdk.items.quest.TheGoldenBird)) && Town.goToTown(3) && Town.npcInteract("alkor");
-
- // Potion of life
- let pol = me.getItem(sdk.items.quest.PotofLife);
- if (pol) {
- pol.isInStash && Town.openStash() && delay(300);
- pol.use() && console.log("ÿc8Kolbot-SoloPlayÿc0: used potion of life");
- }
-
- // LamEssen's Tome
- let tome = me.getItem(sdk.items.quest.LamEsensTome);
- if (tome) {
- !me.inTown && Town.goToTown(3);
- tome.isInStash && Town.openStash() && Storage.Inventory.MoveTo(tome) && delay(300);
- Town.npcInteract("alkor") && delay(300);
- me.getStat(sdk.stats.StatPts) > 0 && AutoStat.init(Config.AutoStat.Build, Config.AutoStat.Save, Config.AutoStat.BlockChance, Config.AutoStat.UseBulk);
- console.log("ÿc8Kolbot-SoloPlayÿc0: LamEssen Tome completed");
- }
-
- // Free Lam Essen quest
- if (Pather.accessToAct(3) && !me.getQuest(sdk.quest.id.LamEsensTome, sdk.quest.states.Completed)) {
- !me.inArea(sdk.areas.KurastDocktown) && Town.goToTown(3);
- Town.move("alkor");
- let unit = getUnit(1, "alkor");
- if (unit) {
- sendPacket(1, sdk.packets.send.QuestMessage, 4, unit.gid, 4, 564);
- delay((me.ping || 0) * 2 + 200);
- unit.openMenu();
- me.cancel();
- me.cancel();
- }
- }
-
- // Remove Khalim's Will if quest not completed and restarting run.
- let kw = me.getItem(sdk.items.quest.KhalimsWill);
- if (kw) {
- if (Item.getEquippedItem(sdk.body.RightArm).classid === sdk.items.quest.KhalimsWill) {
- Town.clearInventory();
- delay(500);
- Quest.stashItem(sdk.items.quest.KhalimsWill);
- console.log("ÿc8Kolbot-SoloPlayÿc0: removed khalims will");
- Item.autoEquip();
- }
- }
-
- // Killed council but haven't talked to cain
- if (!Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed) && Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, 4)) {
- me.overhead("Finishing Travincal by talking to cain");
- Town.goToTown(3) && Town.npcInteract("cain") && delay(300);
- me.cancel();
- }
- }
-
- // Act 4
- if (highestAct >= 4) {
- // Drop hellforge hammer and soulstone at startup
- let hammer = me.getItem(sdk.items.quest.HellForgeHammer);
- if (hammer) {
- !me.inTown && Town.goToTown();
- hammer.isInStash && Town.openStash() && Storage.Inventory.MoveTo(hammer) && delay(300);
- getUIFlag(sdk.uiflags.Stash) && me.cancel();
- hammer.drop();
- }
-
- let soulstone = me.getItem(sdk.items.quest.MephistosSoulstone);
- if (soulstone) {
- !me.inTown && Town.goToTown();
- soulstone.isInStash && Town.openStash() && Storage.Inventory.MoveTo(soulstone) && delay(300);
- getUIFlag(sdk.uiflags.Stash) && me.cancel();
- soulstone.drop();
- }
- }
-
- // Act 5
- if (highestAct === 5) {
- let socketItem = Misc.checkItemsForSocketing();
- !!socketItem && Quest.useSocketQuest(socketItem);
-
- // Scroll of resistance
- let sor = me.getItem(sdk.items.quest.ScrollofResistance);
- if (sor) {
- sor.isInStash && Town.openStash() && delay(300);
- sor.use() && console.log("ÿc8Kolbot-SoloPlayÿc0: used scroll of resistance");
- }
- }
-
- Misc.checkSocketables();
-
- Town.heal();
- me.cancelUIFlags();
-
- return true;
- },
+ preReqs: function () {
+ /**
+ * @param {string} task
+ * @param {function(): boolean} req
+ * @returns {boolean}
+ */
+ const getReq = function (task, req = () => true) {
+ for (let i = 0; i < 5 && !req(); i++) {
+ Loader.runScript(task);
+ }
+ return req();
+ };
+
+ if (me.accessToAct(2)) {
+ !me.cube && getReq("cube", function () { return me.cube; });
+
+ if (!me.staff && !me.horadricstaff) {
+ !me.amulet && getReq("amulet", function () { return me.amulet; });
+ !me.shaft && getReq("staff", function () { return me.shaft; });
+ }
+ }
+
+ if (me.accessToAct(3) && !me.travincal && !me.khalimswill) {
+ !me.eye && getReq("eye", function () { return me.eye; });
+ !me.heart && getReq("heart", function () { return me.heart; });
+ !me.brain && getReq("brain", function () { return me.brain; });
+ }
+ },
+
+ /**
+ * @param {number} outcome
+ * @param {...number} classids
+ */
+ cubeItems: function (outcome, ...classids) {
+ if (me.getItem(outcome)
+ || outcome === sdk.quest.item.HoradricStaff && me.horadricstaff
+ || outcome === sdk.quest.item.KhalimsWill && me.travincal) {
+ return true;
+ }
+
+ !me.inTown && Town.goToTown();
+ outcome === sdk.quest.item.HoradricStaff
+ ? me.overhead("cubing staff")
+ : outcome === sdk.quest.item.KhalimsWill
+ ? me.overhead("cubing flail")
+ : me.overhead("cubing " + outcome);
+
+ Town.doChores();
+ Town.openStash();
+ Cubing.emptyCube();
+
+ for (let classid of classids) {
+ let cubingItem = me.getItem(classid);
+
+ if (!cubingItem || !Storage.Cube.MoveTo(cubingItem)) {
+ return false;
+ }
+ }
+
+ Misc.poll(function () {
+ return Cubing.openCube();
+ }, Time.seconds(5), 1000);
+
+ let wantedItem;
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < 5000) {
+ if (Cubing.openCube()) {
+ transmute();
+ delay(750 + me.ping);
+
+ wantedItem = me.getItem(outcome);
+
+ if (wantedItem) {
+ Storage.Inventory.MoveTo(wantedItem);
+ me.cancel();
+
+ break;
+ }
+ }
+ }
+
+ me.cancel();
+
+ outcome === sdk.items.quest.HoradricStaff && Town.npcInteract("cain");
+
+ return me.getItem(outcome);
+ },
+
+ placeStaff: function () {
+ if (me.horadricstaff) return true;
+
+ let tick = getTickCount();
+ let orifice = Misc.poll(function () {
+ return Game.getObject(sdk.objects.HoradricStaffHolder);
+ });
+ if (!orifice) return false;
+
+ let hstaff = (
+ me.getItem(sdk.items.quest.HoradricStaff)
+ || Quest.cubeItems(
+ sdk.items.quest.HoradricStaff,
+ sdk.items.quest.ShaftoftheHoradricStaff,
+ sdk.items.quest.ViperAmulet
+ )
+ );
+
+ if (hstaff) {
+ if (hstaff.location !== sdk.storage.Inventory) {
+ !me.inTown && Town.goToTown();
+
+ if (!Storage.Inventory.CanFit(hstaff)) {
+ Town.clearJunk();
+ me.sortInventory();
+ }
+
+ hstaff.isInStash && Town.openStash();
+ hstaff.isInCube && Cubing.openCube();
+ Storage.Inventory.MoveTo(hstaff);
+ me.cancelUIFlags();
+ Town.move("portalspot") && Pather.usePortal(null, me.name);
+ }
+ }
+
+ Pather.moveToPreset(me.area, sdk.unittype.Object, 152);
+ Misc.openChest(orifice);
+
+ if (!hstaff) {
+ if (getTickCount() - tick < 500) {
+ delay(500 + me.ping);
+ }
+
+ return false;
+ }
+
+ clickItemAndWait(sdk.clicktypes.click.item.Left, hstaff);
+ submitItem();
+ delay(750 + me.ping);
+
+ // Clear cursor of staff - credit @Jaenster
+ let item = me.getItemsEx().filter((el) => el.isInInventory).first();
+ let _b = [item.x, item.y, item.location], x = _b[0], y = _b[1], loc = _b[2];
+ clickItemAndWait(sdk.clicktypes.click.item.Left, item);
+ clickItemAndWait(sdk.clicktypes.click.item.Left, x, y, loc);
+ delay(750 + me.ping);
+
+ return true;
+ },
+
+ tyraelTomb: function () {
+ Pather.moveTo(22629, 15714);
+ Pather.moveTo(22609, 15707);
+ Pather.moveTo(22579, 15704);
+ Pather.moveTo(22577, 15649, 10);
+ Pather.moveTo(22577, 15609, 10);
+
+ let tyrael = Game.getNPC(NPC.Tyrael);
+ if (!tyrael) return false;
+
+ for (let talk = 0; talk < 3; talk += 1) {
+ tyrael.distance > 3 && Pather.moveToUnit(tyrael);
+
+ tyrael.interact();
+ delay(1000 + me.ping);
+ me.cancel();
+
+ if (Pather.getPortal(null)) {
+ me.cancel();
+ break;
+ }
+ }
+
+ !me.inTown && Town.goToTown();
+
+ return true;
+ },
+
+ /** @param {number} classid */
+ stashItem: function (classid) {
+ let questItem = typeof classid === "object" ? classid : me.getItem(classid);
+ if (!questItem) return false;
+ myPrint("Stashing: " + questItem.prettyPrint);
+
+ !me.inTown && Town.goToTown();
+ Town.openStash();
+
+ if (!Storage.Stash.CanFit(questItem)) {
+ Town.sortStash(true);
+
+ if (!Storage.Stash.CanFit(questItem)) return false;
+ }
+
+ Storage.Stash.MoveTo(questItem);
+
+ return questItem.isInStash;
+ },
+
+ collectItem: function (classid, chestID) {
+ if (me.getItem(classid)) return true;
+
+ if (chestID !== undefined) {
+ let chest = Game.getObject(chestID);
+ if (!chest || !Misc.openChest(chest)) return false;
+ }
+
+ let questItem = Misc.poll(function () {
+ return Game.getItem(classid);
+ }, 3000, 100 + me.ping);
+
+ if (Storage.Inventory.CanFit(questItem)) {
+ Pickit.pickItem(questItem);
+ } else {
+ Town.visitTown();
+ Pickit.pickItem(questItem);
+ Pickit.pickItems();
+ }
+
+ return me.getItem(classid);
+ },
+
+ /**
+ * @param {number} classid
+ * @param {number} loc
+ */
+ equipItem: function (classid, loc) {
+ let questItem = me.getItem(classid);
+ !getUIFlag(sdk.uiflags.Stash) && me.cancel();
+
+ if (questItem) {
+ me.dualWielding && Item.removeItem(sdk.body.LeftArm);
+ if (questItem.isInStash && !Town.openStash()) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: failed to open stash. (Quest.equipItem)");
+ Item.autoEquip();
+ return false;
+ }
+
+ if (!questItem.equip(loc)) {
+ Pickit.pickItems();
+ console.log("ÿc8Kolbot-SoloPlayÿc0: failed to equip " + classid + " .(Quest.equipItem)");
+ }
+ } else {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Lost " + classid + " before trying to equip it. (Quest.equipItem)");
+ return false;
+ }
+
+ if (me.itemoncursor) {
+ let olditem = Game.getCursorUnit();
+
+ if (olditem) {
+ if (Storage.Inventory.CanFit(olditem)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Keeping weapon");
+
+ Storage.Inventory.MoveTo(olditem);
+ } else {
+ me.cancel();
+ console.log("ÿc8Kolbot-SoloPlayÿc0: No room to keep weapon");
+
+ olditem.drop();
+ }
+ }
+ }
+
+ me.cancelUIFlags();
+
+ return questItem.bodylocation === loc;
+ },
+
+ /** @param {number} classid */
+ smashSomething: function (classid) {
+ let tool = classid === sdk.objects.CompellingOrb
+ ? sdk.items.quest.KhalimsWill
+ : classid === sdk.quest.chest.HellForge
+ ? sdk.items.quest.HellForgeHammer
+ : null;
+ let smashable = Game.getObject(classid);
+
+ if (me.equipped.get(sdk.body.RightArm).classid !== tool || !me.getItem(tool)) return false;
+ if (!smashable) return false;
+ let tick = getTickCount();
+ let questTool = me.getItem(tool);
+
+ while (me.getItem(tool)) {
+ if (smashable.distance > 4) {
+ Pather.moveToEx(smashable.x, smashable.y, { clearSettings: { allowClearing: false } });
+ }
+ Skill.cast(sdk.skills.Attack, sdk.skills.hand.Right, smashable);
+ smashable.interact();
+
+ if (getTickCount() - tick > Time.seconds(30)) {
+ console.warn("Timed out trying to smash quest object");
+
+ return false;
+ }
+
+ if (!questTool.isEquipped) {
+ break;
+ }
+
+ delay(750 + me.ping);
+ }
+
+ return !me.getItem(tool);
+ },
+
+ /**
+ * @param {string} npcName
+ * @param {number | number[]} action
+ * @returns {boolean}
+ */
+ npcAction: function (npcName, action) {
+ if (!npcName || !action) return false;
+ !Array.isArray(action) && (action = [action]);
+
+ !me.inTown && Town.goToTown();
+ npcName = npcName.capitalize(true);
+ Town.move(NPC[npcName]);
+ let npc = Misc.poll(function () {
+ return Game.getNPC(NPC[npcName]);
+ });
+
+ Packet.flash(me.gid);
+ delay(1 + me.ping * 2);
+
+ if (npc && npc.openMenu()) {
+ action.forEach(function (menuOption) {
+ Misc.useMenu(menuOption) && delay(100 + me.ping);
+ });
+ return true;
+ }
+
+ return false;
+ },
+
+ // Akara reset for build change
+ characterRespec: function () {
+ if (me.respec || SetUp.currentBuild === SetUp.finalBuild) return;
+
+ switch (true) {
+ case me.charlvl >= CharInfo.respecOne && SetUp.currentBuild === "Start":
+ case CharInfo.respecTwo > 0 && me.charlvl >= CharInfo.respecTwo && SetUp.currentBuild === "Stepping":
+ case me.charlvl === SetUp.finalRespec() && SetUp.currentBuild === "Leveling":
+ if (!me.den) {
+ myPrint("time to respec, but den is incomplete");
+ return;
+ }
+
+ let preSkillAmount = me.getStat(sdk.stats.NewSkills);
+ let preStatAmount = me.getStat(sdk.stats.StatPts);
+ let npc;
+
+ Town.goToTown(1);
+ myPrint("time to respec");
+
+ for (let i = 0; i < 2; i++) {
+ // attempt packet respec on first try
+ if (i === 0) {
+ npc = Town.npcInteract("akara");
+ me.cancelUIFlags();
+ delay(100 + me.ping);
+ npc && sendPacket(1, sdk.packets.send.EntityAction, 4, 0, 4, npc.gid, 4, 0);
+ } else {
+ this.npcAction("akara", [sdk.menu.Respec, sdk.menu.Ok]);
+ }
+
+ Misc.checkQuest(sdk.quest.id.Respec, sdk.quest.states.Completed);
+ delay(10 + me.ping * 2);
+
+ if (me.respec || (me.getStat(sdk.stats.NewSkills) > preSkillAmount
+ && me.getStat(sdk.stats.StatPts) > preStatAmount)) {
+ me.data.currentBuild = CharInfo.getActiveBuild();
+ me.data[sdk.difficulty.nameOf(me.diff).toLowerCase()].respecUsed = true;
+ CharData.updateData("me", me.data);
+ delay(750 + me.ping * 2);
+ me.clearBelt();
+ myPrint("respec done, restarting");
+ delay(1000 + me.ping);
+ scriptBroadcast("quit");
+ }
+ }
+
+ break;
+ }
+ },
+
+ // Credit dzik or laz unsure who for this
+ /** @param {ItemUnit} item */
+ useSocketQuest: function (item = undefined) {
+ if (SetUp.finalBuild === "Socketmule") return false;
+
+ try {
+ if (!item || item.mode === sdk.items.mode.onGround) throw new Error("Couldn't find item");
+ if (!me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.ReqComplete)) throw new Error("Quest unavailable");
+ if (item.sockets > 0 || getBaseStat("items", item.classid, "gemsockets") === 0) throw new Error("Item cannot be socketed");
+ if (!Storage.Inventory.CanFit(item)) throw new Error("(useSocketQuest) No space to get item back");
+ if (me.act !== 5 || !me.inTown) {
+ if (!Town.goToTown(5)) throw new Error("Failed to go to act 5");
+ }
+
+ if (item.isInStash && (!Town.openStash() || !Storage.Inventory.MoveTo(item))) {
+ throw new Error("Failed to move item from stash to inventory");
+ }
+
+ let invo = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory);
+ let slot = item.bodylocation;
+
+ // Take note of all the items in the invo minus the item to socket
+ for (let i = 0; i < invo.length; i++) {
+ if (item.gid !== invo[i].gid) {
+ invo[i] = invo[i].x + "/" + invo[i].y;
+ }
+ }
+
+ if (!this.npcAction("larzuk", sdk.menu.AddSockets)) throw new Error("Failed to interact with Lazruk");
+ if (!getUIFlag(sdk.uiflags.SubmitItem)) throw new Error("Failed to open SubmitItem screen");
+ if (!item.toCursor()) throw new Error("Couldn't get item");
+
+ submitItem();
+ delay(500 + me.ping);
+ Packet.questRefresh();
+
+ item = false; // Delete item reference, it's not longer valid anyway
+ let items = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory);
+
+ for (let i = 0; i < items.length; i++) {
+ if (invo.indexOf(items[i].x + "/" + items[i].y) === -1) {
+ item = items[i];
+ }
+ }
+
+ if (!item || item.sockets === 0) {
+ me.itemoncursor && Storage.Stash.MoveTo(item);
+ throw new Error("Failed to socket item");
+ }
+
+ Item.logItem("Used my " + sdk.difficulty.nameOf(me.diff) + " socket quest on : ", item, null, true);
+ D2Bot.printToConsole("Kolbot-SoloPlay :: Used my " + sdk.difficulty.nameOf(me.diff) + " socket quest on : " + item.name, sdk.colors.D2Bot.Gold);
+ CharData.updateData(sdk.difficulty.nameOf(me.diff), "socketUsed", true);
+ me.data[sdk.difficulty.nameOf(me.diff).toLowerCase()].socketUsed = true;
+ me.update();
+
+ if (!slot && !item.isInStash) {
+ // Move item back to stash
+ if (Storage.Stash.CanFit(item)) {
+ Town.move("stash");
+ Storage.Stash.MoveTo(item);
+ me.cancel();
+ }
+ }
+
+ slot && Item.equip(item, slot);
+ } catch (e) {
+ myPrint(e);
+ me.itemoncursor && Storage.Inventory.MoveTo(Game.getCursorUnit());
+ me.cancelUIFlags();
+
+ return false;
+ }
+
+ return true;
+ },
+
+ // Credit whoever did useSocketQuest, I modified that to come up with this
+ /** @param {ItemUnit} item */
+ useImbueQuest: function (item = undefined) {
+ if (SetUp.finalBuild === "Imbuemule") return false;
+
+ try {
+ if (!item || item.mode === sdk.items.mode.onGround) throw new Error("Couldn't find item");
+ if (!Misc.checkQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete)) throw new Error("Quest unavailable");
+ if (item.sockets > 0 || item.quality > sdk.items.quality.Superior) throw new Error("Item cannot be imbued");
+ if (!Storage.Inventory.CanFit(item)) throw new Error("(useImbueQuest) No space to get item back");
+ if (me.act !== 1 || !me.inTown) {
+ if (!Town.goToTown(1)) throw new Error("Failed to go to act 1");
+ }
+
+ if (item.isInStash && (!Town.openStash() || !Storage.Inventory.MoveTo(item))) {
+ throw new Error("Failed to move item from stash to inventory");
+ }
+
+ let invo = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory);
+ let slot = item.bodylocation;
+
+ // Take note of all the items in the invo minus the item to socket
+ for (let i = 0; i < invo.length; i++) {
+ if (item.gid !== invo[i].gid) {
+ invo[i] = invo[i].x + "/" + invo[i].y;
+ }
+ }
+
+ if (!this.npcAction("charsi", sdk.menu.Imbue)) throw new Error("Failed to interact with Charsi");
+ if (!getUIFlag(sdk.uiflags.SubmitItem)) throw new Error("Failed to open SubmitItem screen");
+ if (!item.toCursor()) throw new Error("Couldn't get item");
+
+ submitItem();
+ delay(500 + me.ping);
+ Packet.questRefresh();
+
+ item = false; // Delete item reference, it's not longer valid anyway
+ let items = me.findItems(-1, sdk.items.mode.inStorage, sdk.storage.Inventory);
+
+ for (let i = 0; i < items.length; i++) {
+ if (invo.indexOf(items[i].x + "/" + items[i].y) === -1) {
+ item = items[i];
+ }
+ }
+
+ if (!item || !item.rare) {
+ me.itemoncursor && Storage.Stash.MoveTo(item);
+ throw new Error("Failed to imbue item");
+ }
+
+ Item.logItem("Used my " + sdk.difficulty.nameOf(me.diff) + " imbue quest on : ", item, null, true);
+ D2Bot.printToConsole("Kolbot-SoloPlay :: Used my " + sdk.difficulty.nameOf(me.diff) + " imbue quest on : " + item.name, sdk.colors.D2Bot.Gold);
+ CharData.updateData(sdk.difficulty.nameOf(me.diff), "imbueUsed", true);
+ me.data[sdk.difficulty.nameOf(me.diff).toLowerCase()].imbueUsed = true;
+ me.update();
+
+ if (!slot && !item.isInStash) {
+ // Move item back to stash
+ if (Storage.Stash.CanFit(item)) {
+ Town.move("stash");
+ Storage.Stash.MoveTo(item);
+ me.cancel();
+ }
+ }
+
+ slot && Item.equip(item, slot);
+ } catch (e) {
+ myPrint(e);
+ me.itemoncursor && Storage.Inventory.MoveTo(Game.getCursorUnit());
+ me.cancelUIFlags();
+
+ return false;
+ }
+
+ return true;
+ },
+
+ unfinishedQuests: function () {
+ const highestAct = me.highestAct;
+ // Act 1
+ // Tools of the trade
+ let malus = me.getItem(sdk.items.quest.HoradricMalus);
+ !!malus && Town.goToTown(1) && Town.npcInteract("charsi");
+
+ let imbueItem = Misc.checkItemsForImbueing();
+ (imbueItem) && Quest.useImbueQuest(imbueItem) && Item.autoEquip();
+
+ // Drop wirts leg at startup
+ let leg = me.getItem(sdk.items.quest.WirtsLeg);
+ if (leg) {
+ !me.inTown && Town.goToTown();
+ leg.isInStash && Town.openStash() && Storage.Inventory.MoveTo(leg) && delay(300);
+ getUIFlag(sdk.uiflags.Stash) && me.cancel();
+ leg.drop();
+ }
+
+ // Act 2
+ if (highestAct >= 2) {
+ // Radament skill book
+ let book = me.getItem(sdk.quest.item.BookofSkill);
+ if (book) {
+ book.isInStash && Town.openStash() && delay(300);
+ Misc.poll(function () {
+ book.use();
+ if (me.getStat(sdk.stats.NewSkills) > 0) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: used Radament skill book");
+ AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
+ return true;
+ }
+ return false;
+ }, 1000, 100);
+ }
+ }
+
+ // Act 3
+ if (highestAct >= 3) {
+ // Figurine -> Golden Bird
+ if (me.getItem(sdk.items.quest.AJadeFigurine)) {
+ myPrint("starting jade figurine");
+ Town.goToTown(3) && Town.npcInteract("meshif");
+ }
+
+ // Golden Bird -> Ashes
+ (me.getItem(sdk.items.quest.TheGoldenBird)) && Town.goToTown(3) && Town.npcInteract("alkor");
+
+ // Potion of life
+ let pol = me.getItem(sdk.items.quest.PotofLife);
+ if (pol) {
+ pol.isInStash && Town.openStash() && delay(300);
+ pol.use() && console.log("ÿc8Kolbot-SoloPlayÿc0: used potion of life");
+ }
+
+ // LamEssen's Tome
+ let tome = me.getItem(sdk.items.quest.LamEsensTome);
+ if (tome) {
+ !me.inTown && Town.goToTown(3);
+ tome.isInStash && Town.openStash() && Storage.Inventory.MoveTo(tome) && delay(300);
+ Town.npcInteract("alkor") && delay(300);
+ if (me.getStat(sdk.stats.StatPts) > 0) {
+ AutoStat.init(
+ Config.AutoStat.Build,
+ Config.AutoStat.Save,
+ Config.AutoStat.BlockChance,
+ Config.AutoStat.UseBulk
+ );
+ }
+ console.log("ÿc8Kolbot-SoloPlayÿc0: LamEssen Tome completed");
+ }
+
+ // Free Lam Essen quest
+ if (me.accessToAct(3) && !me.getQuest(sdk.quest.id.LamEsensTome, sdk.quest.states.Completed)) {
+ !me.inArea(sdk.areas.KurastDocktown) && Town.goToTown(3);
+ Town.move("alkor");
+ let unit = getUnit(1, "alkor");
+ if (unit) {
+ sendPacket(1, sdk.packets.send.QuestMessage, 4, unit.gid, 4, 564);
+ delay((me.ping || 0) * 2 + 200);
+ unit.openMenu();
+ me.cancel();
+ me.cancel();
+ }
+ }
+
+ // Remove Khalim's Will if quest not completed and restarting run.
+ let kw = me.getItem(sdk.items.quest.KhalimsWill);
+ if (kw) {
+ if (me.equipped.get(sdk.body.RightArm).classid === sdk.items.quest.KhalimsWill) {
+ Town.clearInventory();
+ delay(500);
+ Quest.stashItem(sdk.items.quest.KhalimsWill);
+ console.log("ÿc8Kolbot-SoloPlayÿc0: removed khalims will");
+ Item.autoEquip();
+ }
+ }
+
+ // Killed council but haven't talked to cain
+ if (!Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed)
+ && Misc.checkQuest(sdk.quest.id.TheBlackenedTemple, 4)) {
+ me.overhead("Finishing Travincal by talking to cain");
+ Town.goToTown(3) && Town.npcInteract("cain") && delay(300);
+ me.cancel();
+ }
+ }
+
+ // Act 4
+ if (highestAct >= 4) {
+ // Drop hellforge hammer and soulstone at startup
+ let hammer = me.getItem(sdk.items.quest.HellForgeHammer);
+ if (hammer) {
+ !me.inTown && Town.goToTown();
+ hammer.isInStash && Town.openStash() && Storage.Inventory.MoveTo(hammer) && delay(300);
+ getUIFlag(sdk.uiflags.Stash) && me.cancel();
+ hammer.drop();
+ }
+
+ let soulstone = me.getItem(sdk.items.quest.MephistosSoulstone);
+ if (soulstone) {
+ !me.inTown && Town.goToTown();
+ soulstone.isInStash && Town.openStash() && Storage.Inventory.MoveTo(soulstone) && delay(300);
+ getUIFlag(sdk.uiflags.Stash) && me.cancel();
+ soulstone.drop();
+ }
+ }
+
+ // Act 5
+ if (highestAct === 5) {
+ let socketItem = Misc.checkItemsForSocketing();
+ !!socketItem && Quest.useSocketQuest(socketItem);
+
+ // Scroll of resistance
+ let sor = me.getItem(sdk.items.quest.ScrollofResistance);
+ if (sor) {
+ sor.isInStash && Town.openStash() && delay(300);
+ sor.use() && console.log("ÿc8Kolbot-SoloPlayÿc0: used scroll of resistance");
+ }
+
+ if (Misc.checkQuest(sdk.quest.id.PrisonofIce, 7/** Used the scroll */)
+ && !Misc.checkQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.Completed)) {
+ // never talked to anya after drinking potion, lets do that
+ Town.npcInteract("anya");
+ }
+ }
+
+ Misc.checkSocketables();
+
+ Town.heal();
+ me.cancelUIFlags();
+
+ return true;
+ },
};
diff --git a/libs/SoloPlay/Functions/RunewordsOverrides.js b/libs/SoloPlay/Functions/RunewordsOverrides.js
index 0979b505..0e65ddb6 100644
--- a/libs/SoloPlay/Functions/RunewordsOverrides.js
+++ b/libs/SoloPlay/Functions/RunewordsOverrides.js
@@ -5,63 +5,140 @@
*
*/
-!includeIfNotIncluded("common/Runewords.js");
-
-// Don't use ladder-only on NL
-Runeword.Brand = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Jah, sdk.items.runes.Lo, sdk.items.runes.Mal, sdk.items.runes.Gul] : false; // Jah + Lo + Mal + Gul
-Runeword.Death = (me.ladder || Developer.addLadderRW) ? [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
-Runeword.Destruction = (me.ladder || Developer.addLadderRW) ? [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
-Runeword.Dragon = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Sur, sdk.items.runes.Lo, sdk.items.runes.Sol] : false; // Sur + Lo + Sol
-Runeword.Dream = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Io, sdk.items.runes.Jah, sdk.items.runes.Pul] : false; // Io + Jah + Pul
-Runeword.Edge = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Tir, sdk.items.runes.Tal, sdk.items.runes.Amn] : false; // Tir + Tal + Amn
-Runeword.Faith = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Ohm, sdk.items.runes.Jah, sdk.items.runes.Lem, sdk.items.runes.Eld] : false; // Ohm + Jah + Lem + Eld
-Runeword.Fortitude = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.El, sdk.items.runes.Sol, sdk.items.runes.Dol, sdk.items.runes.Lo] : false; // El + Sol + Dol + Lo
-Runeword.Grief = (me.ladder || Developer.addLadderRW) ? [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
-Runeword.Harmony = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Tir, sdk.items.runes.Ith, sdk.items.runes.Sol, sdk.items.runes.Ko] : false; // Tir + Ith + Sol + Ko
-Runeword.Ice = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Amn, sdk.items.runes.Shael, sdk.items.runes.Jah, sdk.items.runes.Lo] : false; // Amn + Shael + Jah + Lo
-Runeword.Infinity = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Ber, sdk.items.runes.Mal, sdk.items.runes.Ber, sdk.items.runes.Ist] : false; // Ber + Mal + Ber + Ist
-Runeword.Insight = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Ral, sdk.items.runes.Tir, sdk.items.runes.Tal, sdk.items.runes.Sol] : false; // Ral + Tir + Tal + Sol
-Runeword.LastWish = (me.ladder || Developer.addLadderRW) ? [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
-Runeword.Lawbringer = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Amn, sdk.items.runes.Lem, sdk.items.runes.Ko] : false; // Amn + Lem + Ko
-Runeword.Oath = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Shael, sdk.items.runes.Pul, sdk.items.runes.Mal, sdk.items.runes.Lum] : false; // Shael + Pul + Mal + Lum
-Runeword.Obedience = (me.ladder || Developer.addLadderRW) ? [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
-Runeword.Phoenix = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Vex, sdk.items.runes.Vex, sdk.items.runes.Lo, sdk.items.runes.Jah] : false; // Vex + Vex + Lo + Jah
-Runeword.Pride = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Cham, sdk.items.runes.Sur, sdk.items.runes.Io, sdk.items.runes.Lo] : false; // Cham + Sur + Io + Lo
-Runeword.Rift = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Hel, sdk.items.runes.Ko, sdk.items.runes.Lem, sdk.items.runes.Gul] : false; // Hel + Ko + Lem + Gul
-Runeword.Spirit = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Tal, sdk.items.runes.Thul, sdk.items.runes.Ort, sdk.items.runes.Amn] : false; // Tal + Thul + Ort + Amn
-Runeword.VoiceofReason = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Lem, sdk.items.runes.Ko, sdk.items.runes.El, sdk.items.runes.Eld] : false; // Lem + Ko + El + Eld
-Runeword.Wrath = (me.ladder || Developer.addLadderRW) ? [sdk.items.runes.Pul, sdk.items.runes.Lum, sdk.items.runes.Ber, sdk.items.runes.Mal] : false; // Pul + Lum + Ber + Mal
-Runeword.PDiamondShield = [sdk.items.gems.Perfect.Diamond, sdk.items.gems.Perfect.Diamond, sdk.items.gems.Perfect.Diamond];
+includeIfNotIncluded("core/Runewords.js");
+includeIfNotIncluded("SoloPlay/Functions/NTIPOverrides.js");
+Runeword.PDiamondShield = Runeword.addRuneword(
+ "PDiamondShield", 3,
+ [sdk.items.gems.Perfect.Diamond, sdk.items.gems.Perfect.Diamond, sdk.items.gems.Perfect.Diamond],
+ [sdk.items.type.AnyShield]
+);
+
+Runewords.pickitEntries = new NTIPList();
+
+Runewords.init = function () {
+ if (!Config.MakeRunewords) return;
+
+ Runewords.pickitEntries.clear();
+
+ // initiate pickit entries
+ for (let entry of Config.KeepRunewords) {
+ let info = {
+ file: "Character Config",
+ line: entry
+ };
+
+ let parsedLine = NTIP.ParseLineInt(entry, info);
+ if (parsedLine) {
+ Runewords.pickitEntries.add(parsedLine, info);
+ }
+ }
+
+ // change text to classid
+ for (let i = 0; i < Config.Runewords.length; i += 1) {
+ const [runeword, base] = Config.Runewords[i];
+
+ if (!runeword.ladderRestricted()) {
+ if (isNaN(base)) {
+ if (NTIPAliasClassID.hasOwnProperty(base.replace(/\s+/g, "").toLowerCase())) {
+ Config.Runewords[i][1] = NTIPAliasClassID[base.replace(/\s+/g, "").toLowerCase()];
+ } else {
+ Misc.errorReport("ÿc1Invalid runewords entry:ÿc0 " + base);
+ Config.Runewords.splice(i, 1);
+
+ i -= 1;
+ }
+ }
+ }
+ }
+
+ this.buildLists();
+};
+
+Runewords.checkRunewords = function () {
+ // keep a const reference of our items so failed checks don't remove items from the list
+ const itemsRef = me.findItems(-1, sdk.items.mode.inStorage);
+
+ for (let i = 0; i < Config.Runewords.length; i += 1) {
+ let itemList = []; // reset item list
+ let items = itemsRef.slice(); // copy itemsRef
+
+ const [runeword, wantedBase, ethFlag] = Config.Runewords[i];
+ if (runeword.reqLvl > me.charlvl) continue; // skip runeword if we don't meet the level requirement
+ let base = this.getBase(runeword, wantedBase, (ethFlag || 0)); // check base
+
+ if (base) {
+ itemList.push(base); // push the base
+
+ for (let j = 0; j < runeword.runes.length; j += 1) {
+ for (let k = 0; k < items.length; k += 1) {
+ if (items[k].classid === runeword.runes[j]) { // rune matched
+ itemList.push(items[k]); // push into the item list
+ items.splice(k, 1); // remove from item list as to not count it twice
+
+ k -= 1;
+
+ break; // stop item cycle - we found the item
+ }
+ }
+
+ // can't complete runeword - go to next one
+ if (itemList.length !== j + 2) {
+ break;
+ }
+
+ if (itemList.length === runeword.runes.length + 1) { // runes + base
+ return itemList; // these items are our runeword
+ }
+ }
+ }
+ }
+
+ return false;
+};
+
+/**
+ * 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}
+ */
Runewords.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() && Item.betterBaseThanWearing(item, Developer.debugging.baseCheck)) ||
- (reroll && item.getItem() && !NTIP.CheckItem(item, this.pickitEntries) && !Item.autoEquipKeepCheckMerc(item) && !Item.autoEquipCheck(item, true))) {
- if (!ethFlag || (ethFlag === Roll.Eth && item.ethereal) || (ethFlag === Roll.NonEth && !item.ethereal)) {
- return copyUnit(item);
- }
- }
- }
- } while (typeof base !== "object" && item.getNext());
- }
-
- return false;
+ 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() && Item.betterBaseThanWearing(item, Developer.debugging.baseCheck))
+ || (reroll && item.getItem() && !NTIP.CheckItem(item, this.pickitEntries)
+ && !Item.autoEquipCheckMerc(item, true) && !Item.autoEquipCheck(item, true))) {
+ if (!ethFlag || (ethFlag === Roll.Eth && item.ethereal) || (ethFlag === Roll.NonEth && !item.ethereal)) {
+ return copyUnit(item);
+ }
+ }
+ }
+ } while (typeof base !== "object" && item.getNext());
+ }
+
+ return false;
};
Runewords.checkRune = function (...runes) {
- if (!Config.MakeRunewords || runes.length < 1) return false;
+ if (!Config.MakeRunewords || runes.length < 1) return false;
- for (let classid of runes) {
- if (this.needList.includes(classid)) return true;
- }
+ for (let classid of runes) {
+ if (this.needList.includes(classid)) return true;
+ }
- return false;
+ return false;
};
diff --git a/libs/SoloPlay/Functions/SkillOverrides.js b/libs/SoloPlay/Functions/SkillOverrides.js
index 85831479..3f735318 100644
--- a/libs/SoloPlay/Functions/SkillOverrides.js
+++ b/libs/SoloPlay/Functions/SkillOverrides.js
@@ -5,256 +5,245 @@
*
*/
-includeIfNotIncluded("common/Misc.js");
+includeIfNotIncluded("core/Skill.js");
includeIfNotIncluded("SoloPlay/Tools/Developer.js");
-Skill.forcePacket = (Developer.forcePacketCasting.enabled && !Developer.forcePacketCasting.excludeProfiles.includes(me.profile));
+Skill.forcePacket = (
+ Developer.forcePacketCasting.enabled
+ && !Developer.forcePacketCasting.excludeProfiles.includes(me.profile)
+);
Skill.casterSkills = [
- sdk.skills.FireBolt, sdk.skills.ChargedBolt, sdk.skills.IceBolt, sdk.skills.FrostNova, sdk.skills.IceBlast, sdk.skills.FireBall,
- sdk.skills.Nova, sdk.skills.Lightning, sdk.skills.ChainLightning, sdk.skills.Teleport, sdk.skills.GlacialSpike, sdk.skills.Meteor,
- sdk.skills.Blizzard, sdk.skills.FrozenOrb, sdk.skills.BoneSpear, sdk.skills.Decrepify, sdk.skills.PoisonNova, sdk.skills.BoneSpirit,
- sdk.skills.HolyBolt, sdk.skills.BlessedHammer, sdk.skills.FistoftheHeavens, sdk.skills.Howl, sdk.skills.Taunt, sdk.skills.BattleCry,
- sdk.skills.WarCry, sdk.skills.Firestorm, sdk.skills.MoltenBoulder, sdk.skills.ArcticBlast, sdk.skills.Fissure, sdk.skills.Twister,
- sdk.skills.Volcano, sdk.skills.Armageddon, sdk.skills.Hurricane, sdk.skills.FireBlast, sdk.skills.ShockField, sdk.skills.ChargedBoltSentry,
- /* sdk.skills.WakeofFire, */ sdk.skills.LightningSentry, sdk.skills.DeathSentry
+ sdk.skills.FireBolt, sdk.skills.ChargedBolt,
+ sdk.skills.IceBolt, sdk.skills.FrostNova,
+ sdk.skills.IceBlast, sdk.skills.FireBall,
+ sdk.skills.Nova, sdk.skills.Lightning,
+ sdk.skills.ChainLightning, sdk.skills.Teleport,
+ sdk.skills.GlacialSpike, sdk.skills.Meteor,
+ sdk.skills.Blizzard, sdk.skills.FrozenOrb,
+ sdk.skills.BoneSpear, sdk.skills.Decrepify,
+ sdk.skills.PoisonNova, sdk.skills.BoneSpirit,
+ sdk.skills.HolyBolt, sdk.skills.BlessedHammer,
+ sdk.skills.FistoftheHeavens, sdk.skills.Howl,
+ sdk.skills.Taunt, sdk.skills.BattleCry,
+ sdk.skills.WarCry, sdk.skills.Firestorm,
+ sdk.skills.MoltenBoulder, sdk.skills.ArcticBlast,
+ sdk.skills.Fissure, sdk.skills.Twister,
+ sdk.skills.Volcano, sdk.skills.Armageddon,
+ sdk.skills.Hurricane, sdk.skills.FireBlast,
+ sdk.skills.ShockField, sdk.skills.ChargedBoltSentry,
+ /* sdk.skills.WakeofFire, */ sdk.skills.LightningSentry, sdk.skills.DeathSentry
];
-new Overrides.Override(Skill, Skill.getRange, function (orignal, skillId) {
- switch (skillId) {
- case sdk.skills.ChargedBolt:
- return !!this.usePvpRange ? 11 : 6;
- case sdk.skills.Lightning:
- case sdk.skills.BoneSpear:
- case sdk.skills.BoneSpirit:
- return !!this.usePvpRange ? 30 : 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 ? 30 : 20;
- default:
- return orignal(skillId);
- }
-}).apply();
-
-// Thank you @sakana
-Skill.getManaCost = function (skillId = -1) {
- // first skills dont use mana
- if (skillId < 6) return 0;
- // Decoy wasn't reading from skill bin
- if (skillId === sdk.skills.Decoy) return Math.max(19.75 - (0.75 * me.getSkill(sdk.skills.Decoy, sdk.skills.subindex.SoftPoints)), 1);
- if (this.manaCostList.hasOwnProperty(skillId)) return this.manaCostList[skillId];
-
- let skillLvl = me.getSkill(skillId, sdk.skills.subindex.SoftPoints), 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(3, skillId, "manashift")] / 256), getBaseStat("skills", skillId, "minmana"));
-
- if (!this.manaCostList.hasOwnProperty(skillId)) {
- this.manaCostList[skillId] = ret;
- }
-
- return ret;
-};
-
// Cast a skill on self, Unit or coords. Always use packet casting for caster skills becasue it's more stable.
Skill.cast = function (skillId, hand, x, y, item) {
- switch (true) {
- case me.inTown && !this.townSkill(skillId): // cant cast this in town
- case !item && (!Skill.canUse(skillId) || this.getManaCost(skillId) > me.mp): // Dont have this skill or dont have enough mana for this
- case !this.wereFormCheck(skillId): // can't cast in wereform
- return false;
- case skillId === undefined:
- throw new Error("Unit.cast: Must supply a skill ID");
- }
-
- 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)) {
- console.debug("Skill: " + getSkillById(skillId) + " manaCost: " + this.getManaCost(skillId) + " myMana: " + me.mp);
- delay(300);
- }
-
- return false;
- }
-
- if (!this.setSkill(skillId, hand, item)) return false;
-
- if (Config.PacketCasting > 1 || [sdk.skills.Teleport, sdk.skills.Telekinesis].includes(skillId)
- || (this.forcePacket && this.casterSkills.includes(skillId) && (!!me.realm || [sdk.skills.Teeth, sdk.skills.Tornado].indexOf(skillId) === -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++) {
- if ([sdk.player.mode.GettingHit, sdk.player.mode.Blocking].includes(me.mode) || me.skillDelay) {
- break;
- }
-
- delay(10);
- }
- }
-
- return true;
+ switch (true) {
+ case me.inTown && !this.townSkill(skillId): // cant cast this in town
+ case !item && (!Skill.canUse(skillId) || this.getManaCost(skillId) > me.mp): // Dont have this skill or dont have enough mana for this
+ case !this.wereFormCheck(skillId): // can't cast in wereform
+ return false;
+ case skillId === undefined:
+ throw new Error("Unit.cast: Must supply a skill ID");
+ }
+
+ 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)) {
+ console.debug("Skill: " + getSkillById(skillId) + " manaCost: " + this.getManaCost(skillId) + " myMana: " + me.mp);
+ delay(300);
+ }
+
+ return false;
+ }
+
+ if (!this.setSkill(skillId, hand, item)) return false;
+
+ if (Config.PacketCasting > 1 || [sdk.skills.Teleport, sdk.skills.Telekinesis].includes(skillId)
+ || (this.forcePacket && this.casterSkills.includes(skillId)
+ && (!!me.realm || [sdk.skills.Teeth, sdk.skills.Tornado].indexOf(skillId) === -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] = (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];
+ }
+ })();
+
+ 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++) {
+ if ([sdk.player.mode.GettingHit, sdk.player.mode.Blocking].includes(me.mode) || me.skillDelay) {
+ break;
+ }
+
+ delay(10);
+ }
+ }
+
+ return true;
};
Skill.switchCast = function (skillId, givenSettings = {}) {
- const settings = Object.assign({}, {
- hand: undefined,
- x: undefined,
- y: undefined,
- switchBack: true,
- oSkill: false
- }, givenSettings);
-
- switch (true) {
- case me.classic: // No switch in classic
- case me.inTown && !this.townSkill(skillId): // cant cast this in town
- case this.getManaCost(skillId) > me.mp: // dont have enough mana for this
- case !this.wereFormCheck(skillId): // can't cast in wereform
- //case (!me.getSkill(skillId, sdk.skills.subindex.SoftPoints) && !settings.oSkill): // Dont have this skill
- return false;
- case skillId === undefined:
- throw new Error("Unit.cast: Must supply a skill ID");
- }
-
- settings.hand === undefined && (settings.hand = this.getHand(skillId));
- settings.x === undefined && (settings.x = me.x);
- settings.y === undefined && (settings.y = me.y);
-
- // Check mana cost, charged skills don't use mana
- if (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;
- }
-
- // switch to secondary
- me.weaponswitch === 0 && me.switchWeapons(1);
-
- // Failed to set the skill, switch back
- if (!this.setSkill(skillId, settings.hand)) {
- me.switchWeapons(0);
- return false;
- }
-
- if ((this.forcePacket && this.casterSkills.includes(skillId) && (!!me.realm || [sdk.skills.Teeth, sdk.skills.Tornado].indexOf(skillId) === -1))
- || Config.PacketCasting > 1
- || skillId === sdk.skills.Teleport) {
- switch (typeof settings.x) {
- case "number":
- Packet.castSkill(settings.hand, settings.x, settings.y);
-
- break;
- case "object":
- Packet.unitCast(settings.hand, settings.x);
-
- break;
- }
- // make sure we give enough time back so we don't fail our next cast
- delay(250);
- } else {
- let [clickType, shift] = (() => {
- switch (settings.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 settings.x === "object" ? clickMap(clickType, shift, settings.x) : clickMap(clickType, shift, settings.x, settings.y);
- delay(20);
- typeof settings.x === "object" ? clickMap(clickType + 2, shift, settings.x) : clickMap(clickType + 2, shift, settings.x, settings.y);
-
- for (let i = 0; i < 8; i++) {
- 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++) {
- if ([sdk.player.mode.GettingHit, sdk.player.mode.Blocking].includes(me.mode) || me.skillDelay) {
- break;
- }
-
- delay(10);
- }
- }
-
- // switch back to main secondary
- me.weaponswitch === 1 && settings.switchBack && me.switchWeapons(0);
-
- return true;
+ const settings = Object.assign({}, {
+ hand: undefined,
+ x: undefined,
+ y: undefined,
+ switchBack: true,
+ oSkill: false
+ }, givenSettings);
+
+ switch (true) {
+ case me.classic: // No switch in classic
+ case me.inTown && !this.townSkill(skillId): // cant cast this in town
+ case this.getManaCost(skillId) > me.mp: // dont have enough mana for this
+ case !this.wereFormCheck(skillId): // can't cast in wereform
+ //case (!me.getSkill(skillId, sdk.skills.subindex.SoftPoints) && !settings.oSkill): // Dont have this skill
+ return false;
+ case skillId === undefined:
+ throw new Error("Unit.cast: Must supply a skill ID");
+ }
+
+ settings.hand === undefined && (settings.hand = this.getHand(skillId));
+ settings.x === undefined && (settings.x = me.x);
+ settings.y === undefined && (settings.y = me.y);
+
+ // Check mana cost, charged skills don't use mana
+ if (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;
+ }
+
+ // switch to secondary
+ me.weaponswitch === 0 && me.switchWeapons(1);
+
+ // Failed to set the skill, switch back
+ if (!this.setSkill(skillId, settings.hand)) {
+ me.switchWeapons(0);
+ return false;
+ }
+
+ if (me.paladin && settings.hand !== sdk.skills.hand.Right) {
+ // set aura
+ Skill.setSkill(Config.AttackSkill[2], sdk.skills.hand.Right);
+ }
+
+ if ((this.forcePacket && this.casterSkills.includes(skillId)
+ && (!!me.realm || [sdk.skills.Teeth, sdk.skills.Tornado].indexOf(skillId) === -1))
+ || Config.PacketCasting > 1
+ || skillId === sdk.skills.Teleport) {
+ switch (typeof settings.x) {
+ case "number":
+ Packet.castSkill(settings.hand, settings.x, settings.y);
+
+ break;
+ case "object":
+ Packet.unitCast(settings.hand, settings.x);
+
+ break;
+ }
+ // make sure we give enough time back so we don't fail our next cast
+ delay(250);
+ } else {
+ let [clickType, shift] = (function () {
+ switch (settings.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 settings.x === "object"
+ ? clickMap(clickType, shift, settings.x)
+ : clickMap(clickType, shift, settings.x, settings.y);
+ delay(20);
+ typeof settings.x === "object"
+ ? clickMap(clickType + 2, shift, settings.x)
+ : clickMap(clickType + 2, shift, settings.x, settings.y);
+
+ for (let i = 0; i < 8; i++) {
+ 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++) {
+ if ([sdk.player.mode.GettingHit, sdk.player.mode.Blocking].includes(me.mode) || me.skillDelay) {
+ break;
+ }
+
+ delay(10);
+ }
+ }
+
+ // switch back to main secondary
+ me.weaponswitch === 1 && settings.switchBack && me.switchWeapons(0);
+
+ return true;
};
diff --git a/libs/SoloPlay/Functions/SoloEvents.js b/libs/SoloPlay/Functions/SoloEvents.js
index 29a6ae2d..a6ad3fc3 100644
--- a/libs/SoloPlay/Functions/SoloEvents.js
+++ b/libs/SoloPlay/Functions/SoloEvents.js
@@ -5,446 +5,625 @@
*
*/
-const SoloEvents = {
- filePath: "libs/SoloPlay/Threads/EventThread.js",
- check: false,
- inGame: false,
- cloneWalked: false,
- townChicken: false,
- profileResponded: false,
- gameInfo: {
- gameName: "",
- gamePass: "",
- },
-
- outOfGameCheck: function () {
- if (!this.check) return false;
-
- if (this.gameInfo.gameName.length > 0) {
- D2Bot.printToConsole("Kolbot-SoloPlay :: SoloEvents.outOfGameCheck(): Attempting to join other bots game", sdk.colors.D2Bot.Gold);
- SoloEvents.inGame = true;
- me.blockmouse = true;
-
- delay(2000);
- joinGame(this.gameInfo.gameName, this.gameInfo.gamePass);
-
- me.blockmouse = false;
-
- delay(5000);
-
- while (me.ingame) {
- delay(1000);
- }
-
- console.log("ÿc8Kolbot-SoloPlayÿc0: End of SoloEvents.outOfGameCheck()");
- SoloEvents.inGame = false;
- SoloEvents.check = false;
- this.gameInfo.gameName = "";
- this.gameInfo.gamePass = "";
-
- return true;
- }
-
- return false;
- },
-
- inGameCheck: function () {
- if (me.ingame && me.hell && !me.classic && Misc.getPlayerCount() > 1) {
- let possibleChars = this.getCharacterNames();
-
- for (let i = 0; i < possibleChars.length; i++) {
- if (Misc.findPlayer(possibleChars[i].toLowerCase())) {
- if (!me.inArea(sdk.areas.RogueEncampment)) {
- Town.goToTown(1);
- }
-
- Town.move("stash");
-
- let torch, anni, tick = getTickCount();
-
- me.overhead("Waiting for charm to drop");
- while (getTickCount() - tick < 120 * 1000) {
- anni = me.findItem(sdk.items.SmallCharm, sdk.items.mode.onGround, -1, sdk.items.quality.Unique);
- torch = me.findItem(sdk.items.LargeCharm, sdk.items.mode.onGround, -1, sdk.items.quality.Unique);
-
- if (torch || anni) {
- break;
- }
- }
-
- if (torch || anni) {
- for (let j = 0; j < 12 || me.findItem((anni ? sdk.items.SmallCharm : sdk.items.LargeCharm), sdk.items.mode.inStorage, -1, sdk.items.quality.Unique); j++) {
- Town.move("stash");
- me.overhead("Looking for " + (anni ? "Annihilus" : "Torch"));
- Pickit.pickItems();
- delay(10000); // 10 seconds
- }
- } else {
- me.overhead("No charm on ground");
- }
-
- quit();
- return true;
- }
- }
-
- console.log("Couldnt find player");
- }
-
- return false;
- },
-
- getProfiles: function () {
- let profileInfo, realm = me.realm.toLowerCase(), profileList = [];
- //realm = "useast"; // testing purposes
-
- if (!FileTools.exists("logs/Kolbot-SoloPlay/" + realm)) {
- return profileList;
- }
-
- let files = dopen("logs/Kolbot-SoloPlay/" + realm + "/").getFiles();
-
- for (let i = 0; i < files.length; i++) {
- try {
- profileInfo = Developer.readObj("logs/Kolbot-SoloPlay/" + realm + "/" + files[i]);
- if (profileList.indexOf(profileInfo.profile) === -1) {
- profileList.push(profileInfo.profile);
- }
- } catch (e) {
- console.log(e);
- }
- }
-
- return profileList;
- },
-
- getCharacterNames: function () {
- let characterInfo, realm = me.realm.toLowerCase(), charList = [];
- //realm = "useast"; // testing purposes
-
- if (!FileTools.exists("logs/Kolbot-SoloPlay/" + realm)) {
- return profileList;
- }
-
- let files = dopen("logs/Kolbot-SoloPlay/" + realm + "/").getFiles();
-
- for (let i = 0; i < files.length; i++) {
- try {
- characterInfo = Developer.readObj("logs/Kolbot-SoloPlay/" + realm + "/" + files[i]);
- if (charList.indexOf(characterInfo.charName) === -1) {
- charList.push(characterInfo.charName);
- }
- } catch (e) {
- console.log(e);
- }
- }
-
- return charList;
- },
-
- sendToProfile: function (profile, message, mode = 65) {
- if (profile.toLowerCase() !== me.profile.toLowerCase()) {
- sendCopyData(null, profile, mode, JSON.stringify(message));
- }
- },
-
- sendToList: function (message, mode = 55) {
- let profiles = this.getProfiles();
-
- if (!profiles || profiles === undefined) {
- return false;
- }
-
- return profiles.forEach((profileName) => {
- if (profileName.toLowerCase() !== me.profile.toLowerCase()) {
- sendCopyData(null, profileName, mode, JSON.stringify(message));
- }
- });
- },
-
- dropCharm: function (charm) {
- if (!charm || charm === undefined) return false;
-
- D2Bot.printToConsole("Kolbot-SoloPlay :: Dropping " + charm.name, sdk.colors.D2Bot.Orange);
- let orginalLocation = {act: me.act, area: me.area, x: me.x, y: me.y};
-
- if (!me.inTown) {
- Town.goToTown(1);
- Town.move("stash");
- charm.drop();
- }
-
- if (me.act !== orginalLocation.act) {
- Town.goToTown(orginalLocation.act);
- }
-
- Town.move("portalspot");
-
- if (!Pather.usePortal(orginalLocation.area)) {
- Pather.journeyTo(orginalLocation.area);
- }
-
- return Pather.moveTo(orginalLocation.x, orginalLocation.y);
- },
-
- // @todo redo this, I think better option would be to make this it's own script
- // end the current script but insert it to be continued after dclone is dead
- killdclone: function () {
- D2Bot.printToConsole("Kolbot-SoloPlay :: Trying to kill DClone.", sdk.colors.D2Bot.Orange);
- let orginalLocation = {area: me.area, x: me.x, y: me.y};
-
- !me.inTown && Town.goToTown();
-
- if (Pather.accessToAct(2) && Pather.checkWP(sdk.areas.ArcaneSanctuary)) {
- Pather.useWaypoint(sdk.areas.ArcaneSanctuary);
- Precast.doPrecast(true);
-
- if (!Pather.usePortal(null)) {
- console.log("ÿc8Kolbot-SoloPlayÿc1: Failed to move to Palace Cellar");
- }
- } else if (Pather.checkWP(sdk.areas.InnerCloister)) {
- Pather.useWaypoint(sdk.areas.InnerCloister);
- Pather.moveTo(20047, 4898);
- } else {
- Pather.useWaypoint(sdk.areas.ColdPlains);
- Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.DenofEvil], true);
- Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Corpsefire, 0, 0, false, true);
- }
-
- Attack.killTarget(sdk.monsters.DiabloClone);
- Pickit.pickItems();
-
- let newAnni = Game.getItem(sdk.items.SmallCharm, sdk.items.mode.onGround);
- let oldAnni = me.findItem(sdk.items.SmallCharm, sdk.items.mode.inStorage, -1, sdk.items.quality.Unique);
-
- if (newAnni && oldAnni) {
- this.sendToList({profile: me.profile, ladder: me.ladder}, 60);
-
- let tick = getTickCount();
-
- while (getTickCount() - tick < 10000) {
- me.overhead("Waiting to see if I get a response from other profiles");
-
- if (this.profileResponded) {
- me.overhead("Recieved response, dropping old Annihilus in Rogue Encampment");
- break;
- }
-
- delay(50);
- }
-
- if (newAnni && oldAnni && this.profileResponded) {
- this.dropCharm(oldAnni);
- } else {
- me.overhead("No response from other profiles");
- }
-
- SoloEvents.profileResponded = false;
- Pickit.pickItems();
- }
-
- if ((newAnni && oldAnni && !this.profileResponded) && AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("torchMuleInfo")) {
- scriptBroadcast("muleAnni");
- }
-
- // Move back to where we orignally where
- Pather.journeyTo(orginalLocation.area);
- Pather.moveTo(orginalLocation.x, orginalLocation.y);
- SoloEvents.cloneWalked = false;
- },
-
- moveSettings: {
- allowTeleport: false,
- allowClearing: false,
- allowPicking: false,
- allowTown: false,
- retry: 10,
- },
-
- moveTo: function (x, y, givenSettings) {
- // Abort if dead
- if (me.dead) return false;
- return Pather.move({ x: x, y: y }, Object.assign({}, SoloEvents.moveSettings, givenSettings));
- },
-
- skip: function () {
- let tick = getTickCount();
- myPrint("Attempting baal wave skip");
-
- // Disable anything that will cause us to stop
- [Precast.enabled, Misc.townEnabled, Pickit.enabled] = [false, false, false];
- me.barbarian && (Config.FindItem = false);
-
- // Prep, move to throne entrance
- while (getTickCount() - tick < 6500) {
- this.moveTo(15091, 5073, { allowTeleport: true });
- }
-
- tick = getTickCount();
-
- // 5 second delay (5000ms), then leave throne
- while (getTickCount() - tick < 5000) {
- this.moveTo(15098, 5082, { allowTeleport: true });
- }
-
- tick = getTickCount();
- this.moveTo(15099, 5078); // Re-enter throne
-
- // 2 second delay (2000ms)
- while (getTickCount() - tick < 2000) {
- this.moveTo(15098, 5082);
- }
-
- this.moveTo(15099, 5078);
-
- // Re-enable
- [Precast.enabled, Misc.townEnabled, Pickit.enabled] = [true, true, true];
-
- let skipWorked = getUnits(sdk.unittype.Monster)
- .some(el => el.attackable && el.x >= 15070 && el.x <= 15120 && el.y >= 5000 && el.y <= 5075);
- myPrint("skip " + (skipWorked ? "worked" : "failed"));
- },
-
- dodge: function () {
- let diablo = Game.getMonster(sdk.monsters.Diablo);
- // Credit @Jaenster
- const shouldDodge = function (coord) {
- return !!diablo && getUnits(sdk.unittype.Missile)
- // For every missle that isnt from our merc
- .filter((missile) => missile && diablo && diablo.gid === missile.owner)
- // if any
- .some(function (missile) {
- let xoff = Math.abs(coord.x - missile.targetx);
- let yoff = Math.abs(coord.y - missile.targety);
- let xdist = Math.abs(coord.x - missile.x);
- let ydist = Math.abs(coord.y - missile.y);
- // If missile wants to hit is and is close to us
- return xoff < 10 && yoff < 10 && xdist < 15 && ydist < 15;
- });
- };
-
- if (diablo && shouldDodge(me)) {
- let tick = getTickCount();
- let overrides = { allowTeleport: false, allowClearing: false, allowTown: false };
- // Disable anything that will cause us to stop
- [Precast.enabled, Misc.townEnabled, Pickit.enabled] = [false, false, false];
- console.log("DODGE");
- // Disable things that will cause us to stop
- let dist = me.assassin ? 15 : 3;
-
- while (getTickCount() - tick < 2000) {
- // Above D
- if (me.y <= diablo.y) {
- // Move east
- me.x <= diablo.x && this.moveTo(diablo.x + dist, diablo.y, overrides);
- // Move south
- me.x > diablo.x && this.moveTo(diablo.x, diablo.y + dist, overrides);
- }
-
- // Below D
- if (me.y > diablo.y) {
- // Move west
- me.x >= diablo.x && this.moveTo(diablo.x - dist, diablo.y, overrides);
- // Move north
- me.x < diablo.x && this.moveTo(diablo.x, diablo.y - dist, overrides);
- }
- }
-
- // Re-enable
- [Precast.enabled, Misc.townEnabled, Pickit.enabled] = [true, true, true];
- }
- },
-
- finishDen: function () {
- Pickit.pickItems();
-
- // No Tome, or tome has no tps, or no scrolls
- if (!me.canTpToTown() && !me.inTown) {
- // should really check how close the town exit is
- Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.ColdPlains], true);
- Pather.getWP(sdk.areas.ColdPlains);
- Pather.useWaypoint(sdk.areas.RogueEncampment);
- } else {
- Town.goToTown();
- }
-
- Town.npcInteract("akara");
- },
-
- bugAndy: function () {
- Town.goToTown();
- Pather.changeAct();
- delay(2000 + me.ping);
-
- // Now check my area
- if (me.act === 2) {
- // Act change sucessful, Andy has been bugged
- myPrint("Andy bug " + (!me.getQuest(sdk.quest.id.SistersToTheSlaughter, 15) ? "sucessful" : "failed"));
- scriptBroadcast("quit");
- }
- },
-
- diaEvent: function (bytes = []) {
- if (!bytes.length) return;
- // dia lightning
- if (bytes[0] === 0x4C && bytes[6] === 193) {
- Messaging.sendToScript(SoloEvents.filePath, "dodge");
- }
- },
-
- skippedWaves: [],
-
- baalEvent: function (bytes = []) {
- if (!bytes.length) return;
- // baal wave
- if (bytes[0] === 0xA4) {
- if ((me.hell && me.paladin && !Attack.auradin)
- || me.barbarian
- || me.gold < 5000
- || (!me.baal && SetUp.finalBuild !== "Bumper")) {
- let waveMonster = ((bytes[1]) | (bytes[2] << 8));
- let wave = [
- sdk.monsters.WarpedShaman,
- sdk.monsters.BaalSubjectMummy,
- sdk.monsters.Council4,
- sdk.monsters.VenomLord2,
- sdk.monsters.ListerTheTormenter
- ].indexOf(waveMonster);
- console.debug("Wave # " + wave);
- if (SoloEvents.skippedWaves.includes(wave)) return;
- const waveBoss = {
- COLENZO: 0,
- ACHMEL: 1,
- BARTUC: 2,
- VENTAR: 3,
- LISTER: 4
- };
-
- switch (wave) {
- case waveBoss.COLENZO:
- break;
- case waveBoss.ACHMEL:
- if ((me.paladin && !Attack.auradin && me.hell)
- || (me.barbarian && ((me.charlvl < CharInfo.levelCap && !me.baal)
- || me.hardcore))) {
- Messaging.sendToScript(SoloEvents.filePath, "skip");
- SoloEvents.skippedWaves.push(wave);
- }
-
- break;
- case waveBoss.BARTUC:
- case waveBoss.VENTAR:
- break;
- case waveBoss.LISTER:
- if ((me.barbarian && (me.charlvl < CharInfo.levelCap || !me.baal || me.hardcore))
- || (me.charlvl < CharInfo.levelCap && (me.gold < 5000 || (!me.baal && SetUp.finalBuild !== "Bumper")))) {
- Messaging.sendToScript(SoloEvents.filePath, "skip");
- SoloEvents.skippedWaves.push(wave);
- }
-
- break;
- }
- }
- }
- },
-};
+(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.SoloEvents = factory();
+ }
+}(this, function () {
+ const SoloEvents = {
+ filePath: "libs/SoloPlay/Threads/EventThread.js",
+ check: false,
+ inGame: false,
+ cloneWalked: false,
+ townChicken: {
+ disabled: false,
+ running: false,
+ },
+ profileResponded: false,
+ gameInfo: {
+ gameName: "",
+ gamePass: "",
+ },
+
+ outOfGameCheck: function () {
+ if (!this.check) return false;
+
+ if (this.gameInfo.gameName.length > 0) {
+ D2Bot.printToConsole("Kolbot-SoloPlay :: SoloEvents.outOfGameCheck(): Attempting to join other bots game", sdk.colors.D2Bot.Gold);
+ SoloEvents.inGame = true;
+ me.blockMouse = true;
+
+ delay(2000);
+ joinGame(this.gameInfo.gameName, this.gameInfo.gamePass);
+
+ me.blockMouse = false;
+
+ delay(5000);
+
+ while (me.ingame) {
+ delay(1000);
+ }
+
+ console.log("ÿc8Kolbot-SoloPlayÿc0: End of SoloEvents.outOfGameCheck()");
+ SoloEvents.inGame = false;
+ SoloEvents.check = false;
+ this.gameInfo.gameName = "";
+ this.gameInfo.gamePass = "";
+
+ return true;
+ }
+
+ return false;
+ },
+
+ inGameCheck: function () {
+ if (me.ingame && me.hell && !me.classic && Misc.getPlayerCount() > 1) {
+ let possibleChars = this.getCharacterNames();
+
+ for (let i = 0; i < possibleChars.length; i++) {
+ if (Misc.findPlayer(possibleChars[i].toLowerCase())) {
+ if (!me.inArea(sdk.areas.RogueEncampment)) {
+ Town.goToTown(1);
+ }
+
+ Town.move("stash");
+
+ let torch, anni, tick = getTickCount();
+
+ me.overhead("Waiting for charm to drop");
+ while (getTickCount() - tick < 120 * 1000) {
+ anni = me.findItem(sdk.items.SmallCharm, sdk.items.mode.onGround, -1, sdk.items.quality.Unique);
+ torch = me.findItem(sdk.items.LargeCharm, sdk.items.mode.onGround, -1, sdk.items.quality.Unique);
+
+ if (torch || anni) {
+ break;
+ }
+ }
+
+ if (torch || anni) {
+ for (let j = 0; j < 12 || me.findItem((anni ? sdk.items.SmallCharm : sdk.items.LargeCharm), sdk.items.mode.inStorage, -1, sdk.items.quality.Unique); j++) {
+ Town.move("stash");
+ me.overhead("Looking for " + (anni ? "Annihilus" : "Torch"));
+ Pickit.pickItems();
+ delay(10000); // 10 seconds
+ }
+ } else {
+ me.overhead("No charm on ground");
+ }
+
+ quit();
+ return true;
+ }
+ }
+
+ console.log("Couldnt find player");
+ }
+
+ return false;
+ },
+
+ getProfiles: function () {
+ let profileInfo, realm = me.realm.toLowerCase(), profileList = [];
+ //realm = "useast"; // testing purposes
+
+ if (!FileTools.exists("logs/Kolbot-SoloPlay/" + realm)) {
+ return profileList;
+ }
+
+ let files = dopen("logs/Kolbot-SoloPlay/" + realm + "/").getFiles();
+
+ for (let i = 0; i < files.length; i++) {
+ try {
+ profileInfo = Tracker.readObj("logs/Kolbot-SoloPlay/" + realm + "/" + files[i]);
+ if (profileList.indexOf(profileInfo.profile) === -1) {
+ profileList.push(profileInfo.profile);
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ return profileList;
+ },
+
+ getCharacterNames: function () {
+ let characterInfo, realm = me.realm.toLowerCase(), charList = [];
+ //realm = "useast"; // testing purposes
+
+ if (!FileTools.exists("logs/Kolbot-SoloPlay/" + realm)) {
+ return charList;
+ }
+
+ let files = dopen("logs/Kolbot-SoloPlay/" + realm + "/").getFiles();
+
+ for (let i = 0; i < files.length; i++) {
+ try {
+ characterInfo = Tracker.readObj("logs/Kolbot-SoloPlay/" + realm + "/" + files[i]);
+ if (charList.indexOf(characterInfo.charName) === -1) {
+ charList.push(characterInfo.charName);
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ return charList;
+ },
+
+ sendToProfile: function (profile, message, mode = 65) {
+ if (profile.toLowerCase() !== me.profile.toLowerCase()) {
+ sendCopyData(null, profile, mode, JSON.stringify(message));
+ }
+ },
+
+ sendToList: function (message, mode = 55) {
+ let profiles = this.getProfiles();
+
+ if (!profiles || profiles === undefined) {
+ return false;
+ }
+
+ return profiles.forEach((profileName) => {
+ if (profileName.toLowerCase() !== me.profile.toLowerCase()) {
+ sendCopyData(null, profileName, mode, JSON.stringify(message));
+ }
+ });
+ },
+
+ dropCharm: function (charm) {
+ if (!charm || charm === undefined) return false;
+
+ D2Bot.printToConsole("Kolbot-SoloPlay :: Dropping " + charm.name, sdk.colors.D2Bot.Orange);
+ let orginalLocation = { act: me.act, area: me.area, x: me.x, y: me.y };
+
+ if (!me.inTown) {
+ Town.goToTown(1);
+ Town.move("stash");
+ charm.drop();
+ }
+
+ if (me.act !== orginalLocation.act) {
+ Town.goToTown(orginalLocation.act);
+ }
+
+ Town.move("portalspot");
+
+ if (!Pather.usePortal(orginalLocation.area)) {
+ Pather.journeyTo(orginalLocation.area);
+ }
+
+ return Pather.moveTo(orginalLocation.x, orginalLocation.y);
+ },
+
+ // @todo redo this, I think better option would be to make this it's own script
+ // end the current script but insert it to be continued after dclone is dead
+ killdclone: function () {
+ D2Bot.printToConsole("Kolbot-SoloPlay :: Trying to kill DClone.", sdk.colors.D2Bot.Orange);
+ let orginalLocation = { area: me.area, x: me.x, y: me.y };
+
+ !me.inTown && Town.goToTown();
+
+ if (me.accessToAct(2) && Pather.checkWP(sdk.areas.ArcaneSanctuary)) {
+ Pather.useWaypoint(sdk.areas.ArcaneSanctuary);
+ Precast.doPrecast(true);
+
+ if (!Pather.usePortal(null)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc1: Failed to move to Palace Cellar");
+ }
+ } else if (Pather.checkWP(sdk.areas.InnerCloister)) {
+ Pather.useWaypoint(sdk.areas.InnerCloister);
+ Pather.moveTo(20047, 4898);
+ } else {
+ Pather.useWaypoint(sdk.areas.ColdPlains);
+ Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.DenofEvil], true);
+ Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Corpsefire, 0, 0, false, true);
+ }
+
+ try {
+ console.log("Added dia lightning listener");
+ addEventListener("gamepacket", SoloEvents.diaEvent);
+
+ Attack.killTarget(sdk.monsters.DiabloClone);
+ Pickit.pickItems();
+
+ let newAnni = Game.getItem(sdk.items.SmallCharm, sdk.items.mode.onGround);
+ let oldAnni = me.findItem(sdk.items.SmallCharm, sdk.items.mode.inStorage, -1, sdk.items.quality.Unique);
+
+ if (newAnni && oldAnni) {
+ this.sendToList({ profile: me.profile, ladder: me.ladder }, 60);
+
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < 10000) {
+ me.overhead("Waiting to see if I get a response from other profiles");
+
+ if (this.profileResponded) {
+ me.overhead("Recieved response, dropping old Annihilus in Rogue Encampment");
+ break;
+ }
+
+ delay(50);
+ }
+
+ if (newAnni && oldAnni && this.profileResponded) {
+ this.dropCharm(oldAnni);
+ } else {
+ me.overhead("No response from other profiles");
+ }
+
+ SoloEvents.profileResponded = false;
+ Pickit.pickItems();
+ }
+
+ if ((newAnni && oldAnni && !this.profileResponded) && AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("torchMuleInfo")) {
+ scriptBroadcast("muleAnni");
+ }
+ } finally {
+ removeEventListener("gamepacket", SoloEvents.diaEvent);
+ // Move back to where we orignally where
+ Pather.journeyTo(orginalLocation.area);
+ Pather.moveTo(orginalLocation.x, orginalLocation.y);
+ SoloEvents.cloneWalked = false;
+ }
+ },
+
+ moveSettings: {
+ allowTeleport: false,
+ allowClearing: false,
+ allowPicking: false,
+ allowTown: false,
+ allowNodeActions: false,
+ retry: 10,
+ },
+
+ moveTo: function (x, y, givenSettings) {
+ // Abort if dead
+ if (me.dead) return false;
+ /**
+ * assign settings
+ * @type {pathSettings}
+ */
+ const settings = Object.assign({}, {
+ allowTeleport: false,
+ minDist: 3,
+ retry: 10,
+ pop: false,
+ returnSpotOnError: true,
+ callback: null,
+ }, givenSettings);
+
+ /** @type {PathNode} */
+ let node = { x: x, y: y };
+
+ if (settings.minDist > 3) {
+ node = Pather.spotOnDistance(
+ node,
+ settings.minDist,
+ { returnSpotOnError: settings.returnSpotOnError, reductionType: (me.inTown ? 0 : 2) }
+ );
+ }
+
+ let fail = 0;
+ let invalidCheck = false;
+ let cbCheck = false;
+
+ Pather.clearUIFlags();
+
+ if (typeof node.x !== "number" || typeof node.y !== "number") return false;
+ if (node.distance < 2 && !CollMap.checkColl(me, node, sdk.collision.BlockMissile, 5)) {
+ return true;
+ }
+
+ const useTeleport = (
+ settings.allowTeleport
+ && (node.distance > 15 || me.diff || me.act > 3)
+ && Pather.useTeleport()
+ );
+ const useChargedTele = settings.allowTeleport && Pather.canUseTeleCharges();
+ const usingTele = (useTeleport || useChargedTele);
+ const tpMana = Skill.getManaCost(sdk.skills.Teleport);
+ const annoyingArea = Pather.inAnnoyingArea(me.area);
+ let path = getPath(
+ me.area,
+ node.x, node.y,
+ me.x, me.y,
+ usingTele ? 1 : 0,
+ usingTele ? (annoyingArea ? 30 : Pather.teleDistance) : Pather.walkDistance
+ );
+ if (!path) throw new Error("move: Failed to generate path.");
+
+ path.reverse();
+ settings.pop && path.pop();
+ PathDebug.drawPath(path);
+ if (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
+ if (Pather.recursion) {
+ Pather.currentWalkingPath = path;
+ PathDebug.drawPath(Pather.currentWalkingPath);
+ }
+ Pather.clearUIFlags();
+
+ /** @type {PathNode} */
+ node = path.shift();
+
+ if (typeof settings.callback === "function" && settings.callback()) {
+ cbCheck = true;
+ break;
+ }
+
+ if (getDistance(me, node) > 2) {
+ // Make life in Maggot Lair easier
+ if (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 = Pather.getNearestWalkable(node.x, node.y, 15, 3, sdk.collision.BlockWalk);
+
+ if (adjustedNode) {
+ [node.x, node.y] = adjustedNode;
+ invalidCheck && (invalidCheck = false);
+ }
+ }
+
+ if (useTeleport && tpMana <= me.mp
+ ? Pather.teleportTo(node.x, node.y)
+ : useChargedTele && (getDistance(me, node) >= 15 || me.inArea(sdk.areas.ThroneofDestruction))
+ ? Pather.teleUsingCharges(node.x, node.y)
+ : Pather.walkTo(node.x, node.y, (fail > 0 || me.inTown) ? 2 : 4)) {
+ // we successfully moved to the node
+ } else {
+ if (!me.inTown) {
+ if (!useTeleport && (Pather.openDoors(node.x, node.y) || Pather.kickBarrels(node.x, node.y))) {
+ console.debug("Failed to walk to node, but opened door/barrel");
+ continue;
+ }
+ }
+
+ // Reduce node distance in new path
+ path = getPath(
+ me.area,
+ node.x, node.y,
+ me.x, me.y,
+ useTeleport ? 1 : 0,
+ useTeleport ? rand(25, 35) : rand(10, 15)
+ );
+ if (!path) throw new Error("moveTo: 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);
+ }
+
+ me.switchToPrimary();
+ PathDebug.removeHooks();
+
+ return cbCheck || getDistance(me, node.x, node.y) < 5;
+ },
+
+ skip: function () {
+ let tick = getTickCount();
+ myPrint("Attempting baal wave skip");
+
+ try {
+ // Disable anything that will cause us to stop
+ [Precast.enabled, Pickit.enabled] = [false, false];
+ SoloEvents.townChicken.disabled = true;
+ me.barbarian && (Config.FindItem = false);
+
+ // Prep, move to throne entrance
+ while (getTickCount() - tick < 6500) {
+ this.moveTo(15091, 5073, { allowTeleport: true });
+ }
+
+ tick = getTickCount();
+
+ // 5 second delay (5000ms), then leave throne
+ while (getTickCount() - tick < 5000) {
+ this.moveTo(15098, 5082, { allowTeleport: true });
+ }
+
+ tick = getTickCount();
+ // this.moveTo(15099, 5078); // Re-enter throne
+ Pather.walkTo(15099, 5078, 2);
+
+ // 2 second delay (2000ms)
+ while (getTickCount() - tick < 2000) {
+ this.moveTo(15098, 5082);
+ }
+
+ this.moveTo(15099, 5078);
+
+ let skipFailed = getUnits(sdk.unittype.Monster)
+ .some(mon => mon.attackable
+ && mon.x >= 15072 && mon.x <= 15118
+ && mon.y >= 5002 && mon.y <= 5073
+ );
+ myPrint("skip " + (!skipFailed ? "worked" : "failed"));
+ } finally {
+ // Re-enable
+ [Precast.enabled, Pickit.enabled] = [true, true];
+ SoloEvents.townChicken.disabled = false;
+ }
+ },
+
+ dodge: function () {
+ let diablo = me.inArea(sdk.areas.ChaosSanctuary)
+ ? Game.getMonster(sdk.monsters.Diablo)
+ : Game.getMonster(sdk.monsters.DiabloClone);
+ // Credit @Jaenster
+ const shouldDodge = function (coord) {
+ return !!diablo && getUnits(sdk.unittype.Missile)
+ // For every missle that isnt from our merc
+ .filter((missile) => missile && diablo && diablo.gid === missile.owner)
+ // if any
+ .some(function (missile) {
+ let xoff = Math.abs(coord.x - missile.targetx);
+ let yoff = Math.abs(coord.y - missile.targety);
+ let xdist = Math.abs(coord.x - missile.x);
+ let ydist = Math.abs(coord.y - missile.y);
+ // If missile wants to hit is and is close to us
+ return xoff < 10 && yoff < 10 && xdist < 15 && ydist < 15;
+ });
+ };
+
+ if (diablo && shouldDodge(me)) {
+ let tick = getTickCount();
+ let overrides = { allowTeleport: false, allowClearing: false, allowTown: false };
+
+ try {
+ // Disable anything that will cause us to stop
+ [Precast.enabled, Pickit.enabled] = [false, false];
+ SoloEvents.townChicken.disabled = true;
+ console.log("DODGE");
+ // Disable things that will cause us to stop
+ let dist = me.assassin ? 15 : 3;
+
+ while (getTickCount() - tick < 2000) {
+ // Above D
+ if (me.y <= diablo.y) {
+ // Move east
+ me.x <= diablo.x && this.moveTo(diablo.x + dist, diablo.y, overrides);
+ // Move south
+ me.x > diablo.x && this.moveTo(diablo.x, diablo.y + dist, overrides);
+ }
+
+ // Below D
+ if (me.y > diablo.y) {
+ // Move west
+ me.x >= diablo.x && this.moveTo(diablo.x - dist, diablo.y, overrides);
+ // Move north
+ me.x < diablo.x && this.moveTo(diablo.x, diablo.y - dist, overrides);
+ }
+ }
+ } finally {
+ // Re-enable
+ [Precast.enabled, Pickit.enabled] = [true, true];
+ SoloEvents.townChicken.disabled = false;
+ }
+ }
+ },
+
+ finishDen: function () {
+ Pickit.pickItems();
+
+ // No Tome, or tome has no tps, or no scrolls
+ if (!me.canTpToTown() && !me.inTown) {
+ // should really check how close the town exit is
+ Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.ColdPlains], true);
+ Pather.getWP(sdk.areas.ColdPlains);
+ Pather.useWaypoint(sdk.areas.RogueEncampment);
+ } else {
+ Town.goToTown(1);
+ }
+
+ Town.npcInteract("akara");
+ },
+
+ bugAndy: function () {
+ Town.goToTown();
+ Pather.changeAct();
+ delay(2000 + me.ping);
+
+ // Now check my area
+ if (me.act === 2) {
+ // Act change sucessful, Andy has been bugged
+ myPrint("Andy bug " + (!me.getQuest(sdk.quest.id.SistersToTheSlaughter, 15) ? "sucessful" : "failed"));
+ scriptBroadcast("quit");
+ }
+ },
+
+ diaEvent: function (bytes = []) {
+ if (!bytes.length) return;
+ // dia lightning
+ if (bytes[0] === 0x4C && bytes[6] === 193) {
+ me.emit("soloEvent", "dodge");
+ }
+ },
+
+ skippedWaves: [],
+
+ baalEvent: function (bytes = []) {
+ if (!bytes.length) return;
+ // baal wave
+ if (bytes[0] === 0xA4) {
+ if ((me.hell && me.paladin && !Attack.auradin)
+ || me.barbarian
+ || me.gold < 5000
+ || (!me.baal && SetUp.finalBuild !== "Bumper")) {
+ let waveMonster = ((bytes[1]) | (bytes[2] << 8));
+ let wave = [
+ sdk.monsters.WarpedShaman,
+ sdk.monsters.BaalSubjectMummy,
+ sdk.monsters.Council4,
+ sdk.monsters.VenomLord2,
+ sdk.monsters.ListerTheTormenter
+ ].indexOf(waveMonster);
+ console.debug("Wave # " + wave);
+ if (SoloEvents.skippedWaves.includes(wave)) return;
+ const waveBoss = {
+ COLENZO: 0,
+ ACHMEL: 1,
+ BARTUC: 2,
+ VENTAR: 3,
+ LISTER: 4
+ };
+
+ switch (wave) {
+ case waveBoss.COLENZO:
+ break;
+ case waveBoss.ACHMEL:
+ if ((me.paladin && !Attack.auradin && me.hell)
+ || (me.barbarian && ((me.charlvl < CharInfo.levelCap && !me.baal)
+ || me.hardcore))) {
+ Messaging.sendToScript(SoloEvents.filePath, "skip");
+ SoloEvents.skippedWaves.push(wave);
+ }
+
+ break;
+ case waveBoss.BARTUC:
+ case waveBoss.VENTAR:
+ break;
+ case waveBoss.LISTER:
+ if ((me.barbarian && (me.charlvl < CharInfo.levelCap || !me.baal || me.hardcore))
+ || (me.charlvl < CharInfo.levelCap && (me.gold < 5000 || (!me.baal && SetUp.finalBuild !== "Bumper")))) {
+ // Messaging.sendToScript(SoloEvents.filePath, "skip");
+ me.emit("soloEvent", "skip");
+ SoloEvents.skippedWaves.push(wave);
+ }
+
+ break;
+ }
+ }
+ }
+ },
+ };
+
+ return SoloEvents;
+}));
diff --git a/libs/SoloPlay/Functions/SoloWants.js b/libs/SoloPlay/Functions/SoloWants.js
new file mode 100644
index 00000000..4400dc7c
--- /dev/null
+++ b/libs/SoloPlay/Functions/SoloWants.js
@@ -0,0 +1,260 @@
+/**
+* @filename SoloWants.js
+* @author theBGuy
+* @desc SoloWants system for Kolbot-SoloPlay, handles inserting socketables
+*
+*/
+
+const SoloWants = {
+ needList: [],
+ validGids: [],
+
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ checkItem: function (item) {
+ if (!item) return false;
+ if (this.validGids.includes(item.gid)) return true;
+ let i = 0;
+ for (let el of this.needList) {
+ if (item.isInsertable) {
+ if (el.needed.includes(item.classid)) {
+ this.validGids.push(item.gid);
+ this.needList[i].needed.splice(this.needList[i].needed.indexOf(item.classid), 1);
+ if (this.needList[i].needed.length === 0) {
+ // no more needed items so remove from list
+ this.needList.splice(i, 1);
+ }
+ return true;
+ }
+ }
+ i++; // keep track of index
+ }
+
+ return false;
+ },
+
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ keepItem: function (item) {
+ if (!item) return false;
+ return this.validGids.includes(item.gid);
+ },
+
+ buildList: function () {
+ let myItems = me.getItemsEx()
+ .filter(function (item) {
+ if (item.isRuneword || item.questItem) return false;
+ return item.quality >= sdk.items.quality.Normal && (item.sockets > 0 || getBaseStat("items", item.classid, "gemsockets") > 0);
+ });
+ myItems
+ .filter(item => item.isEquipped)
+ .forEach(item => SoloWants.addToList(item));
+ myItems
+ .filter(item => item.isInStorage && item.quality >= sdk.items.quality.Magic && item.getItemType() && AutoEquip.wanted(item))
+ .forEach(item => SoloWants.addToList(item));
+
+ return myItems.forEach(item => SoloWants.checkItem(item));
+ },
+
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ addToList: function (item) {
+ if (!item || me.classic || item.isRuneword) return false;
+ if (SoloWants.needList.some(check => item.classid === check.classid)) return false;
+ let hasWantedItems;
+ let list = [];
+ let socketedWith = item.getItemsEx();
+ let numSockets = item.sockets;
+ let curr = Config.socketables.find(({ classid }) => item.classid === classid);
+
+ if (curr && curr.socketWith.length > 0) {
+ hasWantedItems = socketedWith.some(el => curr.socketWith.includes(el.classid));
+ if (hasWantedItems && socketedWith.length === numSockets) {
+ return true; // this item is full
+ }
+
+ if (curr.socketWith.includes(sdk.items.runes.Hel)) {
+ let merc = me.getMerc();
+ switch (true) {
+ case Item.autoEquipCheck(item, true) && me.trueStr >= item.strreq && me.trueDex >= item.dexreq:
+ case Item.autoEquipCheckMerc(item, true) && !!merc && merc.rawStrength >= item.strreq && merc.rawDexterity >= item.dexreq:
+ curr.socketWith.splice(curr.socketWith.indexOf(sdk.items.runes.Hel), 1);
+ break;
+ }
+ }
+
+ if (curr.socketWith.length > 1 && hasWantedItems) {
+ // handle different wanted socketables, if we already have a wanted socketable inserted then remove it from the check list
+ socketedWith.forEach(function (socketed) {
+ if (curr.socketWith.length > 1 && curr.socketWith.includes(socketed.classid)) {
+ curr.socketWith.splice(curr.socketWith.indexOf(socketed.classid), 1);
+ }
+ });
+ }
+
+ // add the wanted items to the list
+ for (let i = 0; i < numSockets - (hasWantedItems ? socketedWith.length : 0); i++) {
+ // handle different wanted socketables
+ curr.socketWith.length === numSockets
+ ? list.push(curr.socketWith[i])
+ : list.push(curr.socketWith[0]);
+ }
+
+ // currently no sockets but we might use our socket quest on it
+ numSockets === 0 && curr.useSocketQuest && list.push(curr.socketWith[0]);
+
+ // if temp socketables are used for this item and its not already socketed with wanted items add the temp items too
+ if (!hasWantedItems && !!curr.temp && !!curr.temp.length > 0) {
+ for (let i = 0; i < numSockets - socketedWith.length; i++) {
+ list.push(curr.temp[0]);
+ }
+ // Make sure we keep a hel rune so we can unsocket temp socketables if needed
+ if (!SoloWants.needList.some(check => sdk.items.runes.Hel === check.classid)) {
+ let hel = me.getItemsEx(sdk.items.runes.Hel, sdk.items.mode.inStorage);
+ // we don't have any hel runes and its not already in our needList
+ if ((!hel || hel.length === 0)) {
+ SoloWants.needList.push({ classid: sdk.items.runes.Hel, needed: [sdk.items.runes.Hel] });
+ } else if (!hel.some(check => SoloWants.validGids.includes(check.gid))) {
+ SoloWants.needList.push({ classid: sdk.items.runes.Hel, needed: [sdk.items.runes.Hel] });
+ }
+ }
+ }
+ } else {
+ let itemtype = item.getItemType();
+ if (!itemtype) return false;
+ let gemType = ["Helmet", "Armor"].includes(itemtype)
+ ? "Ruby" : itemtype === "Shield"
+ ? "Diamond" : itemtype === "Weapon" && !Check.currentBuild().caster
+ ? "Skull" : "";
+ let runeType;
+
+ // Tir rune in normal, Io rune otherwise and Shael's if assassin TODO: use jewels too
+ !gemType && (runeType = me.normal ? "Tir" : me.assassin ? "Shael" : "Io");
+
+ hasWantedItems = socketedWith.some(el => gemType ? el.itemType === sdk.items.type[gemType] : el.classid === sdk.items.runes[runeType]);
+ if (hasWantedItems && socketedWith.length === numSockets) {
+ return true; // this item is full
+ }
+
+ for (let i = 0; i < numSockets - socketedWith.length; i++) {
+ list.push(gemType ? sdk.items.gems.Perfect[gemType] : sdk.items.runes[runeType]);
+ }
+ }
+
+ // add to our needList so we pick the items
+ return list.length > 0 ? this.needList.push({ classid: item.classid, needed: list }) : false;
+ },
+
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ update: function (item) {
+ if (!item) return false;
+ if (this.validGids.includes(item.gid)) return true; // already in the list
+ let i = 0;
+ for (let el of this.needList) {
+ if (!me.getItem(el.classid)) {
+ // We no longer have the item we wanted socketables for
+ this.needList.splice(i, 1);
+ continue;
+ }
+ if (item.isInsertable) {
+ if (el.needed.includes(item.classid)) {
+ this.validGids.push(item.gid);
+ this.needList[i].needed.splice(this.needList[i].needed.indexOf(item.classid), 1);
+ if (this.needList[i].needed.length === 0) {
+ // no more needed items so remove from list
+ this.needList.splice(i, 1);
+ }
+ return true;
+ }
+ }
+ i++; // keep track of index
+ }
+
+ return false;
+ },
+
+ ensureList: function () {
+ let i = 0;
+ for (let el of this.needList) {
+ if (!me.getItem(el.classid)) {
+ // We no longer have the item we wanted socketables for
+ this.needList.splice(i, 1);
+ continue;
+ }
+ i++; // keep track of index
+ }
+ },
+
+ // Cube ingredients
+ checkSubrecipes: function () {
+ for (let el of this.needList) {
+ for (let i = 0; i < el.needed.length; i++) {
+ switch (true) {
+ case [
+ sdk.items.gems.Perfect.Ruby, sdk.items.gems.Perfect.Sapphire, sdk.items.gems.Perfect.Topaz, sdk.items.gems.Perfect.Emerald,
+ sdk.items.gems.Perfect.Amethyst, sdk.items.gems.Perfect.Diamond, sdk.items.gems.Perfect.Skull].includes(el.needed[i]):
+ if (Cubing.subRecipes.indexOf(el.needed[i]) === -1) {
+ Cubing.subRecipes.push(el.needed[i]);
+ Cubing.recipes.push({
+ Ingredients: [el.needed[i] - 1, el.needed[i] - 1, el.needed[i] - 1],
+ Index: 0,
+ AlwaysEnabled: true,
+ MainRecipe: "Crafting"
+ });
+ }
+
+ break;
+ case el.needed[i] >= sdk.items.runes.El && el.needed[i] <= sdk.items.runes.Ort:
+ if (Cubing.subRecipes.indexOf(el.needed[i]) === -1) {
+ Cubing.subRecipes.push(el.needed[i]);
+ Cubing.recipes.push({
+ Ingredients: [el.needed[i] - 1, el.needed[i] - 1, el.needed[i] - 1],
+ Index: Recipe.Rune,
+ AlwaysEnabled: true,
+ MainRecipe: "Crafting"
+ });
+ }
+
+ break;
+ // case el.needed[i] >= sdk.items.runes.Thul && el.needed[i] <= sdk.items.runes.Lem:
+ // // gems repeat so should be able to math this out chipped (TASRED) -> repeat flawed (TASRED)
+ // if (Cubing.subRecipes.indexOf(el.needed[i]) === -1) {
+ // Cubing.subRecipes.push(el.needed[i]);
+ // Cubing.recipes.push({
+ // Ingredients: [el.needed[i] - 1, el.needed[i] - 1, el.needed[i] - 1],
+ // Index: Recipe.Rune,
+ // AlwaysEnabled: true,
+ // MainRecipe: "Crafting"
+ // });
+ // }
+
+ // break;
+ // case el.needed[i] >= sdk.items.runes.Mal && el.needed[i] <= sdk.items.runes.Zod:
+ // // gems repeat so should be able to math this out Base (TASRED) -> repeat Flawless (TASRE) (stops at Emerald)
+ // if (Cubing.subRecipes.indexOf(el.needed[i]) === -1) {
+ // Cubing.subRecipes.push(el.needed[i]);
+ // Cubing.recipes.push({
+ // Ingredients: [el.needed[i] - 1, el.needed[i] - 1],
+ // Index: Recipe.Rune,
+ // AlwaysEnabled: true,
+ // MainRecipe: "Crafting"
+ // });
+ // }
+
+ // break;
+ }
+ }
+ }
+
+ return true;
+ },
+};
diff --git a/libs/SoloPlay/Functions/StorageOverrides.js b/libs/SoloPlay/Functions/StorageOverrides.js
index 27cc5f85..9747cd81 100644
--- a/libs/SoloPlay/Functions/StorageOverrides.js
+++ b/libs/SoloPlay/Functions/StorageOverrides.js
@@ -8,645 +8,725 @@
*/
includeIfNotIncluded("SoloPlay/Tools/Developer.js");
-
-/**
- * @constructor
- * @param {string} name - container name
- * @param {number} width - container width
- * @param {number} height - container height
- * @param {number} location - container location
- */
-function Container(name, width, height, location) {
- this.name = name;
- this.width = width;
- this.height = height;
- this.location = location;
- /** @type {number[][]} */
- this.buffer = [];
- /** @type {ItemUnit[]} */
- this.itemList = [];
- this.openPositions = this.height * this.width;
-
- for (let h = 0; h < this.height; h += 1) {
- this.buffer.push([]);
-
- for (let w = 0; w < this.width; w += 1) {
- this.buffer[h][w] = 0;
- }
- }
-}
-
-/**
- * @param {ItemUnit} item
- */
-Container.prototype.Mark = function (item) {
- let x, y;
-
- // Make sure it is in this container.
- if (item.location !== this.location || (item.mode !== sdk.items.mode.inStorage && item.mode !== sdk.items.mode.inBelt)) {
- return false;
- }
-
- // Mark item in buffer.
- for (x = item.x; x < (item.x + item.sizex); x += 1) {
- for (y = item.y; y < (item.y + item.sizey); y += 1) {
- this.buffer[y][x] = this.itemList.length + 1;
- this.openPositions -= 1;
- }
- }
-
- // Add item to list.
- this.itemList.push(copyUnit(item));
-
- return true;
-};
-
-/**
- * @param {ItemUnit} item
- * @param {number[][]} baseRef
- */
-Container.prototype.IsLocked = function (item, baseRef) {
- let h, w;
- let reference = baseRef.slice(0);
-
- // Make sure it is in this container.
- if (item.mode !== sdk.items.mode.inStorage || item.location !== this.location) {
- return false;
- }
-
- // Make sure the item is ours
- if (!item.getParent() || item.getParent().type !== me.type || item.getParent().gid !== me.gid) {
- return false;
- }
-
- //Insure valid reference.
- if (typeof (reference) !== "object" || reference.length !== this.buffer.length || reference[0].length !== this.buffer[0].length) {
- throw new Error("Storage.IsLocked: Invalid inventory reference");
- }
-
- try {
- // Check if the item lies in a locked spot.
- for (h = item.y; h < (item.y + item.sizey); h += 1) {
- for (w = item.x; w < (item.x + item.sizex); w += 1) {
- if (reference[h][w] === 0) {
- return true;
- }
- }
- }
- } catch (e2) {
- throw new Error("Storage.IsLocked error! Item info: " + item.name + " " + item.y + " " + item.sizey + " " + item.x + " " + item.sizex + " " + item.mode + " " + item.location);
- }
-
- return false;
-};
-
-Container.prototype.Reset = function () {
- 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;
-};
-
-/**
- * @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;
- }
-
- 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.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);
-
- 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.isInStash && item.x === 0 && item.y === 0) {
- continue; // dont touch the cube
- }
-
- let ix = item.y, iy = item.x; // x and y are backwards!
-
- if (this.location !== item.location) {
- D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a non-storage item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold);
- continue; // dont try to touch non-storage items | TODO: prevent non-storage items from getting this far
- }
-
- if (this.location === sdk.storage.Inventory && this.IsLocked(item, Config.Inventory)) {
- continue; // locked spot / item
- }
-
- if (ix < x || iy < y) {
- continue; // not top left part of item
- }
-
- if (item.type !== sdk.unittype.Item) {
- D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a non-item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold);
- continue; // dont try to touch non-items | TODO: prevent non-items from getting this far
- }
-
- if (item.mode === sdk.items.mode.onGround) {
- D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a ground item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold);
- continue; // dont try to touch ground items | TODO: prevent ground items from getting this far
- }
-
- // always sort stash left-to-right
- if (this.location === sdk.storage.Stash) {
- nPos = this.FindSpot(item);
- } else if (this.location === sdk.storage.Inventory && ((!itemIdsLeft && !itemIdsRight) || !itemIdsLeft || itemIdsRight.includes(item.classid) || itemIdsLeft.indexOf(item.classid) === -1)) {
- // sort from right by default or if specified
- nPos = this.FindSpot(item, true, false, SetUp.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, SetUp.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} 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;
-
- let x, y, nx, ny, makeSpot;
- let startX, startY, endX, endY, xDir = 1, yDir = 1;
-
- startX = 0;
- startY = 0;
- endX = this.width - (item.sizex - 1);
- endY = this.height - (item.sizey - 1);
-
- Storage.Reload();
-
- 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;
- }
-
- //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 (SetUp.sortSettings.PrioritySorting && priorityClassIds && priorityClassIds.includes(item.classid)
- && !this.IsLocked(this.itemList[this.buffer[x][y] - 1], Config.Inventory) // don't try to make a spot by moving locked items! TODO: move this to the start of loop
- && (priorityClassIds.indexOf(bufferItemClass) === -1
- || priorityClassIds.indexOf(item.classid) < priorityClassIds.indexOf(bufferItemClass))) { // item in this spot needs to move!
- makeSpot = this.MakeSpot(item, {x: x, y: y}); // NOTE: passing these in buffer order [h/x][w/y]
-
- if (item.classid !== bufferItemClass // higher priority item
- || (item.classid === bufferItemClass && item.quality > bufferItemQuality) // same class, higher quality item
- || (item.classid === bufferItemClass && item.quality === bufferItemQuality && item.gfx > bufferItemGfx) // same quality, higher graphic item
- || (Config.AutoEquip && item.classid === bufferItemClass && item.quality === bufferItemQuality && item.gfx === bufferItemGfx // same graphic, higher tier item
- && NTIP.GetTier(item) > NTIP.GetTier(this.itemList[this.buffer[x][y] - 1]))) {
- makeSpot = this.MakeSpot(item, {x: x, y: y}); // NOTE: passing these in buffer order [h/x][w/y]
-
- if (makeSpot) {
- // this item cannot be moved
- if (makeSpot === -1) return false;
-
- return makeSpot;
- }
- }
- }
-
- if (item.gid === undefined) return false;
-
- // ignore same gid
- if (item.gid !== this.itemList[this.buffer[x][y] - 1].gid ) {
- continue;
- }
- }
-
- //Loop the item size to make sure we can fit it.
- for (nx = 0; nx < item.sizey; nx += 1) {
- for (ny = 0; ny < item.sizex; ny += 1) {
- if (this.buffer[x + nx][y + ny]) {
- // ignore same gid
- if (item.gid !== this.itemList[this.buffer[x + nx][y + ny] - 1].gid ) {
- continue Loop;
- }
- }
- }
- }
-
- return ({x: x, y: y});
- }
- }
-
- return false;
-};
-
-/**
- * @param {ItemUnit} item
- * @param {{x: number, y: number}} location
- * @param {boolean} force
- */
-Container.prototype.MakeSpot = function (item, location, force) {
- let x, y, endx, endy, tmpLocation;
- let [itemsToMove, itemsMoved] = [[], []];
- // TODO: test the scenario where all possible items have been moved, but this item still can't be placed
- // e.g. if there are many LCs in an inventory and the spot for a GC can't be freed up without
- // moving other items that ARE NOT part of the position desired
-
- // Make sure it's a valid item and item is in a priority sorting list
- if (!item || !item.classid
- || (SetUp.sortSettings.ItemsSortedFromRightPriority.indexOf(item.classid) === -1
- && SetUp.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 = !(SetUp.sortSettings.ItemsSortedFromRight.includes(item.classid));
- tmpLocation = this.FindSpot(itemsToMove[i], reverseX, false);
- // D2Bot.printToConsole(itemsToMove[i].name + " moving from " + itemsToMove[i].x + "," + itemsToMove[i].y + " to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold);
-
- if (this.MoveToSpot(itemsToMove[i], tmpLocation.y, tmpLocation.x)) {
- // D2Bot.printToConsole(itemsToMove[i].name + " moved to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold);
- itemsMoved.push(copyUnit(itemsToMove[i]));
- Storage.Reload(); // success on this item, reload!
- delay(1); // give reload a moment of time to avoid moving the same item twice
- } else {
- D2Bot.printToConsole(itemsToMove[i].name + " failed to move to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold);
-
- return false;
- }
- }
- }
-
- //D2Bot.printToConsole("MakeSpot success! " + item.name + " can now be placed at " + location.y + "," + location.x, sdk.colors.D2Bot.Gold);
- return ({x: location.x, y: location.y});
-};
-
-/**
- * @param {ItemUnit} item
- * @param {number} mX
- * @param {number} mY
- */
-Container.prototype.MoveToSpot = function (item, mX, mY) {
- let cItem, cube;
-
- // Cube -> Stash, must place item in inventory first
- if (item.location === sdk.storage.Cube && 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;
-
- 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);
- (cItem !== null && cube !== null) && sendPacket(1, sdk.packets.send.ItemToCube, 4, cItem.gid, 4, cube.gid);
-
- break;
- case sdk.storage.Stash:
- sendPacket(1, sdk.packets.send.ItemToBuffer, 4, item.gid, 4, x, 4, y, 4, 0x04);
-
- break;
- default:
- clickItemAndWait(sdk.clicktypes.click.item.Left, x, y, location);
-
- break;
- }
-
- let nDelay = getTickCount();
-
- while ((getTickCount() - nDelay) < Math.max(1000, me.ping * 2 + 200)) {
- if (!me.itemoncursor) return true;
-
- delay(10 + me.ping);
- }
- }
-
- return false;
- };
-
- if (Packet.itemToCursor(item)) {
- if (moveItem(mX, mY, this.location)) return true;
- moveItem(orgX, orgY, orgLoc) && console.debug("Failed to move " + item.fname + " to " + mX + "/" + mY);
- }
-
- return false;
-};
-
-/**
- * @param {ItemUnit} item
- */
-Container.prototype.MoveTo = function (item) {
- let nPos;
-
- try {
- //Can we even fit it in here?
- nPos = this.FindSpot(item);
- if (!nPos) return false;
-
- return this.MoveToSpot(item, nPos.y, nPos.x);
- } catch (e) {
- console.log("Storage.Container.MoveTo caught error : " + e + " - " + e.toSource());
-
- return false;
- }
-};
-
-Container.prototype.Dump = function () {
- let x, y, string;
-
- if (this.UsedSpacePercent() > 60) {
- for (x = 0; x < this.height; x += 1) {
- string = "";
-
- for (y = 0; y < this.width; y += 1) {
- string += (this.buffer[x][y] > 0) ? "ÿc1x" : "ÿc0o";
- }
-
- console.log(string);
- }
- }
-
- console.log("ÿc9SoloPlayÿc0: " + this.name + " has used " + this.UsedSpacePercent().toFixed(2) + "% of its total space");
-};
-
-Container.prototype.UsedSpacePercent = function () {
- let usedSpace = 0;
- let totalSpace = this.height * this.width;
-
- Storage.Reload();
-
- for (let x = 0; x < this.height; x += 1) {
- for (let y = 0; y < this.width; y += 1) {
- if (this.buffer[x][y] > 0) {
- usedSpace += 1;
- }
- }
- }
-
- return usedSpace * 100 / totalSpace;
-};
-
-/**
- * @param {number[][]} baseRef
- */
-Container.prototype.Compare = function (baseRef) {
- let h, w, n, item, itemList, reference;
-
- Storage.Reload();
-
- try {
- itemList = [];
- reference = baseRef.slice(0, baseRef.length);
-
- //Insure valid reference.
- if (typeof (reference) !== "object" || reference.length !== this.buffer.length || reference[0].length !== this.buffer[0].length) {
- throw new Error("Unable to compare different containers.");
- }
-
- for (h = 0; h < this.height; h += 1) {
- Loop:
- for (w = 0; w < this.width; w += 1) {
- item = this.itemList[this.buffer[h][w] - 1];
-
- if (!item) {
- continue;
- }
-
- for (n = 0; n < itemList.length; n += 1) {
- if (itemList[n].gid === item.gid) {
- continue Loop;
- }
- }
-
- //Check if the buffers changed and the current buffer has an item there.
- if (this.buffer[h][w] > 0 && reference[h][w] > 0) {
- itemList.push(copyUnit(item));
- }
- }
- }
-
- return itemList;
- } catch (e) {
- return false;
- }
-};
-
-Container.prototype.toSource = function () {
- return this.buffer.toSource();
-};
-
-/**
- * @type {storage} Storage
- */
-let Storage = new function () {
- this.Init = () => {
- this.StashY = me.classic ? 4 : Developer.plugyMode ? 10 : 8;
- this.Inventory = new Container("Inventory", 10, 4, 3);
- this.TradeScreen = new Container("Inventory", 10, 4, 5);
- this.Stash = new Container("Stash", (Developer.plugyMode ? 10 : 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) return 1; // nothing equipped
-
- do {
- if (item.bodylocation === sdk.body.Belt) {
- switch (item.code) {
- case "lbl": // sash
- case "vbl": // light belt
- return 2;
- case "mbl": // belt
- case "tbl": // heavy belt
- return 3;
- default: // everything else
- return 4;
- }
- }
- } while (item.getNext());
-
- return 1; // no belt
- };
-
- this.Reload = function () {
- this.Inventory.Reset();
- this.Stash.Reset();
- this.Belt.Reset();
- this.Cube.Reset();
- this.TradeScreen.Reset();
-
- let item = me.getItem();
- if (!item) return false;
-
- do {
- switch (item.location) {
- case sdk.storage.Inventory:
- this.Inventory.Mark(item);
-
- break;
- case sdk.storage.TradeWindow:
- this.TradeScreen.Mark(item);
-
- break;
- case sdk.storage.Belt:
- this.Belt.Mark(item);
-
- break;
- case sdk.storage.Cube:
- this.Cube.Mark(item);
-
- break;
- case sdk.storage.Stash:
- this.Stash.Mark(item);
-
- break;
- }
- } while (item.getNext());
-
- return true;
- };
-};
+(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) {
+ const { name, y, sizey, x, sizex, mode, location } = item;
+ const errMsg = "Storage.IsLocked error! Item info: ";
+ throw new Error(errMsg + name + " " + y + " " + sizey + " " + x + " " + sizex + " " + mode + " " + item.location);
+ }
+
+ return false;
+ };
+
+ Container.prototype.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;
+ };
+
+ /**
+ * @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.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);
+
+ let x, y, 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
+ }
+
+ let 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 = item.y, iy = item.x; // x and y are backwards!
+
+ if (this.location !== item.location) {
+ D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a non-storage item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold);
+ continue; // dont try to touch non-storage items | TODO: prevent non-storage items from getting this far
+ }
+
+ if (this.location === sdk.storage.Inventory && this.IsLocked(item, Config.Inventory)) {
+ continue; // locked spot / item
+ }
+
+ if (ix < x || iy < y) {
+ continue; // not top left part of item
+ }
+
+ if (item.type !== sdk.unittype.Item) {
+ D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a non-item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold);
+ continue; // dont try to touch non-items | TODO: prevent non-items from getting this far
+ }
+
+ if (item.mode === sdk.items.mode.onGround) {
+ D2Bot.printToConsole("StorageOverrides.js>SortItems WARNING: Detected a ground item in the list: " + item.name + " at " + ix + "," + iy, sdk.colors.D2Bot.Gold);
+ continue; // dont try to touch ground items | TODO: prevent ground items from getting this far
+ }
+
+ // always sort stash left-to-right
+ if (this.location === sdk.storage.Stash) {
+ nPos = this.FindSpot(item);
+ } else if (this.location === sdk.storage.Inventory
+ && (
+ (!itemIdsLeft && !itemIdsRight)
+ || !itemIdsLeft
+ || itemIdsRight.includes(item.classid)
+ || itemIdsLeft.indexOf(item.classid) === -1
+ )) {
+ // sort from right by default or if specified
+ nPos = this.FindSpot(item, true, false, SetUp.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, SetUp.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} 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;
+
+ /**
+ * @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);
+
+ Storage.Reload();
+
+ if (item.sizex && item.sizey && item.gid === undefined) {
+ // 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;
+ }
+
+ Storage.Reload();
+
+ 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;
+ }
+
+ //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 (SetUp.sortSettings.PrioritySorting && priorityClassIds && priorityClassIds.includes(item.classid)
+ && !this.IsLocked(this.itemList[this.buffer[x][y] - 1], Config.Inventory) // don't try to make a spot by moving locked items! TODO: move this to the start of loop
+ && (priorityClassIds.indexOf(bufferItemClass) === -1
+ || priorityClassIds.indexOf(item.classid) < priorityClassIds.indexOf(bufferItemClass))) { // item in this spot needs to move!
+ makeSpot = this.MakeSpot(item, { x: x, y: y }); // NOTE: passing these in buffer order [h/x][w/y]
+
+ if (item.classid !== bufferItemClass // higher priority item
+ || (item.classid === bufferItemClass && item.quality > bufferItemQuality) // same class, higher quality item
+ || (item.classid === bufferItemClass && item.quality === bufferItemQuality && item.gfx > bufferItemGfx) // same quality, higher graphic item
+ || (Config.AutoEquip && item.classid === bufferItemClass && item.quality === bufferItemQuality && item.gfx === bufferItemGfx // same graphic, higher tier item
+ && NTIP.GetTier(item) > NTIP.GetTier(this.itemList[this.buffer[x][y] - 1]))) {
+ makeSpot = this.MakeSpot(item, { x: x, y: y }); // NOTE: passing these in buffer order [h/x][w/y]
+
+ if (makeSpot) {
+ return makeSpot !== -1 ? makeSpot : false;
+ }
+ }
+ }
+
+ 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] = [[], []];
+ const {
+ ItemsSortedFromRight,
+ ItemsSortedFromLeftPriority,
+ ItemsSortedFromRightPriority
+ } = SetUp.sortSettings;
+ // 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
+ || (
+ ItemsSortedFromRightPriority.indexOf(item.classid) === -1
+ && 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 = !(ItemsSortedFromRight.includes(item.classid));
+ tmpLocation = this.FindSpot(itemsToMove[i], reverseX, false);
+ // D2Bot.printToConsole(itemsToMove[i].name + " moving from " + itemsToMove[i].x + "," + itemsToMove[i].y + " to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold);
+
+ if (this.MoveToSpot(itemsToMove[i], tmpLocation.y, tmpLocation.x)) {
+ // D2Bot.printToConsole(itemsToMove[i].name + " moved to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold);
+ itemsMoved.push(copyUnit(itemsToMove[i]));
+ Storage.Reload(); // success on this item, reload!
+ delay(1); // give reload a moment of time to avoid moving the same item twice
+ } else {
+ D2Bot.printToConsole(itemsToMove[i].name + " failed to move to " + tmpLocation.y + "," + tmpLocation.x, sdk.colors.D2Bot.Gold);
+
+ return false;
+ }
+ }
+ }
+
+ //D2Bot.printToConsole("MakeSpot success! " + item.name + " can now be placed at " + location.y + "," + location.x, sdk.colors.D2Bot.Gold);
+ return ({ x: location.x, y: location.y });
+ };
+
+ /**
+ * @param {ItemUnit} item
+ * @param {number} mX
+ * @param {number} mY
+ */
+ Container.prototype.MoveToSpot = function (item, mX, mY) {
+ let cube;
+
+ // handle getting to 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;
+ }
+ }
+
+ // Cube -> Stash, must place item in inventory first
+ if (item.isInCube && this.location === sdk.storage.Stash) {
+ cube = me.getItem(sdk.quest.item.Cube);
+ if (!cube || !Packet.itemToCursor(item)) return false;
+ cube.isInStash && Storage.Stash.CanFit(item) && Cubing.closeCube(true);
+ if (!cube.isInStash && !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;
+
+ const [orgX, orgY, orgLoc] = [item.x, item.y, item.location];
+ const moveItem = function (x, y, location) {
+ let cItem;
+ 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:
+ if (!getUIFlag(sdk.uiflags.Stash) && !Town.openStash()) {
+ continue;
+ }
+ 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) {
+ try {
+ if (item.location === this.location) return true;
+ // Can we even fit it in here?
+ let nPos = this.FindSpot(item);
+ if (!nPos) return false;
+
+ return this.MoveToSpot(item, nPos.y, nPos.x);
+ } catch (e) {
+ console.log("Storage.Container.MoveTo caught error : " + e + " - " + e.toSource());
+
+ return false;
+ }
+ };
+
+ Container.prototype.Dump = function () {
+ if (this.UsedSpacePercent() > 60) {
+ for (let x = 0; x < this.height; x += 1) {
+ let string = "";
+
+ for (let y = 0; y < this.width; y += 1) {
+ string += (this.buffer[x][y] > 0) ? "ÿc1x" : "ÿc0o";
+ }
+
+ console.log(string);
+ }
+ }
+
+ console.log("ÿc9SoloPlayÿc0: " + this.name + " has used " + this.UsedSpacePercent().toFixed(2) + "% of its total space");
+ };
+
+ Container.prototype.UsedSpacePercent = function () {
+ let usedSpace = 0;
+ let totalSpace = this.height * this.width;
+
+ Storage.Reload();
+
+ for (let x = 0; x < this.height; x += 1) {
+ for (let y = 0; y < this.width; y += 1) {
+ if (this.buffer[x][y] > 0) {
+ usedSpace += 1;
+ }
+ }
+ }
+
+ return usedSpace * 100 / totalSpace;
+ };
+
+ /**
+ * @param {number[][]} baseRef
+ */
+ Container.prototype.Compare = function (baseRef) {
+ Storage.Reload();
+
+ try {
+ const itemList = [];
+ const reference = baseRef.slice(0, baseRef.length);
+
+ // Ensure 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 (let h = 0; h < this.height; h += 1) {
+ Loop:
+ for (let w = 0; w < this.width; w += 1) {
+ const item = this.itemList[this.buffer[h][w] - 1];
+
+ if (!item) {
+ continue;
+ }
+
+ for (let n = 0; n < itemList.length; n += 1) {
+ if (itemList[n].gid === item.gid) {
+ continue Loop;
+ }
+ }
+
+ // Check if the buffers changed and the current buffer has an item there.
+ if (this.buffer[h][w] > 0 && reference[h][w] > 0) {
+ itemList.push(copyUnit(item));
+ }
+ }
+ }
+
+ return itemList;
+ } catch (e) {
+ return false;
+ }
+ };
+
+ Container.prototype.toSource = function () {
+ return this.buffer.toSource();
+ };
+
+ /**
+ * @type {storage} Storage
+ */
+ const Storage = new function () {
+ this.Init = () => {
+ this.StashY = me.classic ? 4 : Developer.plugyMode ? 10 : 8;
+ this.Inventory = new Container("Inventory", 10, 4, 3);
+ this.TradeScreen = new Container("Inventory", 10, 4, 5);
+ this.Stash = new Container("Stash", (Developer.plugyMode ? 10 : 6), this.StashY, 7);
+ this.Belt = new Container("Belt", 4 * this.BeltSize(), 1, 2);
+
+ /**
+ * @description Return column status (needed potions in each column)
+ * @param {0 | 1 | 2 | 3 | 4} beltSize
+ * @returns {[number, number, number, number]}
+ */
+ this.Belt.checkColumns = function (beltSize) {
+ beltSize === undefined && (beltSize = this.width / 4);
+ let col = [beltSize, beltSize, beltSize, beltSize];
+ let pot = me.getItem(-1, sdk.items.mode.inBelt);
+
+ // No potions
+ if (!pot) return col;
+
+ do {
+ col[pot.x % 4] -= 1;
+ } while (pot.getNext());
+
+ return col;
+ };
+
+ this.Cube = new Container("Horadric Cube", 3, 4, 6);
+ this.InvRef = [];
+
+ this.Reload();
+ };
+
+ this.BeltSize = function () {
+ let item = me.getItem(-1, sdk.items.mode.Equipped); // get equipped item
+ if (!item) return 1; // nothing equipped
+
+ do {
+ if (item.bodylocation === sdk.body.Belt) {
+ switch (item.code) {
+ case "lbl": // sash
+ case "vbl": // light belt
+ return 2;
+ case "mbl": // belt
+ case "tbl": // heavy belt
+ return 3;
+ default: // everything else
+ return 4;
+ }
+ }
+ } while (item.getNext());
+
+ return 1; // no belt
+ };
+
+ this.Reload = function () {
+ this.Inventory.Reset();
+ this.Stash.Reset();
+ this.Belt.Reset();
+ this.Cube.Reset();
+ this.TradeScreen.Reset();
+
+ let item = me.getItem();
+ if (!item) return false;
+
+ do {
+ switch (item.location) {
+ case sdk.storage.Inventory:
+ this.Inventory.Mark(item);
+
+ break;
+ case sdk.storage.TradeWindow:
+ this.TradeScreen.Mark(item);
+
+ break;
+ case sdk.storage.Belt:
+ this.Belt.Mark(item);
+
+ break;
+ case sdk.storage.Cube:
+ this.Cube.Mark(item);
+
+ break;
+ case sdk.storage.Stash:
+ this.Stash.Mark(item);
+
+ break;
+ }
+ } while (item.getNext());
+
+ return true;
+ };
+ };
+
+ // export to global scope
+ global.Storage = Storage;
+})();
diff --git a/libs/SoloPlay/Functions/TownOverrides.js b/libs/SoloPlay/Functions/TownOverrides.js
index bce5d043..9ec3d72c 100644
--- a/libs/SoloPlay/Functions/TownOverrides.js
+++ b/libs/SoloPlay/Functions/TownOverrides.js
@@ -12,1311 +12,1048 @@
* - some of these functions might be better as part of me. Example Town.getTpTool -> me.getTpTool makes more sense
*/
-includeIfNotIncluded("common/Town.js");
-
-new Overrides.Override(Town, Town.drinkPots, function(orignal, type) {
- const objDrank = orignal(type, false);
- const pots = {};
- pots[sdk.items.StaminaPotion] = "stamina";
- pots[sdk.items.AntidotePotion] = "antidote";
- pots[sdk.items.ThawingPotion] = "thawing";
-
- try {
- if (objDrank.potName) {
- let objID = objDrank.potName.split(" ")[0].toLowerCase();
-
- if (objID) {
- // non-english version
- if (!CharData.buffData[objID]) {
- typeof type === "number" ? (objID = pots[objID]) : (objID = type.toLowerCase());
- }
-
- if (!CharData.buffData[objID].active() || CharData.buffData[objID].timeLeft() <= 0) {
- CharData.buffData[objID].tick = getTickCount();
- CharData.buffData[objID].duration = objDrank.quantity * 30 * 1000;
- } else {
- CharData.buffData[objID].duration += (objDrank.quantity * 30 * 1000) - (getTickCount() - CharData.buffData[objID].tick);
- }
-
- console.log("ÿc9DrinkPotsÿc0 :: drank " + objDrank.quantity + " " + objDrank.potName + "s. Timer [" + Developer.formatTime(CharData.buffData[objID].duration) + "]");
- }
- }
- } catch (e) {
- console.error(e);
- return false;
- }
-
- return true;
+includeIfNotIncluded("core/Town.js");
+
+const wantedTasks = new Set();
+
+new Overrides.Override(Town, Town.drinkPots, function (orignal, type) {
+ const objDrank = orignal(type, false);
+ const pots = new Map([
+ [sdk.items.StaminaPotion, "stamina"],
+ [sdk.items.AntidotePotion, "antidote"],
+ [sdk.items.ThawingPotion, "thawing"]
+ ]);
+
+ try {
+ if (objDrank.potName) {
+ let objID = objDrank.potName.split(" ")[0].toLowerCase();
+
+ if (objID) {
+ // non-english version
+ if (!CharData.pots.has(objID)) {
+ typeof type === "number"
+ ? (objID = pots.get(objID))
+ : (objID = type.toLowerCase());
+ }
+
+ if (!CharData.pots.get(objID).active() || CharData.pots.get(objID).timeLeft() <= 0) {
+ CharData.pots.get(objID).tick = getTickCount();
+ CharData.pots.get(objID).duration = objDrank.quantity * 30 * 1000;
+ } else {
+ CharData.pots.get(objID).duration += (
+ objDrank.quantity * 30 * 1000) - (getTickCount() - CharData.pots.get(objID).tick
+ );
+ }
+
+ console.log("ÿc9DrinkPotsÿc0 :: drank " + objDrank.quantity + " " + objDrank.potName + "s. Timer [" + Time.format(CharData.pots.get(objID).duration) + "]");
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ return false;
+ }
+
+ return true;
}).apply();
// ugly for now but proxy the functions I moved to Me.js in case somewhere the base functions are being used
-Town.getIdTool = () => me.getIdTool();
-Town.getTpTool = () => me.getTpTool();
-Town.needPotions = () => me.needPotions();
-Town.fieldID = () => me.fieldID();
Town.getItemsForRepair = (repairPercent, chargedItems) => me.getItemsForRepair(repairPercent, chargedItems);
Town.needRepair = () => me.needRepair();
+Town.buyPotions = () => NPCAction.buyPotions();
+Town.fillTome = (classid, force = false) => NPCAction.fillTome(classid, force);
+Town.cainID = (force = false) => NPCAction.cainID(force);
+Town.lastShopped = { who: "", tick: 0 };
+// todo - allow earlier shopping, mainly to get a belt
+Town.shopItems = (force = false) => NPCAction.shopItems(force);
+Town.gamble = () => NPCAction.gamble();
Town.sell = [];
// Removed Missle Potions for easy gold
// Items that won't be stashed
Town.ignoredItemTypes = [
- 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
+ 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 Start a task and return the NPC Unit
+ * @param {string} task
+ * @param {string} reason
+ * @returns {boolean | NPCUnit}
+ */
+Town.initNPC = function (task = "", reason = "undefined") {
+ console.info(true, reason, "initNPC");
+ task = task.capitalize(false);
+
+ delay(250);
+
+ /** @type {NPCUnit} */
+ let npc = null;
+ let wantedNpc = Town.tasks.get(me.act)[task] !== undefined
+ ? Town.tasks.get(me.act)[task]
+ : "undefined";
+ const justUseClosest = (
+ ["clearinventory", "sell"].includes(reason.toLowerCase())
+ && !me.getUnids().length
+ );
+
+ if (getUIFlag(sdk.uiflags.NPCMenu)) {
+ console.debug("Currently interacting with an npc");
+ npc = getInteractedNPC();
+ }
+
+ try {
+ if (npc) {
+ let npcName = npc.name.toLowerCase();
+ if (!justUseClosest && ((npcName !== wantedNpc)
+ // Jamella gamble fix
+ || (task === "Gamble" && npcName === NPC.Jamella))) {
+ me.cancelUIFlags();
+ npc = null;
+ }
+ } else {
+ me.cancelUIFlags();
+ }
+
+ /**
+ * we are just trying to clear our inventory, use the closest npc
+ * Things to conisder:
+ * - what if we have unid items? Should we use cain if he is closer than the npc with scrolls?
+ * - what is our next task?
+ * - would it be faster to change acts and use the closest npc?
+ */
+ if (justUseClosest) {
+ let choices = new Set();
+ let npcs = Town.tasks.get(me.act);
+ let _needPots = me.needPotions();
+ let _needRepair = me.needRepair().length > 0;
+ if (_needPots && _needRepair) {
+ if (me.act === 2) {
+ choices = new Set([npcs.Key, npcs.Repair]);
+ } else {
+ choices = new Set([npcs.Key, npcs.Repair, npcs.Gamble, npcs.Shop]);
+ // todo - handle when we are in normal and current act < 4
+ // if we are going to go to a4 for potions anyway we should go ahead and change act
+ }
+ } else if (!_needPots && _needRepair) {
+ choices.add(npcs.Repair);
+ } else if (_needPots && !_needRepair) {
+ choices.add(npcs.Shop);
+ if (me.act === 2) {
+ choices.add(npcs.Key);
+ }
+ } else if (!_needPots && !_needRepair) {
+ choices = new Set([npcs.Key, npcs.Repair, npcs.Gamble, npcs.Shop]);
+ }
+ if (choices.size) {
+ console.log("closest npc choices", choices);
+ wantedNpc = Array.from(choices.values()).sort(function (a, b) {
+ return Town.getDistance(a) - Town.getDistance(b);
+ }).first();
+ console.debug("Choosing closest npc", wantedNpc);
+ }
+ }
+
+ if (me.act === 2) {
+ if (task === "Heal") {
+ // lets see if we are closer to Atma than Fara
+ if (Town.getDistance(NPC.Atma) < Town.getDistance(NPC.Fara)) {
+ wantedNpc = NPC.Atma;
+ }
+ } else if (reason === "buyPotions") {
+ if (Town.getDistance(NPC.Drognan) > 10) {
+ let _needStack = CharData.pots.get("thawing").need() || CharData.pots.get("antidote").need();
+ if (_needStack) {
+ wantedNpc = NPC.Lysander;
+ }
+ }
+ }
+ }
+
+ if (!npc && wantedNpc !== "undefined") {
+ npc = Game.getNPC(wantedNpc);
+
+ if (!npc && this.move(wantedNpc)) {
+ npc = Game.getNPC(wantedNpc);
+ }
+ }
+
+ 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":
+ if (String.isEqual(npc.name, NPC.Atma)) {
+ // prevent crash due to atma not being a shoppable npc
+ me.cancelUIFlags();
+ }
+ break;
+ }
+
+ console.info(false, "Did " + reason + " at " + npc.name, "initNPC");
+ } catch (e) {
+ console.error(e);
+
+ if (!!e.message && e.message === "Couldn't interact with npc") {
+ // getUnit bug probably, lets see if going to different act helps
+ let highestAct = me.highestAct;
+ if (highestAct === 1) return false; // can't go to any of the other acts
+ let myAct = me.act;
+ let potentialActs = [1, 2, 3, 4, 5].filter(a => a <= highestAct && a !== myAct);
+ let goTo = potentialActs[rand(0, potentialActs.length - 1)];
+ Config.DebugMode.Town && console.debug("Going to Act " + goTo + " to see if it fixes getUnit bug");
+ Town.goToTown(goTo);
+ }
+
+ return false;
+ }
+
+ Misc.poll(function () {
+ return me.gameReady;
+ }, 2000, 250);
+
+ 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
+ */
+Town.heal = function (force = false) {
+ if (!me.needHealing() && !force) return true;
+ if (me.act === 3
+ && Town.getDistance(Town.tasks.get(me.act).Heal) > 10) {
+ // if we need to repair items as well or stack pots we should go ahead and change act
+ // unless we are already at our intended npc
+ let _needRepair = me.needRepair().length > 0;
+ let _needStack = CharData.pots.get("thawing").need() || CharData.pots.get("antidote").need();
+ let _needMerc = me.needMerc();
+ let _needPotions = me.needPotions();
+ if (_needRepair || _needStack || _needMerc || _needPotions) {
+ if (!_needPotions || !me.normal || me.accessToAct(4)) {
+ // trying to prevent us from going to a1 and ending up buying minor pots in normal
+ Town.goToTown(me.highestAct >= 4 ? 4 : 1);
+ }
+ }
+ }
+ return !!(this.initNPC("Heal", "heal"));
+};
+
+/**
+ * Check if any of the systems need this item
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
Town.systemsKeep = function (item) {
- return (AutoEquip.wanted(item) || Cubing.keepItem(item) || Runewords.keepItem(item) || CraftingSystem.keepItem(item) || SoloWants.keepItem(item));
+ return (AutoEquip.wanted(item)
+ || Cubing.keepItem(item)
+ || Runewords.keepItem(item)
+ || CraftingSystem.keepItem(item)
+ || SoloWants.keepItem(item)
+ );
};
+/**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
Town.needForceID = function (item) {
- const result = Pickit.checkItem(item);
- return ([Pickit.Result.WANTED, Pickit.Result.CUBING].includes(result.result) && !item.identified && AutoEquip.hasTier(item));
+ const result = Pickit.checkItem(item);
+ return ([Pickit.Result.WANTED, Pickit.Result.CUBING].includes(result.result)
+ && !item.identified && AutoEquip.hasTier(item));
};
Town.haveItemsToSell = function () {
- Town.sell = Town.sell.filter(i => i && i.isInStorage);
- return Town.sell.length;
-};
-
-Town.buyPotions = function () {
- // Ain't got money fo' dat shyt
- if (me.gold < 450 || !me.getItem(sdk.items.TomeofTownPortal)) 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
- let [wantedHpPot, wantedMpPot] = [5, 5];
- // only do this if we are low on gold in the first place
- if (me.normal && me.gold < Config.LowGold) {
- const mpPotsEffects = PotData.getMpPots().map(el => el.effect[me.classid]);
- const hpPotsEffects = PotData.getHpPots().map(el => el.effect[me.classid]);
-
- wantedHpPot = (hpPotsEffects.findIndex(eff => me.hpmax / 2 < eff) + 1 || hpPotsEffects.length - 1);
- wantedMpPot = (mpPotsEffects.findIndex(eff => me.mpmax / 2 < eff) + 1 || mpPotsEffects.length - 1);
- console.debug("Wanted hpPot: " + wantedHpPot + " Wanted mpPot: " + wantedMpPot);
- }
-
- if (me.normal && me.highestAct >= 4) {
- let pAct = Math.max(wantedHpPot, wantedMpPot);
- pAct >= 4 ? me.act < 4 && this.goToTown(4) : pAct > me.act && this.goToTown(pAct);
- }
-
- 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) {
- const useShift = this.shiftCheck(col, beltSize);
- const wantedPot = Config.BeltColumn[i] === "hp" ? wantedHpPot : wantedMpPot;
- let pot = this.getPotion(npc, Config.BeltColumn[i], wantedPot);
-
- 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();
-
- const buyHPBuffers = () => {
- if (needBuffer && buffer.hp < Config.HPBuffer) {
- for (let i = 0; i < Config.HPBuffer - buffer.hp; i += 1) {
- let pot = this.getPotion(npc, "hp", wantedHpPot);
- !!pot && Storage.Inventory.CanFit(pot) && pot.buy(false);
- }
- }
- return true;
- };
- const buyMPBuffers = () => {
- if (needBuffer && buffer.mp < Config.MPBuffer) {
- for (let i = 0; i < Config.MPBuffer - buffer.mp; i += 1) {
- let pot = this.getPotion(npc, "mp", wantedMpPot);
- !!pot && Storage.Inventory.CanFit(pot) && pot.buy(false);
- }
- }
- return true;
- };
- // priortize mana pots if caster
- Check.currentBuild().caster ? buyMPBuffers() && buyHPBuffers() : buyHPBuffers() && buyMPBuffers();
-
- // keep cold/pois res high with potions
- if (me.gold > 50000 && npc.getItem(sdk.items.ThawingPotion)) {
- CharData.buffData.thawing.need() && Town.buyPots(12, "thawing", true);
- CharData.buffData.antidote.need() && Town.buyPots(12, "antidote", true);
- }
-
- return true;
+ let temp = [];
+ while (Town.sell.length) {
+ let i = Town.sell.shift();
+ if (typeof i === "undefined") continue;
+ let realItem = me.getItem(i.classid, -1, i.gid);
+ if (realItem && realItem.isInStorage
+ && !Town.ignoreType(realItem.itemType)
+ && realItem.sellable
+ && !Town.systemsKeep(realItem)) {
+ temp.push(realItem);
+ }
+ }
+ Town.sell = temp.slice(0);
+
+ return Town.sell.length;
};
+/**
+ * @param {ItemUnit[]} itemList
+ * @returns {boolean}
+ */
Town.sellItems = function (itemList = []) {
- !itemList.length && (itemList = Town.sell);
-
- if (this.initNPC("Shop", "sell")) {
- while (itemList.length) {
- let item = itemList.shift();
- if (!item.isInStorage) continue;
-
- try {
- if (getUIFlag(sdk.uiflags.Shop) || getUIFlag(sdk.uiflags.NPCMenu)) {
- console.log("sell " + item.name);
- Misc.itemLogger("Sold", item);
- item.sell();
- delay(100);
- }
- } catch (e) {
- console.error(e);
- }
- }
- }
-
- return !itemList.length;
+ !itemList.length && (itemList = Town.sell);
+ if (!itemList.length) return true;
+
+ if (this.initNPC("Shop", "sell")) {
+ while (itemList.length) {
+ let item = itemList.shift();
+ if (!item.isInStorage) continue;
+
+ try {
+ if (getUIFlag(sdk.uiflags.Shop) || getUIFlag(sdk.uiflags.NPCMenu)) {
+ console.log("sell " + item.prettyPrint);
+ Item.logger("Sold", item);
+ item.sell();
+ delay(100);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ }
+
+ return !itemList.length;
};
Town.checkScrolls = function (id, force = false) {
- 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 || force) ? 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);
-};
-
-Town.fillTome = function (classid, force = false) {
- if (me.gold < 450) return false;
- const have = this.checkScrolls(classid, force);
- if (have >= (me.charlvl < 12 ? 5 : 13)) return true;
-
- let scroll;
- const scrollId = (classid === sdk.items.TomeofTownPortal ? sdk.items.ScrollofTownPortal : sdk.items.ScrollofIdentify);
- let npc = this.initNPC("Shop", "fillTome");
- if (!npc) return false;
-
- delay(500);
-
- if (!me.findItem(classid, sdk.items.mode.inStorage, sdk.storage.Inventory)) {
- let tome = npc.getItem(classid);
-
- try {
- if (!tome || !Storage.Inventory.CanFit(tome)) throw new Error("Can't buy tome");
- tome.buy();
- } catch (e) {
- // couldn't buy tome, lets see if we can just buy a single scroll
- if (me.getItem(scrollId)) return true;
- scroll = npc.getItem(scrollId);
- if (!scroll || !Storage.Inventory.CanFit(scroll)) return false;
- try {
- scroll.buy();
- return true;
- } catch (e) {
- console.error(e);
- return false;
- }
- }
- }
-
- scroll = npc.getItem(scrollId);
- if (!scroll) return false;
-
- try {
- if (me.gold < 5000) {
- let myTome = me.getItem(classid);
- if (myTome) {
- while (myTome.getStat(sdk.stats.Quantity) < 5 && me.gold > 500) {
- scroll = npc.getItem(scrollId);
- scroll && Packet.buyScroll(scroll, myTome, false);
- delay(50);
- }
- }
- } else {
- scroll.buy(true);
- }
- } catch (e2) {
- console.error(e2);
-
- return false;
- }
-
- return true;
+ 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 || force) ? 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);
};
+/**
+ * @param {ItemUnit} item
+ * @param {{ result: PickitResult, line: string }} result
+ * @param {string} system
+ * @param {boolean} sell
+ * @returns {void}
+ */
Town.itemResult = function (item, result, system = "", sell = false) {
- let timer = 0;
- sell && !getInteractedNPC() && (sell = false);
-
- switch (result.result) {
- case Pickit.Result.WANTED:
- case Pickit.Result.SOLOWANTS:
- Misc.itemLogger("Kept", item);
- Misc.logItem("Kept", item, result.line);
- system === "Field" && ((Item.autoEquipCheck(item) && Item.autoEquip("Field")) || (Item.autoEquipCheckSecondary(item) && Item.autoEquipSecondary("Field")));
-
- break;
- case Pickit.Result.UNID:
- // At low level its not worth keeping these items until we can Id them it just takes up too much room
- if (sell && me.charlvl < 10 && item.magic && item.classid !== sdk.items.SmallCharm) {
- Misc.itemLogger("Sold", item);
- item.sell();
- }
-
- break;
- case Pickit.Result.CUBING:
- Misc.itemLogger("Kept", item, "Cubing-" + system);
- Cubing.update();
-
- break;
- case Pickit.Result.RUNEWORD:
- break;
- case Pickit.Result.CRAFTING:
- Misc.itemLogger("Kept", item, "CraftSys-" + system);
- CraftingSystem.update(item);
-
- break;
- case Pickit.Result.SOLOSYSTEM:
- Misc.itemLogger("Kept", item, "SoloWants-" + system);
- SoloWants.update(item);
-
- break;
- default:
- if (!item.sellable || !sell) return;
-
- switch (true) {
- case (Developer.debugging.smallCharm && item.classid === sdk.items.SmallCharm):
- case (Developer.debugging.largeCharm && item.classid === sdk.items.LargeCharm):
- case (Developer.debugging.grandCharm && item.classid === sdk.items.GrandCharm):
- Misc.logItem("Sold", item);
-
- break;
- default:
- Misc.itemLogger("Sold", item);
-
- break;
- }
-
- item.sell();
- timer = getTickCount() - this.sellTimer; // shop speedup test
-
- if (timer > 0 && timer < 500) {
- delay(timer);
- }
-
- break;
- }
-};
-
-Town.cainID = function (force = false) {
- if ((!Config.CainID.Enable && !force) || !Misc.checkQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed)) return false;
-
- let npc = getInteractedNPC();
-
- // Check if we're already in a shop. It would be pointless to go to Cain if so.
- if (npc && npc.name.toLowerCase() === this.tasks[me.act - 1].Shop) return false;
- // Check if we may use Cain - minimum gold
- if (me.gold < Config.CainID.MinGold && !force) return false;
-
- me.cancel();
- this.stash(false);
-
- let unids = me.getUnids();
-
- if (unids) {
- // Check if we may use Cain - number of unid items
- if (unids.length < Config.CainID.MinUnids && !force) return false;
-
- let cain = this.initNPC("CainID", "cainID");
- if (!cain) return false;
-
- if (force) {
- npc = this.initNPC("Shop", "clearInventory");
- if (!npc) return false;
- }
-
- for (let i = 0; i < unids.length; i += 1) {
- let item = unids[i];
- let result = Pickit.checkItem(item);
- // Force ID for unid items matching autoEquip/cubing criteria
- Town.needForceID(item) && (result.result = -1);
-
- switch (result.result) {
- case Pickit.Result.TRASH:
- try {
- console.log("sell " + item.name);
- this.initNPC("Shop", "clearInventory");
- Misc.itemLogger("Sold", item);
- item.sell();
- } catch (e) {
- console.error(e);
- }
-
- break;
- case Pickit.Result.WANTED:
- case Pickit.Result.SOLOWANTS:
- Misc.itemLogger("Kept", item);
- Misc.logItem("Kept", item, result.line);
-
- break;
- case Pickit.Result.CUBING:
- Misc.itemLogger("Kept", item, "Cubing-Town");
- Cubing.update();
-
- break;
- case Pickit.Result.RUNEWORD: // (doesn't trigger normally)
- break;
- case Pickit.Result.CRAFTING:
- Misc.itemLogger("Kept", item, "CraftSys-Town");
- CraftingSystem.update(item);
-
- break;
- case Pickit.Result.SOLOSYSTEM:
- Misc.itemLogger("Kept", item, "SoloWants-Town");
- SoloWants.update(item);
-
- break;
- default:
- if ((getUIFlag(sdk.uiflags.Shop) || getUIFlag(sdk.uiflags.NPCMenu)) && (item.getItemCost(sdk.items.cost.ToSell) <= 1 || !item.sellable)) {
- continue;
- }
-
- this.initNPC("Shop", "clearInventory");
-
- // Might as well sell the item if already in shop
- if (getUIFlag(sdk.uiflags.Shop) || (Config.PacketShopping && getInteractedNPC() && getInteractedNPC().itemcount > 0)) {
- console.log("clearInventory sell " + item.name);
-
- switch (true) {
- case (Developer.debugging.smallCharm && item.classid === sdk.items.SmallCharm):
- case (Developer.debugging.largeCharm && item.classid === sdk.items.LargeCharm):
- case (Developer.debugging.grandCharm && item.classid === sdk.items.GrandCharm):
- Misc.logItem("Sold", item);
-
- break;
- default:
- Misc.itemLogger("Dropped", item, "clearInventory");
-
- break;
- }
-
- item.sell();
- } else {
- console.log("clearInventory dropped " + item.name);
- Misc.itemLogger("Dropped", item, "clearInventory");
- item.drop();
- }
-
- let timer = getTickCount() - this.sellTimer; // shop speedup test
-
- if (timer > 0 && timer < 500) {
- delay(timer);
- }
-
- break;
- case Pickit.Result.UNID:
- if (tome) {
- this.identifyItem(item, tome);
- } 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);
- !!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) continue;
-
- this.identifyItem(item, scroll);
- }
-
- // roll back to now check against other criteria
- if (item.identified) {
- i--;
- }
-
- break;
- }
- }
- }
-
- return true;
+ let timer = 0;
+ sell && !getInteractedNPC() && (sell = false);
+
+ switch (result.result) {
+ case Pickit.Result.WANTED:
+ case Pickit.Result.SOLOWANTS:
+ Item.logger("Kept", item);
+ Item.logItem("Kept", item, result.line);
+ if (system === "Field") {
+ (
+ (Item.autoEquipCheck(item) && Item.autoEquip("Field"))
+ || (Item.autoEquipCheckSecondary(item) && Item.autoEquipSecondary("Field"))
+ );
+ }
+
+ break;
+ case Pickit.Result.UNID:
+ // At low level its not worth keeping these items until we can Id them it just takes up too much room
+ if (sell && me.charlvl < 10 && item.magic && item.classid !== sdk.items.SmallCharm) {
+ Item.logger("Sold", item);
+ item.sell();
+ }
+
+ break;
+ case Pickit.Result.CUBING:
+ Item.logger("Kept", item, "Cubing-" + system);
+ Cubing.update();
+
+ break;
+ case Pickit.Result.RUNEWORD:
+ break;
+ case Pickit.Result.CRAFTING:
+ Item.logger("Kept", item, "CraftSys-" + system);
+ CraftingSystem.update(item);
+
+ break;
+ case Pickit.Result.SOLOSYSTEM:
+ Item.logger("Kept", item, "SoloWants-" + system);
+ SoloWants.update(item);
+
+ break;
+ default:
+ if (!item.sellable || !sell) return;
+
+ switch (true) {
+ case (Developer.debugging.smallCharm && item.classid === sdk.items.SmallCharm):
+ case (Developer.debugging.largeCharm && item.classid === sdk.items.LargeCharm):
+ case (Developer.debugging.grandCharm && item.classid === sdk.items.GrandCharm):
+ Item.logItem("Sold", item);
+
+ break;
+ default:
+ Item.logger("Sold", item);
+
+ break;
+ }
+
+ item.sell();
+ timer = getTickCount() - this.sellTimer; // shop speedup test
+
+ if (timer > 0 && timer < 500) {
+ delay(timer);
+ }
+
+ break;
+ }
};
Town.identify = function () {
- if (me.gold < 5000 && this.cainID(true)) return true;
-
- let list = (Storage.Inventory.Compare(Config.Inventory) || []);
- if (!list.length) 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 && ([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 (!this.ignoredItemTypes.includes(item.itemType) && item.location === sdk.storage.Inventory && !item.identified) {
- let result = Pickit.checkItem(item);
- // Force ID for unid items matching autoEquip/cubing criteria
- Town.needForceID(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 = me.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);
- !!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);
- Town.itemResult(item, result, "TownId", true);
-
- break;
- }
- }
- }
-
- this.fillTome(sdk.items.TomeofTownPortal); // Check for TP tome in case it got sold for ID scrolls
-
- return true;
+ /**
+ * @todo use cain we are closer to him than our shop npc
+ */
+ if (me.gold < 15000 && NPCAction.cainID(true)) return true;
+
+ let list = (Storage.Inventory.Compare(Config.Inventory) || [])
+ .filter(function (item) {
+ return !item.identified;
+ });
+ if (!list.length) return false;
+
+ // Avoid unnecessary NPC visits
+ // Only unid items or sellable junk (low level) should trigger a NPC visit
+ if (!list.some(function (item) {
+ let identified = item.identified;
+ if (!identified && AutoEquip.hasTier(item)) return true;
+ return ([Pickit.Result.UNID, Pickit.Result.TRASH].includes(Pickit.checkItem(item).result));
+ })) {
+ return false;
+ }
+
+ let tome = me.getTome(sdk.items.TomeofIdentify);
+ // if we have a tome might as well use it - this might prevent us from having to run from one npc to another
+ if (tome && tome.getStat(sdk.stats.Quantity) > 0
+ && Town.getDistance(Town.tasks.get(me.act).Shop) > 5) {
+ // not in the field but oh well no need to repeat the code
+ if (me.fieldID() && !me.getUnids().length) {
+ return true;
+ }
+ }
+
+ if (me.act === 3
+ && Town.getDistance(Town.tasks.get(me.act).Shop) > 10) {
+ // if we need to repair items as well or stack pots we should go ahead and change act
+ // unless we are already at our intended npc
+ let _needRepair = me.needRepair().length > 0;
+ let _needStack = CharData.pots.get("thawing").need() || CharData.pots.get("antidote").need();
+ let _needMerc = me.needMerc();
+ let _needPotions = me.normal && me.accessToAct(4) && me.needPotions();
+ if (_needRepair || _needStack || _needMerc || _needPotions) {
+ Town.goToTown(me.highestAct >= 4 ? 4 : 1);
+ }
+ }
+
+ let npc = Town.initNPC("Shop", "identify");
+ if (!npc) return false;
+
+ tome && tome.getStat(sdk.stats.Quantity) < list.length && NPCAction.fillTome(sdk.items.TomeofIdentify);
+
+ MainLoop:
+ while (list.length > 0) {
+ const item = list.shift();
+
+ if (!Town.ignoreType(item.itemType) && item.isInInventory && !item.identified) {
+ let result = Pickit.checkItem(item).result;
+ // Force ID for unid items matching autoEquip/cubing criteria
+ Town.needForceID(item) && (result = -1);
+
+ switch (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 = me.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.getTome(sdk.items.TomeofTownPortal);
+ !!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);
+ Town.itemResult(item, result, "TownId", true);
+
+ break;
+ }
+ }
+ }
+
+ NPCAction.fillTome(sdk.items.TomeofTownPortal); // Check for TP tome in case it got sold for ID scrolls
+
+ return true;
};
-Town.lastShopped = { who: "", tick: 0 };
-
-// todo - allow earlier shopping, mainly to get a belt
-Town.shopItems = function (force = false) {
- if (!Config.MiniShopBot) return true;
- // todo - better gold scaling
- let goldLimit = [10000, 20000, 30000][me.diff];
- let itemTypes = [];
- let lowLevelShop = false;
- if (me.gold < goldLimit && me.charlvl > 6) {
- return false;
- } else if (me.charlvl < 6 && me.gold > 200) {
- lowLevelShop = true;
- Storage.BeltSize() === 1 && itemTypes.push(sdk.items.type.Belt);
- !CharData.skillData.bowData.bowOnSwitch && itemTypes.push(sdk.items.type.Bow, sdk.items.type.Crossbow);
- if (!itemTypes.length) return true;
- goldLimit = 200;
- }
-
- let npc = getInteractedNPC();
- if (!npc || !npc.itemcount) {
- // for now we only do force shop on low level
- if (force && itemTypes.length) {
- console.debug("Attempt force shopping");
- Town.initNPC("Repair", "shopItems");
- npc = getInteractedNPC();
- if (!npc || !npc.itemcount) return false;
- } else {
- return false;
- }
- }
-
- let items = npc.getItemsEx()
- .filter((item) => Town.ignoredItemTypes.indexOf(item.itemType) === -1 && (itemTypes.length === 0 || itemTypes.includes(item.itemType)))
- .sort((a, b) => NTIP.GetTier(b) - NTIP.GetTier(a));
- if (!items.length) return false;
- if (getTickCount() - Town.lastShopped.tick < Time.seconds(3) && Town.lastShopped.who === npc.name) return false;
-
- console.time("shopItems");
- let bought = 0;
- const haveMerc = (!me.classic && Config.UseMerc && !me.mercrevivecost && Misc.poll(() => !!me.getMerc(), 500, 100));
- console.info(true, "ÿc4MiniShopBotÿc0: Scanning " + npc.itemcount + " items.");
-
- const shopReport = (item, action, result, tierInfo) => {
- action === undefined && (action = "");
- tierInfo === undefined && (tierInfo = "");
- console.log("ÿc8Kolbot-SoloPlayÿc0: " + action + (tierInfo ? " " + tierInfo : ""));
- Misc.itemLogger(action, item);
- Developer.debugging.autoEquip && Misc.logItem("Shopped " + action, item, result.line !== undefined ? result.line : "null");
- };
-
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- const myGold = me.gold;
- const itemCost = item.getItemCost(sdk.items.cost.ToBuy);
- if (myGold < itemCost) continue;
- const result = Pickit.checkItem(item);
-
- // no tier'ed items
- if (!lowLevelShop && result.result === Pickit.Result.SOLOWANTS && NTIP.CheckItem(item, NTIP.SoloCheckListNoTier, true).result !== Pickit.Result.UNWANTED) {
- try {
- if (Storage.Inventory.CanFit(item) && myGold >= itemCost && (myGold - itemCost > goldLimit)) {
- if (item.isBaseType) {
- if (Item.betterThanStashed(item) && Item.betterBaseThanWearing(item, Developer.debugging.baseCheck)) {
- shopReport(item, "better base", result.line);
- item.buy() && bought++;
-
- continue;
- }
- } else {
- shopReport(item, "NoTier", result.line);
- item.buy() && bought++;
-
- continue;
- }
- }
- } catch (e) {
- console.error(e);
- }
- }
-
- // tier'ed items
- if (result.result === Pickit.Result.SOLOWANTS && AutoEquip.wanted(item)) {
- const checkDependancy = (item) => {
- let check = Item.hasDependancy(item);
- if (check) {
- let el = npc.getItem(check);
- !!el && el.buy();
- }
- };
- try {
- if (Storage.Inventory.CanFit(item) && myGold >= itemCost && (myGold - itemCost > goldLimit)) {
- if (Item.hasTier(item) && Item.autoEquipCheck(item)) {
- try {
- shopReport(item, "AutoEquip", result.line, (item.fname + " Tier: " + NTIP.GetTier(item)));
- item.buy() && bought++;
- Item.autoEquip("InShop");
- } catch (e) {
- console.error(e);
- }
-
- checkDependancy(item);
-
- continue;
- }
-
- if (!lowLevelShop && haveMerc && Item.hasMercTier(item) && Item.autoEquipCheckMerc(item)) {
- shopReport(item, "AutoEquipMerc", result.line, (item.fname + " Tier: " + NTIP.GetMercTier(item)));
- item.buy() && bought++;
-
- continue;
- }
-
- if (Item.hasSecondaryTier(item) && Item.autoEquipCheckSecondary(item)) {
- try {
- shopReport(item, "AutoEquip Switch Shopped", result.line, (item.fname + " SecondaryTier: " + NTIP.GetSecondaryTier(item)));
- item.buy() && bought++;
- Item.autoEquip("InShop");
- } catch (e) {
- console.error(e);
- }
-
- checkDependancy(item);
-
- continue;
- }
- }
- } catch (e) {
- console.error(e);
- }
- }
-
- delay(2);
- }
-
- Town.lastShopped.tick = getTickCount();
- Town.lastShopped.who = npc.name;
-
- console.info(false, "Bought " + bought + " items", "shopItems");
-
- return true;
-};
-
-Town.gamble = function () {
- if (!this.needGamble() || Config.GambleItems.length === 0) return true;
-
- let list = [];
-
- 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(Pather.accessToAct(4) ? 4 : 2);
-
- let npc = this.initNPC("Gamble", "gamble");
- if (!npc) return false;
-
- 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 {
- 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;
-
- items[i].buy(false, true);
-
- let newItem = this.getGambledItem(list);
-
- if (newItem) {
- let result = Pickit.checkItem(newItem);
-
- 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");
- newItem.sell();
-
- if (!Config.PacketShopping) {
- delay(500);
- }
-
- break;
- }
- }
- }
- }
-
- me.cancel();
- }
-
- return true;
+Town.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) || [])
+ .filter(function (item) {
+ return !Town.ignoreType(item.itemType) && (!item.isCharm || !CharmEquip.keptGids.has(item.gid));
+ })
+ .some(function (item) {
+ return Storage.Stash.CanFit(item);
+ });
};
/**
* @param {ItemUnit} item
*/
Town.canStash = function (item) {
- if (this.ignoredItemTypes.includes(item.itemType)
- || [sdk.items.quest.HoradricStaff, sdk.items.quest.KhalimsWill].includes(item.classid)
- || (item.isCharm && Item.autoEquipCharmCheck(item))) {
- return false;
- }
+ if (Town.ignoreType(item.itemType)
+ || [sdk.items.quest.HoradricStaff, sdk.items.quest.KhalimsWill].includes(item.classid)
+ || (item.isCharm && CharmEquip.check(item))) {
+ return false;
+ }
- !Storage.Stash.CanFit(item) && this.sortStash(true);
+ !Storage.Stash.CanFit(item) && this.sortStash(true);
- return Storage.Stash.CanFit(item);
+ return Storage.Stash.CanFit(item);
};
Town.stash = function (stashGold = true) {
- if (!this.needStash()) return true;
- !getUIFlag(sdk.uiflags.Stash) && me.cancel();
-
- let items = (Storage.Inventory.Compare(Config.Inventory) || []);
-
- items.length > 0 && items.forEach(item => {
- if (this.canStash(item)) {
- const pickResult = Pickit.checkItem(item).result;
- switch (true) {
- case pickResult !== Pickit.Result.UNWANTED && pickResult !== Pickit.Result.TRASH:
- case Town.systemsKeep(item):
- case AutoEquip.wanted(item) && pickResult === Pickit.Result.UNWANTED: // wanted but can't use yet
- case !item.sellable: // quest/essences/keys/ect
- Storage.Stash.MoveTo(item) && Misc.itemLogger("Stashed", item);
- break;
- default:
- break;
- }
- }
- });
-
- // 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;
-};
-
-Town.sortInventory = function () {
- Storage.Inventory.SortItems(SetUp.sortSettings.ItemsSortedFromLeft, SetUp.sortSettings.ItemsSortedFromRight);
- return true;
+ if (!this.needStash()) return true;
+ !getUIFlag(sdk.uiflags.Stash) && me.cancel();
+
+ let items = (Storage.Inventory.Compare(Config.Inventory) || []);
+
+ if (items.length > 0) {
+ Storage.Stash.SortItems();
+
+ items.forEach(function (item) {
+ if (Town.canStash(item)) {
+ const pickResult = Pickit.checkItem(item).result;
+ switch (true) {
+ case pickResult !== Pickit.Result.UNWANTED && pickResult !== Pickit.Result.TRASH:
+ case Town.systemsKeep(item):
+ case AutoEquip.wanted(item) && pickResult === Pickit.Result.UNWANTED: // wanted but can't use yet
+ case !item.sellable: // quest/essences/keys/ect
+ if ([sdk.quest.item.PotofLife, sdk.quest.item.ScrollofResistance].includes(item.classid)) {
+ // don't stash item, use it
+ let refName = item.prettyPrint;
+ if (item.use()) {
+ console.log("Used " + refName);
+ return;
+ }
+ }
+ Storage.Stash.MoveTo(item) && Item.logger("Stashed", item);
+
+ break;
+ }
+ }
+ });
+ }
+
+
+ // 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;
};
Town.sortStash = function (force = false) {
- if (Storage.Stash.UsedSpacePercent() < 50 && !force) return true;
- Storage.Stash.SortItems();
-
- return true;
+ if (Storage.Stash.UsedSpacePercent() < 50 && !force) return true;
+ return Storage.Stash.SortItems();
};
-Town.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 false;
-
- 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();
- let switchBowCheck = CharData.skillData.bowData.bowOnSwitch;
- !bowCheck && switchBowCheck && (bowCheck = (() => {
- switch (CharData.skillData.bowData.bowType) {
- case sdk.items.type.Bow:
- case sdk.items.type.AmazonBow:
- return "bow";
- case sdk.items.type.Crossbow:
- return "crossbow";
- default:
- return "";
- }
- })());
-
- if (bowCheck) {
- let quiver = bowCheck === "bow" ? "aqv" : "cqv";
- switchBowCheck && me.switchWeapons(sdk.player.slot.Secondary);
- let myQuiver = me.getItem(quiver, sdk.items.mode.Equipped);
- !!myQuiver && myQuiver.drop();
-
- npc = this.initNPC("Repair", "buyQuiver");
- if (!npc) return false;
-
- quiver = npc.getItem(quiver);
- !!quiver && quiver.buy();
- switchBowCheck && me.switchWeapons(sdk.player.slot.Main);
- }
-
- break;
- }
- }
-
- Town.shopItems();
-
- return true;
+Town.clearInventory = function () {
+ console.log("ÿc8Start ÿc0:: ÿc8clearInventory");
+ let clearInvoTick = getTickCount();
+
+ // If we are at an npc already, open the window otherwise moving potions around fails
+ if (getUIFlag(sdk.uiflags.NPCMenu) && !getUIFlag(sdk.uiflags.Shop)) {
+ try {
+ console.debug("Open npc menu");
+ !!getInteractedNPC() && Misc.useMenu(sdk.menu.Trade);
+ } catch (e) {
+ console.error(e);
+ me.cancelUIFlags();
+ }
+ }
+
+ // Remove potions in the wrong slot of our belt
+ me.clearBelt();
+
+ // Return potions from inventory to belt
+ me.cleanUpInvoPotions();
+
+ // Cleanup remaining potions
+ 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);
+ }
+ }
+
+ if (Config.FieldID.Enabled && !me.getItem(sdk.items.TomeofIdentify)) {
+ let scrolls = me.getItemsEx()
+ .filter(function (i) {
+ return i.isInInventory && i.classid === sdk.items.ScrollofIdentify;
+ });
+
+ while (scrolls.length > 2) {
+ sellOrDrop.push(scrolls.shift());
+ }
+ }
+
+ // Any leftover items from a failed ID (crashed game, disconnect etc.)
+ 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;
+ if (item.classid === sdk.items.TomeofIdentify && !Config.FieldID.Enabled) return true;
+ if (ignoreTypes.indexOf(item.itemType) === -1 && item.sellable && !Town.systemsKeep(item)) {
+ return true;
+ }
+ return false;
+ });
+
+ /** @type {ItemUnit[]} */
+ let sell = [];
+
+ /**
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ const classItemType = function (item) {
+ return [
+ sdk.items.type.Wand, sdk.items.type.VoodooHeads,
+ sdk.items.type.AuricShields, sdk.items.type.PrimalHelm, sdk.items.type.Pelt
+ ].includes(item.itemType);
+ };
+
+ items.length > 0 && items.forEach(function (item) {
+ let result = Pickit.checkItem(item).result;
+
+ if ([Pickit.Result.UNWANTED, Pickit.Result.TRASH].indexOf(result) === -1) {
+ if ((item.isBaseType && item.sockets > 0) || (classItemType(item) && item.normal && item.sockets === 0)) {
+ if (!Item.betterThanStashed(item) && !Item.betterBaseThanWearing(item, Developer.debugging.baseCheck)) {
+ if (NTIP.CheckItem(item, NTIP.CheckList) === Pickit.Result.UNWANTED) {
+ result = Pickit.Result.TRASH;
+ }
+ }
+ }
+ }
+
+ !item.identified && (result = -1);
+ [Pickit.Result.UNWANTED, Pickit.Result.TRASH].includes(result) && sell.push(item);
+ });
+
+ sell = (sell.length > 0
+ ? sell.concat(sellOrDrop)
+ : sellOrDrop.slice(0)
+ );
+ if (sell.length > 0) {
+ if (me.act === 3
+ && Town.getDistance(Town.tasks.get(me.act).Shop) > 10) {
+ // if we need to repair items as well or stack pots we should go ahead and change act
+ // unless we are already at our intended npc
+ let _needRepair = me.needRepair().length > 0;
+ let _needStack = CharData.pots.get("thawing").need() || CharData.pots.get("antidote").need();
+ let _needMerc = me.needMerc();
+ let _needPotions = me.needPotions();
+ if (_needRepair || _needStack || _needMerc || _needPotions) {
+ if (!_needPotions || !me.normal || me.accessToAct(4)) {
+ // trying to prevent us from going to a1 and ending up buying minor pots in normal
+ Town.goToTown(me.highestAct >= 4 ? 4 : 1);
+ }
+ }
+ }
+ if (this.initNPC("Shop", "clearInventory")) {
+ sell.forEach(function (item) {
+ try {
+ if (getUIFlag(sdk.uiflags.Shop) || getUIFlag(sdk.uiflags.NPCMenu)) {
+ console.log("clearInventory sell " + item.prettyPrint);
+ Item.logger("Sold", item);
+ item.sell();
+ delay(100);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ });
+ }
+ }
+
+ Town.sell = [];
+
+ console.log("ÿc8Exit clearInventory ÿc0- ÿc7Duration: ÿc0" + Time.format(getTickCount() - clearInvoTick));
+
+ if (wantedTasks.has("gamble") && Town.getDistance(Town.tasks.get(me.act).Gamble) < 10) {
+ NPCAction.gamble() && wantedTasks.delete("gamble");
+ }
+
+ return true;
};
-Town.reviveMerc = function () {
- if (!me.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();
- if (!dialog) continue;
-
- 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.getMercEx()) {
- delay(Math.max(750, me.ping * 2));
-
- break MainLoop;
- }
-
- delay(200);
- }
- }
-
- Attack.checkInfinity();
-
- if (me.getMercEx()) {
- // 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;
+Town.clearJunk = function () {
+ let junkItems = me.getItemsEx()
+ .filter(function (i) {
+ return i.isInStorage && !Town.ignoreType(i.itemType) && i.sellable && !Town.systemsKeep(i);
+ });
+ if (!junkItems.length) return false;
+
+ console.log("ÿc8Start ÿc0:: ÿc8clearJunk");
+ let clearJunkTick = getTickCount();
+
+ /**
+ * @type {ItemUnit[][]}
+ */
+ let [totalJunk, junkToSell, junkToDrop] = [[], [], []];
+
+ /**
+ * @param {string} str
+ * @param {ItemUnit} item
+ * @returns {boolean}
+ */
+ const getToItem = function (str = "", item = null) {
+ if (!getUIFlag(sdk.uiflags.Stash) && item.isInStash && !Town.openStash()) {
+ throw new Error("ÿc9" + str + "ÿc0 :: Failed to get " + item.prettyPrint + " from stash");
+ }
+ if (item.isInCube && !Cubing.emptyCube()) {
+ throw new Error("ÿc9" + str + "ÿc0 :: Failed to remove " + item.prettyPrint + " from cube");
+ }
+ return true;
+ };
+
+ while (junkItems.length > 0) {
+ const junk = junkItems.shift();
+ const pickitResult = Pickit.checkItem(junk).result;
+
+ try {
+ if ([Pickit.Result.UNWANTED, Pickit.Result.TRASH].includes(pickitResult)) {
+ console.log("ÿc9JunkCheckÿc0 :: Junk: " + junk.prettyPrint + " Pickit Result: " + pickitResult);
+ getToItem("JunkCheck", junk) && totalJunk.push(junk);
+
+ continue;
+ }
+
+ if (pickitResult !== Pickit.Result.WANTED) {
+ if (!junk.identified && !Cubing.keepItem(junk)
+ && !CraftingSystem.keepItem(junk) && junk.quality < sdk.items.quality.Set) {
+ console.log("ÿc9UnidJunkCheckÿc0 :: Junk: " + junk.prettyPrint + " Junk type: " + junk.itemType + " Pickit Result: " + pickitResult);
+ getToItem("UnidJunkCheck", junk) && totalJunk.push(junk);
+
+ continue;
+ }
+ }
+
+ if (junk.isRuneword && !AutoEquip.wanted(junk)) {
+ console.log("ÿc9AutoEquipJunkCheckÿc0 :: Junk: " + junk.prettyPrint + " Junk type: " + junk.itemType + " Pickit Result: " + pickitResult);
+ getToItem("AutoEquipJunkCheck", junk) && totalJunk.push(junk);
+
+ continue;
+ }
+
+ if (junk.isBaseType && [Pickit.Result.CUBING, Pickit.Result.SOLOWANTS].includes(pickitResult)) {
+ if (!Item.betterThanStashed(junk)) {
+ console.log("ÿc9BetterThanStashedCheckÿc0 :: Base: " + junk.prettyPrint + " Junk type: " + junk.itemType + " Pickit Result: " + pickitResult);
+ getToItem("BetterThanStashedCheck", junk) && totalJunk.push(junk);
+
+ continue;
+ }
+
+ if (!Item.betterBaseThanWearing(junk, Developer.debugging.baseCheck)) {
+ console.log("ÿc9BetterThanWearingCheckÿc0 :: Base: " + junk.prettyPrint + " Junk type: " + junk.itemType + " Pickit Result: " + pickitResult);
+ getToItem("BetterThanWearingCheck", junk) && totalJunk.push(junk);
+
+ continue;
+ }
+ }
+ } catch (e) {
+ console.warn(e.message ? e.message : e);
+ }
+ }
+
+ if (totalJunk.length > 0) {
+ totalJunk
+ .sort(function (a, b) {
+ return b.getItemCost(sdk.items.cost.ToSell) - a.getItemCost(sdk.items.cost.ToSell);
+ })
+ .forEach(function (item) {
+ // extra check should ensure no pickit wanted items get sold/dropped
+ if (NTIP.CheckItem(item, NTIP.CheckList) === Pickit.Result.WANTED) return;
+ if (item.isInInventory || (Storage.Inventory.CanFit(item) && Storage.Inventory.MoveTo(item))) {
+ junkToSell.push(item);
+ } else {
+ junkToDrop.push(item);
+ }
+ });
+
+ myPrint("Junk items to sell: " + junkToSell.length);
+ Town.initNPC("Shop", "clearInventory");
+
+ if (getUIFlag(sdk.uiflags.Shop)
+ || (Config.PacketShopping && getInteractedNPC() && getInteractedNPC().itemcount > 0)) {
+ for (let item of junkToSell) {
+ console.log("ÿc9JunkCheckÿc0 :: Sell " + item.prettyPrint);
+ Item.logger("Sold", item);
+ Developer.debugging.junkCheck && Item.logItem("JunkCheck Sold", item);
+
+ item.sell();
+ delay(100);
+ }
+ }
+
+ me.cancelUIFlags();
+
+ for (let item of junkToDrop) {
+ console.log("ÿc9JunkCheckÿc0 :: Drop " + item.prettyPrint);
+ Item.logger("Sold", item);
+ Developer.debugging.junkCheck && Item.logItem("JunkCheck Sold", item);
+
+ item.drop();
+ delay(100);
+ }
+ }
+
+ console.log("ÿc8Exit clearJunk ÿc0- ÿc7Duration: ÿc0" + Time.format(getTickCount() - clearJunkTick));
+
+ return true;
};
-Town.clearInventory = function () {
- console.log("ÿc8Start ÿc0:: ÿc8clearInventory");
- let clearInvoTick = getTickCount();
-
- // 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
- me.cleanUpInvoPotions();
-
- // Cleanup remaining potions
- console.debug("clearInventory: start clean-up remaining pots");
- let sellOrDrop = [];
- let 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(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:
- return (specials.push(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);
- }
- }
-
- if (Config.FieldID.Enabled && !me.getItem(sdk.items.TomeofIdentify)) {
- let scrolls = me.getItemsEx().filter(i => i.isInInventory && i.classid === sdk.items.ScrollofIdentify);
-
- while (scrolls.length > 2) {
- sellOrDrop.push(scrolls.shift());
- }
- }
-
- // Any leftover items from a failed ID (crashed game, disconnect etc.)
- 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;
- if (item.classid === sdk.items.TomeofIdentify && !Config.FieldID.Enabled) return true;
- if (ignoreTypes.indexOf(item.itemType) === -1 && item.sellable && !Town.systemsKeep(item)) {
- return true;
- }
- return false;
- });
-
- let sell = [];
- const classItemType = (item) => [
- sdk.items.type.Wand, sdk.items.type.VoodooHeads, sdk.items.type.AuricShields, sdk.items.type.PrimalHelm, sdk.items.type.Pelt
- ].includes(item.itemType);
-
- items.length > 0 && items.forEach(function (item) {
- let result = Pickit.checkItem(item).result;
-
- if ([Pickit.Result.UNWANTED, Pickit.Result.TRASH].indexOf(result) === -1) {
- if ((item.isBaseType && item.sockets > 0) || (classItemType(item) && item.normal && item.sockets === 0)) {
- if (!Item.betterThanStashed(item) && !Item.betterBaseThanWearing(item, Developer.debugging.baseCheck)) {
- result = 4;
- }
- }
- }
-
- !item.identified && (result = -1);
- [Pickit.Result.UNWANTED, Pickit.Result.TRASH].includes(result) && sell.push(item);
- });
-
- sell = (sell.length > 0 ? sell.concat(sellOrDrop) : sellOrDrop.slice(0));
- sell.length > 0 && this.initNPC("Shop", "clearInventory") && sell.forEach(function (item) {
- try {
- if (getUIFlag(sdk.uiflags.Shop) || getUIFlag(sdk.uiflags.NPCMenu)) {
- console.log("clearInventory sell " + item.name);
- Misc.itemLogger("Sold", item);
- item.sell();
- delay(100);
- }
- } catch (e) {
- console.error(e);
- }
- });
-
- console.log("ÿc8Exit clearInventory ÿc0- ÿc7Duration: ÿc0" + Time.format(getTickCount() - clearInvoTick));
-
- return true;
-};
+Town.lastChores = 0;
-Town.clearJunk = function () {
- let junkItems = me.getItemsEx()
- .filter(i => i.isInStorage && !Town.ignoredItemTypes.includes(i.itemType) && i.sellable && !Town.systemsKeep(i));
- if (!junkItems.length) return false;
-
- let [totalJunk, junkToSell, junkToDrop] = [[], [], []];
- const getToItem = (str = "", item = null) => {
- if (!getUIFlag(sdk.uiflags.Stash) && item.isInStash && !Town.openStash()) {
- throw new Error("ÿc9" + str + "ÿc0 :: Failed to get " + item.name + " from stash");
- }
- if (item.isInCube && !Cubing.emptyCube()) throw new Error("ÿc9" + str + "ÿc0 :: Failed to remove " + item.name + " from cube");
- return true;
- };
-
- while (junkItems.length > 0) {
- const junk = junkItems.shift();
- const pickitResult = Pickit.checkItem(junk).result;
-
- try {
- if ([Pickit.Result.UNWANTED, Pickit.Result.TRASH].includes(pickitResult)) {
- console.log("ÿc9JunkCheckÿc0 :: Junk: " + junk.name + " Pickit Result: " + pickitResult);
- getToItem("JunkCheck", junk) && totalJunk.push(junk);
-
- continue;
- }
-
- if (pickitResult !== Pickit.Result.WANTED) {
- if (!junk.identified && !Cubing.keepItem(junk) && !CraftingSystem.keepItem(junk) && junk.quality < sdk.items.quality.Set) {
- console.log("ÿc9UnidJunkCheckÿc0 :: Junk: " + junk.name + " Junk type: " + junk.itemType + " Pickit Result: " + pickitResult);
- getToItem("UnidJunkCheck", junk) && totalJunk.push(junk);
-
- continue;
- }
- }
-
- if (junk.isRuneword && !AutoEquip.wanted(junk)) {
- console.log("ÿc9AutoEquipJunkCheckÿc0 :: Junk: " + junk.name + " Junk type: " + junk.itemType + " Pickit Result: " + pickitResult);
- getToItem("AutoEquipJunkCheck", junk) && totalJunk.push(junk);
-
- continue;
- }
-
- if (junk.isBaseType && pickitResult === Pickit.Result.SOLOWANTS) {
- if (!Item.betterThanStashed(junk)) {
- console.log("ÿc9BetterThanStashedCheckÿc0 :: Base: " + junk.name + " Junk type: " + junk.itemType + " Pickit Result: " + pickitResult);
- getToItem("BetterThanStashedCheck", junk) && totalJunk.push(junk);
-
- continue;
- }
-
- if (!Item.betterBaseThanWearing(junk, Developer.debugging.baseCheck)) {
- console.log("ÿc9BetterThanWearingCheckÿc0 :: Base: " + junk.name + " Junk type: " + junk.itemType + " Pickit Result: " + pickitResult);
- getToItem("BetterThanWearingCheck", junk) && totalJunk.push(junk);
-
- continue;
- }
- }
- } catch (e) {
- console.warn(e.message ? e.message : e);
- }
- }
-
- if (totalJunk.length > 0) {
- totalJunk
- .sort((a, b) => b.getItemCost(sdk.items.cost.ToSell) - a.getItemCost(sdk.items.cost.ToSell))
- .forEach(junk => {
- // extra check should ensure no pickit wanted items get sold/dropped
- if (NTIP.CheckItem(junk, NTIP_CheckListNoTier) === Pickit.Result.WANTED) return;
- if (junk.isInInventory || (Storage.Inventory.CanFit(junk) && Storage.Inventory.MoveTo(junk))) {
- junkToSell.push(junk);
- } else {
- junkToDrop.push(junk);
- }
- });
-
- myPrint("Junk items to sell: " + junkToSell.length);
- Town.initNPC("Shop", "clearInventory");
-
- if (getUIFlag(sdk.uiflags.Shop) || (Config.PacketShopping && getInteractedNPC() && getInteractedNPC().itemcount > 0)) {
- for (let i = 0; i < junkToSell.length; i++) {
- console.log("ÿc9JunkCheckÿc0 :: Sell " + junkToSell[i].name);
- Misc.itemLogger("Sold", junkToSell[i]);
- Developer.debugging.junkCheck && Misc.logItem("JunkCheck Sold", junkToSell[i]);
-
- junkToSell[i].sell();
- delay(100);
- }
- }
-
- me.cancelUIFlags();
-
- for (let i = 0; i < junkToDrop.length; i++) {
- console.log("ÿc9JunkCheckÿc0 :: Drop " + junkToDrop[i].name);
- Misc.itemLogger("Sold", junkToDrop[i]);
- Developer.debugging.junkCheck && Misc.logItem("JunkCheck Sold", junkToDrop[i]);
-
- junkToDrop[i].drop();
- delay(100);
- }
- }
-
- return true;
+Town.fillTomes = function () {
+ NPCAction.fillTome(sdk.items.TomeofTownPortal);
+ Config.FieldID.Enabled && NPCAction.fillTome(sdk.items.TomeofIdentify);
+ !!me.getItem(sdk.items.TomeofTownPortal) && this.clearScrolls();
};
+/**
+ * @override
+ * @param {boolean} repair
+ * @param {extraTasks} givenTasks
+ * @returns {boolean}
+ */
Town.doChores = function (repair = false, givenTasks = {}) {
- const extraTasks = Object.assign({}, {
- thawing: false,
- antidote: false,
- stamina: false,
- fullChores: false,
- }, givenTasks);
-
- delay(250);
-
- console.info(true);
- console.time("doChores");
-
- !me.inTown && Town.goToTown();
-
- // 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);
- }
-
- const preAct = me.act;
-
- me.switchWeapons(Attack.getPrimarySlot());
- extraTasks.fullChores && Quest.unfinishedQuests();
- me.getUnids().length && me.gold < 5000 && this.cainID(true);
- this.heal();
- this.identify();
- this.clearInventory();
- this.fillTome(sdk.items.TomeofTownPortal);
- Config.FieldID.Enabled && this.fillTome(sdk.items.TomeofIdentify);
- !!me.getItem(sdk.items.TomeofTownPortal) && this.clearScrolls();
- this.buyPotions();
- this.buyKeys();
- extraTasks.thawing && CharData.buffData.thawing.need() && Town.buyPots(12, "Thawing", true);
- extraTasks.antidote && CharData.buffData.antidote.need() && Town.buyPots(12, "Antidote", true);
- extraTasks.stamina && Town.buyPots(12, "Stamina", true);
- this.shopItems();
- this.repair(repair) && this.shopItems(true);
- this.reviveMerc();
- this.gamble();
- Cubing.emptyCube();
- Runewords.makeRunewords();
- Cubing.doCubing();
- Runewords.makeRunewords();
- AutoEquip.runAutoEquip();
- Mercenary.hireMerc();
- Item.autoEquipMerc();
- Town.haveItemsToSell() && Town.sellItems() && me.cancelUIFlags();
- this.clearJunk();
- this.stash();
- // check pots again, we might have enough gold now if we didn't before
- me.needPotions() && this.buyPotions() && me.cancelUIFlags();
- // check repair again, we might have enough gold now if we didn't before
- me.needRepair() && this.repair() && me.cancelUIFlags();
-
- this.sortInventory();
- extraTasks.fullChores && this.sortStash();
- Quest.characterRespec();
-
- me.act !== preAct && this.goToTown(preAct);
- me.cancelUIFlags();
- !me.barbarian && !Precast.checkCTA() && Precast.doPrecast(false);
-
- if (me.expansion) {
- Attack.checkBowOnSwitch();
- Attack.getCurrentChargedSkillIds();
- Pather.checkForTeleCharges();
- }
-
- delay(300);
- console.info(false, null, "doChores");
- Town.lastInteractedNPC.reset(); // unassign
-
- return true;
+ const extraTasks = Object.assign({}, {
+ thawing: false,
+ antidote: false,
+ stamina: false,
+ fullChores: false,
+ }, givenTasks);
+
+ delay(250);
+
+ console.info(true);
+ console.time("doChores");
+ let _startGold = me.gold;
+ console.debug("doChores Inital Gold :: " + _startGold);
+
+ !me.inTown && Town.goToTown();
+
+ // 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);
+ }
+
+ const preAct = me.act;
+
+ /**
+ * @todo light chores if last chores was < minute? 2 minutes idk yet
+ */
+
+ me.switchToPrimary();
+ extraTasks.fullChores && Quest.unfinishedQuests();
+
+ // Use cainId if we are low on gold or we are closer to him than the shopNPC
+ if (me.getUnids().length) {
+ // use our id tome if we have it first
+ if (!me.fieldID()) {
+ if (me.gold < 5000
+ || Town.getDistance(NPC.Cain) < Town.getDistance(Town.tasks.get(me.act).Shop)) {
+ NPCAction.cainID(true);
+ }
+ }
+ }
+
+ // maybe a check if need healing first, as we might have just used a potion
+ Town.heal();
+ Town.identify();
+ Town.clearInventory();
+ Town.fillTomes();
+ NPCAction.buyPotions();
+ Town.buyKeys();
+ extraTasks.thawing && CharData.pots.get("thawing").need() && Town.buyPots(12, "Thawing", true);
+ extraTasks.antidote && CharData.pots.get("antidote").need() && Town.buyPots(12, "Antidote", true);
+ extraTasks.stamina && Town.buyPots(12, "Stamina", true);
+ NPCAction.shopItems();
+ NPCAction.repair(repair);
+ NPCAction.reviveMerc();
+ NPCAction.gamble();
+
+ // if (me.inArea(sdk.areas.LutGholein) && me.normal && me.gold > 10000) {
+ // // shop at Elzix - what about others?
+ // NPCAction.shopAt(NPC.Elzix);
+ // }
+ Cubing.emptyCube();
+ Runewords.makeRunewords();
+ Cubing.doCubing();
+ Runewords.makeRunewords();
+ AutoEquip.run();
+ Mercenary.hireMerc();
+ Item.autoEquipMerc();
+ Town.haveItemsToSell() && Town.sellItems() && me.cancelUIFlags();
+ Town.clearJunk();
+ Town.stash();
+
+ // check pots again, we might have enough gold now if we didn't before
+ me.needPotions() && NPCAction.buyPotions() && me.cancelUIFlags();
+ // check repair again, we might have enough gold now if we didn't before
+ me.needRepair().length && NPCAction.repair() && me.cancelUIFlags();
+
+ me.sortInventory();
+ Quest.characterRespec();
+
+ me.act !== preAct && Town.goToTown(preAct);
+ me.cancelUIFlags();
+ !me.barbarian && !Precast.checkCTA() && Precast.doPrecast(false);
+
+ if (me.expansion) {
+ Attack.checkBowOnSwitch();
+ Attack.getCurrentChargedSkillIds();
+ Pather.checkForTeleCharges();
+ }
+
+ delay(300);
+ console.debug(
+ "doChores Ending Gold :: " + me.gold
+ + " ÿc8(ÿc7" + (me.gold - _startGold) + "ÿc8)"
+ );
+ console.info(false, null, "doChores");
+ Town.lastChores = getTickCount();
+ wantedTasks.clear();
+
+ return true;
};
diff --git a/libs/SoloPlay/Modules/AreaData.js b/libs/SoloPlay/Modules/AreaData.js
deleted file mode 100644
index 2b947505..00000000
--- a/libs/SoloPlay/Modules/AreaData.js
+++ /dev/null
@@ -1,205 +0,0 @@
-/**
- * @module
- * @param {Object} module
- * @param {function} require
- */
-(function (module, require) {
- const MonsterData = require("./MonsterData");
- const SUPER = [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 1, 4, 0, 2, 3, 1, 0, 1, 1, 0, 0, 0, 1, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5, 1, 1, 1, 1, 3];
- const AREA_LOCALE_STRING = [5389, 5055, 5054, 5053, 5052, 5051, 5050, 5049, 5048, 5047, 5046, 5045, 5044, 5043, 5042, 5041, 5040, 5039, 5038, 5037, 5036, 5035, 5034, 5033, 5032, 5031, 5030, 5029, 5028, 5027, 5026, 5025, 5024, 5023, 5022, 5021, 5020, 5019, 5018, 788, 852, 851, 850, 849, 848, 847, 846, 845, 844, 843, 842, 841, 840, 839, 838, 837, 836, 835, 834, 833, 832, 831, 830, 829, 828, 827, 826, 826, 826, 826, 826, 826, 826, 825, 824, 820, 819, 818, 817, 816, 815, 814, 813, 812, 810, 811, 809, 808, 806, 805, 807, 804, 845, 844, 803, 802, 801, 800, 799, 798, 797, 796, 795, 790, 792, 793, 794, 791, 789, 22646, 22647, 22648, 22649, 22650, 22651, 22652, 22653, 22654, 22655, 22656, 22657, 22658, 22659, 22660, 22662, 21865, 21866, 21867, 22663, 22664, 22665, 22667, 22666, 5389, 5389, 5389, 5018];
- const MONSTER_KEYS = [
- ["mon1", "mon2", "mon3", "mon4", "mon5", "mon6", "mon7", "mon8", "mon9", "mon10"],
- ["nmon1", "nmon2", "nmon3", "nmon4", "nmon5", "nmon6", "nmon7", "nmon8", "nmon9", "nmon10"],
- ][me.diff && 1]; // mon is for normal, nmon is for nm/hell, umon is specific to picking champion/uniques in normal
- const LocaleStringName = require("./LocaleStringID").LocaleStringName;
- const AREA_INDEX_COUNT = 137;
-
- /**
- * @typedef AreaDataObj
- * @type {object}
- * @property {number} Super = number of super uniques present in this area
- * @property {number} Index = areaID
- * @property {number} Act = act this area is in [0-4]
- * @property {number} MonsterDensity = value used to determine monster population density
- * @property {number} ChampionPacks.Min = minimum number of champion or unique packs that spawn here
- * @property {number} ChampionPacks.Max = maximum number of champion or unique packs that spawn here
- * @property {number} Waypoint = number in waypoint menu that leads to this area
- * @property {number} Level = level of area (use GameData.areaLevel)
- * @property {number} Size.x = width of area
- * @property {number} Size.y = depth of area
- * @property {number} Monsters = array of monsters that can spawn in this area
- * @property {number} LocaleString = locale string index for getLocaleString
- */
-
- /** @type {AreaDataObj[]} */
- const AreaData = new Array(AREA_INDEX_COUNT);
-
- for (let i = 0; i < AreaData.length; i++) {
- let index = i;
- AreaData[i] = ({
- Super: SUPER[index],
- Index: index,
- Act: getBaseStat("levels", index, "Act"),
- MonsterDensity: getBaseStat("levels", index, ["MonDen", "MonDen(N)", "MonDen(H)"][me.diff]),
- ChampionPacks: ({
- Min: getBaseStat("levels", index, ["MonUMin", "MonUMin(N)", "MonUMin(H)"][me.diff]),
- Max: getBaseStat("levels", index, ["MonUMax", "MonUMax(N)", "MonUMax(H)"][me.diff])
- }),
- Waypoint: getBaseStat("levels", index, "Waypoint"),
- Level: getBaseStat("levels", index, ["MonLvl1Ex", "MonLvl2Ex", "MonLvl3Ex"][me.diff]),
- Size: (() => {
- if (index === 111) { // frigid highlands doesn't specify size, manual measurement
- return {x: 210, y: 710};
- }
-
- if (index === 112) { // arreat plateau doesn't specify size, manual measurement
- return {x: 690, y: 230};
- }
-
- return {
- x: getBaseStat("leveldefs", index, ["SizeX", "SizeX(N)", "SizeX(H)"][me.diff]),
- y: getBaseStat("leveldefs", index, ["SizeY", "SizeY(N)", "SizeY(H)"][me.diff])
- };
- })(),
- Monsters: (MONSTER_KEYS.map(key => getBaseStat("levels", index, key)).filter(key => key !== 65535)),
- /**
- * Check if this area has a monster of a certain type
- * @function
- * @param {number} type - monster type to check for
- * @returns {boolean}
- */
- hasMonsterType: function (type) {
- return this.Monsters.some(monId => MonsterData[monId].Type === type);
- },
- /**
- * Iterate through each monster in this area and apply a callback function
- * @function
- * @param {function} cb - callback function to apply to each monster
- */
- forEachMonster: function (cb) {
- if (typeof cb === "function") {
- this.Monsters.forEach(monID => {
- cb(MonsterData[monID], MonsterData[monID].Rarity * (MonsterData[monID].GroupCount.Min + MonsterData[monID].GroupCount.Max) / 2);
- });
- }
- },
- /**
- * Iterate through each monster and minion in this area and apply a callback function
- * @function
- * @param {function} cb - callback function to apply to each monster
- */
- forEachMonsterAndMinion: function (cb) {
- if (typeof cb === "function") {
- this.Monsters.forEach(monID => {
- let rarity = MonsterData[monID].Rarity * (MonsterData[monID].GroupCount.Min + MonsterData[monID].GroupCount.Max) / 2;
- cb(MonsterData[monID], rarity, null);
- MonsterData[monID].Minions.forEach(minionID => {
- let minionrarity = MonsterData[monID].Rarity * (MonsterData[monID].MinionCount.Min + MonsterData[monID].MinionCount.Max) / 2 / MonsterData[monID].Minions.length;
- cb(MonsterData[minionID], minionrarity, MonsterData[monID]);
- });
- });
- }
- },
- LocaleString: getLocaleString(AREA_LOCALE_STRING[index]),
- InternalName: LocaleStringName[AREA_LOCALE_STRING[index]],
- /**
- * Check if area is a town area
- * @function
- */
- townArea: function () {
- return AreaData[[sdk.areas.RogueEncampment, sdk.areas.LutGholein, sdk.areas.KurastDocktown, sdk.areas.PandemoniumFortress, sdk.areas.Harrogath][this.Act]];
- },
- /**
- * @function
- */
- haveWaypoint: function () {
- // get the last area that got a WP
- let wpArea = this.nearestWaypointArea();
-
- // If you dont need a wp, we want at least the town's wp
- return getWaypoint(Pather.wpAreas.indexOf(wpArea || this.townArea().Index));
- },
- /**
- * Find nearest waypoint in area
- * @function
- */
- nearestWaypointArea: function () {
- // plot toward this are
- const plot = Pather.plotCourse(this.Index, this.townArea().Index);
-
- // get the last area that got a WP
- return plot.course.filter(el => Pather.wpAreas.indexOf(el) > -1).last();
- },
- /**
- * @function
- * @return {PresetUnit|undefined}
- */
- waypointPreset: function () {
- const wpIDs = [119, 145, 156, 157, 237, 238, 288, 323, 324, 398, 402, 429, 494, 496, 511, 539];
- for (let i = 0, preset, wpArea = this.nearestWaypointArea(); i < wpIDs.length || preset; i++) {
- if ((preset = Game.getPresetObject(wpArea, wpIDs[i]))) {
- return preset;
- }
- }
-
- return undefined;
- },
- });
- }
-
- /**
- * @property {function} AreaData.findByName
- * @param {string} whatToFind
- * @returns
- */
- AreaData.findByName = function (whatToFind) {
- let matches = AreaData.map(area => [Math.min(whatToFind.diffCount(area.LocaleString), whatToFind.diffCount(area.InternalName)), area]).sort((a, b) => a[0] - b[0]);
-
- return matches[0][1];
- };
-
- AreaData.dungeons = {
- DenOfEvil: [sdk.areas.DenofEvil],
-
- Hole: [sdk.areas.HoleLvl1, sdk.areas.HoleLvl2, ],
-
- Pit: [sdk.areas.PitLvl1, sdk.areas.PitLvl2],
-
- Cave: [sdk.areas.CaveLvl1, sdk.areas.CaveLvl2],
-
- UndergroundPassage: [sdk.areas.UndergroundPassageLvl1, sdk.areas.UndergroundPassageLvl2, ],
-
- Cellar: [sdk.areas.TowerCellarLvl1, sdk.areas.TowerCellarLvl2, sdk.areas.TowerCellarLvl3, sdk.areas.TowerCellarLvl4, sdk.areas.TowerCellarLvl5, ],
-
- // act 2
- A2Sewers: [sdk.areas.A2SewersLvl1, sdk.areas.A2SewersLvl2, sdk.areas.A2SewersLvl3, ],
-
- StonyTomb: [sdk.areas.StonyTombLvl1, sdk.areas.StonyTombLvl2, ],
-
- HallsOfDead: [sdk.areas.HallsoftheDeadLvl1, sdk.areas.HallsoftheDeadLvl2, sdk.areas.HallsoftheDeadLvl3, ],
-
- ClawViperTemple: [sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2, ],
-
- MaggotLair: [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3, ],
-
- Tombs: [sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7, ],
-
- // act 3
- Swamp: [sdk.areas.SwampyPitLvl1, sdk.areas.SwampyPitLvl2, sdk.areas.SwampyPitLvl3, ],
-
- FlayerDungeon: [sdk.areas.FlayerDungeonLvl1, sdk.areas.FlayerDungeonLvl2, sdk.areas.FlayerDungeonLvl3, ],
-
- A3Sewers: [sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2, ],
-
- HighLevelForgottenTemples: [sdk.areas.ForgottenTemple, sdk.areas.RuinedFane, sdk.areas.DisusedReliquary],
-
- LowLevelForgottenTemples: [sdk.areas.RuinedTemple, sdk.areas.DisusedFane, sdk.areas.ForgottenReliquary],
-
- // act 4 has no areas like that
-
- // act 5
- RedPortalPits: [sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit, ],
- };
-
- module.exports = AreaData;
-})(module, require);
diff --git a/libs/SoloPlay/Modules/Clear.js b/libs/SoloPlay/Modules/Clear.js
index fe1eab03..1abbaaae 100644
--- a/libs/SoloPlay/Modules/Clear.js
+++ b/libs/SoloPlay/Modules/Clear.js
@@ -1,207 +1,206 @@
var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
+ return (mod && mod.__esModule) ? mod : { "default": mod };
};
(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", "../../modules/sdk", "./Events", "./Coords", "./MissileData"], factory);
- }
+ if (typeof module === "object" && typeof module.exports === "object") {
+ const v = factory(require, exports);
+ if (v !== undefined) module.exports = v;
+ } else if (typeof define === "function" && define.amd) {
+ define(["require", "exports", "../../modules/sdk", "./Events", "./Coords", "./MissileData"], factory);
+ }
})(function (require, exports) {
- "use strict";
- if (!isIncluded("libs/SoloPlay/Functions/PatherOverrides.js")) { include("libs/SoloPlay/Functions/PatherOverrides.js"); }
- include("SoloPlay/Functions/ClassAttackOverrides/" + sdk.player.class.nameOf(me.classid) + "Attacks.js");
- global["__________ignoreMonster"] = [];
- const sdk_1 = __importDefault(require("../../modules/sdk"));
- const Events_1 = require("./Events");
- const Coords_1 = require("./Coords");
- const MissileData_1 = __importDefault(require("./MissileData"));
- const defaults = {
- range: 14,
- spectype: 0,
- once: false,
- nodes: [],
- callback: undefined,
- filter: undefined //(monster: Monster, node: {x, y}[]) => true
- };
- const shamans = [
- sdk_1.default.monsters.FallenShaman, sdk_1.default.monsters.CarverShaman2, sdk_1.default.monsters.DevilkinShaman2, sdk_1.default.monsters.DarkShaman1,
- sdk_1.default.monsters.WarpedShaman, sdk_1.default.monsters.CarverShaman, sdk_1.default.monsters.DevilkinShaman, sdk_1.default.monsters.DarkShaman2
- ];
- const fallens = [
- sdk_1.default.monsters.Fallen, sdk_1.default.monsters.Carver2, sdk_1.default.monsters.Devilkin2, sdk_1.default.monsters.DarkOne1,
- sdk_1.default.monsters.WarpedFallen, sdk_1.default.monsters.Carver1, sdk_1.default.monsters.Devilkin, sdk_1.default.monsters.DarkOne2
- ];
- const clearDistance = function (x, y, xx, yy) {
- getUnits(sdk.unittype.Monster).forEach(function (monster) {
- if (typeof monster["beendead"] === "undefined")
- monster.beendead = false;
- monster.beendead = monster.beendead || monster.dead;
- });
- let path = getPath(me.area, x, y, xx, yy, 0, 4);
- if (!path || !path.length) return Infinity;
+ "use strict";
+ if (!isIncluded("libs/SoloPlay/Functions/PatherOverrides.js")) { include("libs/SoloPlay/Functions/PatherOverrides.js"); }
+ include("SoloPlay/Functions/ClassAttackOverrides/" + sdk.player.class.nameOf(me.classid) + "Attacks.js");
+ global["__________ignoreMonster"] = [];
+ const sdk_1 = __importDefault(require("../../modules/sdk"));
+ const Events_1 = require("./Events");
+ const Coords_1 = require("./Coords");
+ const MissileData_1 = __importDefault(require("./GameData/MissileData"));
+ const defaults = {
+ range: 14,
+ spectype: 0,
+ once: false,
+ nodes: [],
+ callback: undefined,
+ filter: undefined //(monster: Monster, node: {x, y}[]) => true
+ };
+ const shamans = [
+ sdk_1.default.monsters.FallenShaman, sdk_1.default.monsters.CarverShaman2, sdk_1.default.monsters.DevilkinShaman2, sdk_1.default.monsters.DarkShaman1,
+ sdk_1.default.monsters.WarpedShaman, sdk_1.default.monsters.CarverShaman, sdk_1.default.monsters.DevilkinShaman, sdk_1.default.monsters.DarkShaman2
+ ];
+ const fallens = [
+ sdk_1.default.monsters.Fallen, sdk_1.default.monsters.Carver2, sdk_1.default.monsters.Devilkin2, sdk_1.default.monsters.DarkOne1,
+ sdk_1.default.monsters.WarpedFallen, sdk_1.default.monsters.Carver1, sdk_1.default.monsters.Devilkin, sdk_1.default.monsters.DarkOne2
+ ];
+ const clearDistance = function (x, y, xx, yy) {
+ getUnits(sdk.unittype.Monster).forEach(function (monster) {
+ if (typeof monster["beendead"] === "undefined")
+ monster.beendead = false;
+ monster.beendead = monster.beendead || monster.dead;
+ });
+ let path = getPath(me.area, x, y, xx, yy, 0, 4);
+ 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);
- };
- var exporting = (function (_settings) {
- if (_settings === void 0) { _settings = {}; }
- let settings = Object.assign({}, defaults, _settings);
- // The bigger
- let smallStepRange = settings.range / 3 * 2;
- // Get an array with arrays going away from you (what we gonna walk after clearing, within range)
- let nearestNode = settings.nodes[settings.nodes.index];
+ 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);
+ };
+ var exporting = (function (_settings) {
+ if (_settings === void 0) { _settings = {}; }
+ let settings = Object.assign({}, defaults, _settings);
+ // The bigger
+ let smallStepRange = settings.range / 3 * 2;
+ // Get an array with arrays going away from you (what we gonna walk after clearing, within range)
+ let nearestNode = settings.nodes[settings.nodes.index];
- const backTrack = function (units, missiles) {
- if (settings.nodes.index < 2) return false;
- //ToDo; backtrack further if that is a safer bet
- let nodesBack = Math.min(settings.nodes.index, 5);
- me.overhead("backtracking " + nodesBack + " nodes");
- settings.nodes.index -= nodesBack;
- nearestNode = settings.nodes[settings.nodes.index];
- // stationary missiles that deal damages
- let enhancedMissiles = missiles.map(function (m) { return ({ missile: m, data: MissileData_1.default[m.classid] }); });
- let missilesOnFloor = enhancedMissiles
- .filter(function (m) { return !!m.data; })
- .filter(function (m) { return m.data.velocity === 0 && (m.data.minDamage > 0 || m.data.eMin > 0) && m.missile.hits(nearestNode); });
- while (missilesOnFloor.length > 0 && settings.nodes.index > 0) {
- console.log("missilesOnFloor");
- console.log(missilesOnFloor);
- nodesBack += 1;
- settings.nodes.index -= 1;
- nearestNode = settings.nodes[settings.nodes.index];
- missilesOnFloor = enhancedMissiles.filter(function (m) { return !!m.data; })
- .filter(function (m) { return m.data.velocity === 0 && (m.data.minDamage > 0 || m.data.eMin > 0) && m.missile.hits(nearestNode); });
- }
+ const backTrack = function (units, missiles) {
+ if (settings.nodes.index < 2) return false;
+ //ToDo; backtrack further if that is a safer bet
+ let nodesBack = Math.min(settings.nodes.index, 5);
+ me.overhead("backtracking " + nodesBack + " nodes");
+ settings.nodes.index -= nodesBack;
+ nearestNode = settings.nodes[settings.nodes.index];
+ // stationary missiles that deal damages
+ let enhancedMissiles = missiles.map(function (m) { return ({ missile: m, data: MissileData_1.default[m.classid] }); });
+ let missilesOnFloor = enhancedMissiles
+ .filter(function (m) { return !!m.data; })
+ .filter(function (m) { return m.data.velocity === 0 && (m.data.minDamage > 0 || m.data.eMin > 0) && m.missile.hits(nearestNode); });
+ while (missilesOnFloor.length > 0 && settings.nodes.index > 0) {
+ console.log("missilesOnFloor");
+ console.log(missilesOnFloor);
+ nodesBack += 1;
+ settings.nodes.index -= 1;
+ nearestNode = settings.nodes[settings.nodes.index];
+ missilesOnFloor = enhancedMissiles.filter(function (m) { return !!m.data; })
+ .filter(function (m) { return m.data.velocity === 0 && (m.data.minDamage > 0 || m.data.eMin > 0) && m.missile.hits(nearestNode); });
+ }
- let old = Pather.forceRun;
- Pather.forceRun = true;
- try {
- let x = nearestNode.x, y = nearestNode.y;
- // If the path between me and the node we wanna run back to is blocked dont do it
- if (CollMap.checkColl(me, {x: x, y: y}, Coords_1.Collision.BLOCK_MISSILE, 3)) {
- me.overhead("Before backtracking, clear near me");
- let unit = units.first();
- unit && ClassAttack.doAttack(unit);
- settings.nodes.index += nodesBack;
- return true;
- }
+ let old = Pather.forceRun;
+ Pather.forceRun = true;
+ try {
+ let x = nearestNode.x, y = nearestNode.y;
+ // If the path between me and the node we wanna run back to is blocked dont do it
+ if (CollMap.checkColl(me, {x: x, y: y}, Coords_1.Collision.BLOCK_MISSILE, 3)) {
+ me.overhead("Before backtracking, clear near me");
+ let unit = units.first();
+ unit && ClassAttack.doAttack(unit);
+ settings.nodes.index += nodesBack;
+ return true;
+ }
- me.overhead("backtracking " + nodesBack + " nodes");
- if (Pather.getWalkDistance(x, y) > getDistance(me, nearestNode) * 1.5) {
- if (Pather.canTeleport() && getDistance(me, nearestNode) < 35) {
- Pather.teleportTo(x, y);
- } else {
- Pather.moveToOverride(x, y);
- }
- } else {
- Pather.walkTo(x, y);
- }
- start = [x, y];
- } finally {
- Pather.forceRun = old;
- }
- return true;
- };
+ me.overhead("backtracking " + nodesBack + " nodes");
+ if (Pather.getWalkDistance(x, y) > getDistance(me, nearestNode) * 1.5) {
+ if (Pather.canTeleport() && getDistance(me, nearestNode) < 35) {
+ Pather.teleportTo(x, y);
+ } else {
+ Pather.moveToOverride(x, y);
+ }
+ } else {
+ Pather.walkTo(x, y);
+ }
+ start = [x, y];
+ } finally {
+ Pather.forceRun = old;
+ }
+ return true;
+ };
- let start = [], startArea = me.area, cachedNodes = undefined;
- const getUnits_filtered = function () {
- let monsters = getUnits(1, -1)
- .filter(function (m) { return m.area === me.area && m.attackable && !global["__________ignoreMonster"].includes(m.gid); })
- .filter(function (unit) { return ( // Shamaans have a higher range
- (function (range) {
- return start.length // If start has a length
- ? getDistance(start[0], start[1], unit) < range // If it has a range smaller as from the start point (when using me.clear)
- : unit.distance < range;
- } // if "me" move, the object doesnt move. So, check distance of object
- )(shamans.includes(unit.classid) ? settings.range * 1.6 : settings.range)
+ let start = [], startArea = me.area, cachedNodes = undefined;
+ const getUnits_filtered = function () {
+ let monsters = getUnits(1, -1)
+ .filter(function (m) { return m.area === me.area && m.attackable && !global["__________ignoreMonster"].includes(m.gid); })
+ .filter(function (unit) { return ( // Shamaans have a higher range
+ (function (range) {
+ return start.length // If start has a length
+ ? getDistance(start[0], start[1], unit) < range // If it has a range smaller as from the start point (when using me.clear)
+ : unit.distance < range;
+ } // if "me" move, the object doesnt move. So, check distance of object
+ )(shamans.includes(unit.classid) ? settings.range * 1.6 : settings.range)
// clear monsters on the path
|| (( /* cache the nodes*/cachedNodes = cachedNodes || settings.nodes
- .slice(settings.nodes.index, settings.nodes.index + 5)
- .filter(function (el) { return el.distance < 30; }))
- .some(function (node) { return getDistance(unit, node.x, node.y) < smallStepRange; })))
+ .slice(settings.nodes.index, settings.nodes.index + 5)
+ .filter(function (el) { return el.distance < 30; }))
+ .some(function (node) { return getDistance(unit, node.x, node.y) < smallStepRange; })))
&& !CollMap.checkColl(me, unit, Coords_1.Collision.BLOCK_MISSILE, 5); })
- .filter(function (unit) {
- if (!settings.spectype || typeof settings.spectype !== "number")
- return true; // No spectype = all monsters
- // noinspection JSBitwiseOperatorUsage
- return unit.spectype & settings.spectype;
- });
- if (settings.filter) {
- monsters = monsters.filter(settings.filter);
- }
- // too much monsters, quick sort
- if (monsters.length > 7) {
- return monsters.sort(function (a, b) { return a.distance - b.distance; });
- }
- return monsters.sort(function (a, b) {
- // shamans are a mess early game
- var isShamanA = shamans.includes(a.classid);
- var isFallenB = fallens.includes(b.classid);
- if (isShamanA && isFallenB && !checkCollision(me, a, 0x7) /*line of sight*/) {
- // return shaman first, if we have a direct line of sight
- return -1;
- }
- if (typeof a["beendead"] !== "undefined" && typeof b["beendead"] === "undefined" && a["beendead"] && !b["beendead"]) {
- return 1; // those that been dead before (aka fallens) will be moved up from the list, so we are more likely to pwn shamans on a safe moment
- }
- return clearDistance(me.x, me.y, a.x, a.y) - (clearDistance(me.x, me.y, b.x, b.y));
- });
- };
- // If we clear around _me_ we move around, but just clear around where we started
- var units;
- if (me === this) {
- start = [me.x, me.y];
- }
+ .filter(function (unit) {
+ if (!settings.spectype || typeof settings.spectype !== "number")
+ return true; // No spectype = all monsters
+ // noinspection JSBitwiseOperatorUsage
+ return unit.spectype & settings.spectype;
+ });
+ if (settings.filter) {
+ monsters = monsters.filter(settings.filter);
+ }
+ // too much monsters, quick sort
+ if (monsters.length > 7) {
+ return monsters.sort(function (a, b) { return a.distance - b.distance; });
+ }
+ return monsters.sort(function (a, b) {
+ // shamans are a mess early game
+ var isShamanA = shamans.includes(a.classid);
+ var isFallenB = fallens.includes(b.classid);
+ if (isShamanA && isFallenB && !checkCollision(me, a, 0x7) /*line of sight*/) {
+ // return shaman first, if we have a direct line of sight
+ return -1;
+ }
+ if (typeof a["beendead"] !== "undefined" && typeof b["beendead"] === "undefined" && a["beendead"] && !b["beendead"]) {
+ return 1; // those that been dead before (aka fallens) will be moved up from the list, so we are more likely to pwn shamans on a safe moment
+ }
+ return clearDistance(me.x, me.y, a.x, a.y) - (clearDistance(me.x, me.y, b.x, b.y));
+ });
+ };
+ // If we clear around _me_ we move around, but just clear around where we started
+ var units;
+ if (me === this) {
+ start = [me.x, me.y];
+ }
- var backtracked = false;
- while ((units = getUnits_filtered()).length) {
- exporting.emit("sorting", units);
- // sorting algorithm can also take out monsters
- if (!units.length) {
- break;
- }
- // near monsters we can handle kinda depends on our health.
- var maxNearMonsters = Math.floor((4 * (1 / me.hpmax * me.hp)) + 1);
- if (!backtracked) {
- var nearUnits = units.filter(function (unit) { return unit.attackable && unit.distance < 10; });
- var nearMissiles = getUnits(sdk_1.default.unittype.Missile)
- .filter(function (unit) { var _a, _b, _c; return unit.distance < 10 && ((_a = unit.getParent()) === null || _a === void 0 ? void 0 : _a.gid) !== me.gid && ((_b = unit.getParent()) === null || _b === void 0 ? void 0 : _b.gid) !== ((_c = me.getMerc()) === null || _c === void 0 ? void 0 : _c.gid); })
- .filter(function (m) { return MissileData_1.default[m.classid] && (MissileData_1.default[m.classid].velocity > 0 || m.hits(me)) && (MissileData_1.default[m.classid].minDamage > 0 || MissileData_1.default[m.classid].eMin > 0); });
- me.overhead("backtrack counter (" + (nearUnits.length + nearMissiles.length) + "/" + maxNearMonsters + ")");
- if ((nearUnits.length + nearMissiles.length) >= maxNearMonsters && ((me.mp / me.mpmax) > 0.2 || me.getItemsEx().filter(function (i) { return (i.isInBelt || i.isInInventory) && i.itemType === sdk_1.default.itemtype.manapotion; }).length > 0)) {
- me.overhead("Want to backtrack");
- if (backTrack(units, nearMissiles)) {
- backtracked = true;
- continue; // we maybe wanna attack someone else now
- }
- }
- }
- backtracked = false;
- var unit = units.shift();
- if (settings.callback && settings.callback()) {
- return;
- }
- // Do something with the effort to not kill monsters that are too harsh
- var result = ClassAttack.doAttack(unit);
- if (typeof unit.casts === "undefined") {
- unit.casts = 0;
- }
- // cant attack this monsters, skip it
- if (result === 2 || unit.casts++ > 30) {
- console.log("Skip this monster");
- global["__________ignoreMonster"].push(unit.gid);
- }
- if (settings.once || startArea !== me.area) return true;
- Pickit.pickItems(3, true);
- }
+ var backtracked = false;
+ while ((units = getUnits_filtered()).length) {
+ exporting.emit("sorting", units);
+ // sorting algorithm can also take out monsters
+ if (!units.length) {
+ break;
+ }
+ // near monsters we can handle kinda depends on our health.
+ var maxNearMonsters = Math.floor((4 * (1 / me.hpmax * me.hp)) + 1);
+ if (!backtracked) {
+ var nearUnits = units.filter(function (unit) { return unit.attackable && unit.distance < 10; });
+ var nearMissiles = getUnits(sdk_1.default.unittype.Missile)
+ .filter(function (unit) { var _a, _b, _c; return unit.distance < 10 && ((_a = unit.getParent()) === null || _a === void 0 ? void 0 : _a.gid) !== me.gid && ((_b = unit.getParent()) === null || _b === void 0 ? void 0 : _b.gid) !== ((_c = me.getMerc()) === null || _c === void 0 ? void 0 : _c.gid); })
+ .filter(function (m) { return MissileData_1.default[m.classid] && (MissileData_1.default[m.classid].velocity > 0 || m.hits(me)) && (MissileData_1.default[m.classid].minDamage > 0 || MissileData_1.default[m.classid].eMin > 0); });
+ me.overhead("backtrack counter (" + (nearUnits.length + nearMissiles.length) + "/" + maxNearMonsters + ")");
+ if ((nearUnits.length + nearMissiles.length) >= maxNearMonsters && ((me.mp / me.mpmax) > 0.2 || me.getItemsEx().filter(function (i) { return (i.isInBelt || i.isInInventory) && i.itemType === sdk_1.default.itemtype.manapotion; }).length > 0)) {
+ me.overhead("Want to backtrack");
+ if (backTrack(units, nearMissiles)) {
+ backtracked = true;
+ continue; // we maybe wanna attack someone else now
+ }
+ }
+ }
+ backtracked = false;
+ var unit = units.shift();
+ if (settings.callback && settings.callback()) {
+ return;
+ }
+ // Do something with the effort to not kill monsters that are too harsh
+ var result = ClassAttack.doAttack(unit);
+ if (typeof unit.casts === "undefined") {
+ unit.casts = 0;
+ }
+ // cant attack this monsters, skip it
+ if (result === 2 || unit.casts++ > 30) {
+ console.log("Skip this monster");
+ global["__________ignoreMonster"].push(unit.gid);
+ }
+ if (settings.once || startArea !== me.area) return true;
+ Pickit.pickItems(3, true);
+ }
- return true;
- }).bind(me);
- Object.keys(Events_1.Events.prototype).forEach(function (key) { return exporting[key] = Events_1.Events.prototype[key]; });
- return exporting;
+ return true;
+ }).bind(me);
+ Object.keys(Events_1.Events.prototype).forEach(function (key) { return exporting[key] = Events_1.Events.prototype[key]; });
+ return exporting;
});
diff --git a/libs/SoloPlay/Modules/Coords.js b/libs/SoloPlay/Modules/Coords.js
index 5da81c74..112f8e1c 100644
--- a/libs/SoloPlay/Modules/Coords.js
+++ b/libs/SoloPlay/Modules/Coords.js
@@ -1,214 +1,204 @@
-var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
-};
+/* eslint-disable dot-notation */
(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", "../../modules/sdk"], 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", "../../modules/sdk"], factory);
+ }
})(function (require, exports) {
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.getSpotsFor = exports.findCastingSpotRange = exports.findCastingSpotSkill = exports.isBlockedBetween = exports.getCollisionBetweenCoords = exports.convertToCoordArray = exports.getCoordsBetween = exports.Collision = exports.BlockBits = void 0;
- var sdk_1 = require("../../modules/sdk");
- var BlockBits;
- (function (BlockBits) {
- BlockBits[BlockBits["BlockWall"] = 1] = "BlockWall";
- // Simply put, if the monster should be drawn
- BlockBits[BlockBits["LineOfSight"] = 2] = "LineOfSight";
- // Its a bit weird but it seems if this is set if you go out of range to hit a monster
- BlockBits[BlockBits["Ranged"] = 4] = "Ranged";
- // This naming comes from d2bs, but not sure if its accurate
- BlockBits[BlockBits["PlayerToWalk"] = 8] = "PlayerToWalk";
- // This is some light setting, not usefull. Its mostly around doors and waypoints. NOTE: Also set in dungeon areas when monster is in another room
- BlockBits[BlockBits["DarkArea"] = 16] = "DarkArea";
- // Is it a cast blocker? Like a stone or whatever. Not 100% accurate
- BlockBits[BlockBits["Casting"] = 32] = "Casting";
- // Tell me if you see it!
- BlockBits[BlockBits["Unknown_NeverSeen"] = 64] = "Unknown_NeverSeen";
- // These are always set if you check collision between you and a monster
- BlockBits[BlockBits["Players"] = 128] = "Players";
- BlockBits[BlockBits["Monsters"] = 256] = "Monsters";
- BlockBits[BlockBits["Items"] = 512] = "Items";
- BlockBits[BlockBits["Objects"] = 1024] = "Objects";
- // Between me / spot is a door that is closed
- BlockBits[BlockBits["ClosedDoor"] = 2048] = "ClosedDoor";
- // This one is odd, its nearly always set. But not for monsters that fly over lava for example
- // Blizzard / meteor and prob other skills are only castable on spots with this set
- BlockBits[BlockBits["IsOnFloor"] = 4096] = "IsOnFloor";
- // Flavie, merc.
- BlockBits[BlockBits["FriendlyNPC"] = 8192] = "FriendlyNPC";
- BlockBits[BlockBits["Unknown_3"] = 16384] = "Unknown_3";
- BlockBits[BlockBits["DeadBodies"] = 32768] = "DeadBodies";
- })(BlockBits = exports.BlockBits || (exports.BlockBits = {}));
- var Collision;
- (function (Collision) {
- // Collisions that cause a missile to burst
- Collision[Collision["BLOCK_MISSILE"] = 2062] = "BLOCK_MISSILE";
- })(Collision = exports.Collision || (exports.Collision = {}));
- function getCoordsBetween(x1, y1, x2, y2) {
- var abs = Math.abs, min = Math.min, max = Math.max, floor = Math.floor;
- var A = { x: x1, y: y1 };
- var B = { x: x2, y: y2 };
- if (max(x1, x2) - min(x1, x2) < max(y1, y2) - min(y1, y2)) {
- // noinspection JSSuspiciousNameCombination
- return getCoordsBetween(y1, x1, y2, x2).map(function (_a) {
- var x = _a.x, y = _a.y;
- return ({ x: y, y: x });
- });
- }
- function slope(a, b) {
- if (a.x === b.x)
- return null;
- return (b.y - a.y) / (b.x - a.x);
- }
- function intercept(point, slope) {
- // vertical line
- if (slope === null)
- return point.x;
- return point.y - slope * point.x;
- }
- var m = slope(A, B);
- var b = intercept(A, m);
- var coordinates = [];
- for (var x = min(A.x, B.x); x <= max(A.x, B.x); x++) {
- var y = m * x + b;
- coordinates.push({ x: x, y: y });
- }
- return coordinates.map(function (_a) {
- var x = _a.x, y = _a.y;
- return ({ x: floor(x), y: floor(y) });
- })
- .filter(function (el, idx, self) { return self.findIndex(function (other) { return other.x === el.x && other.y === el.y; }) === idx; });
- }
- exports.getCoordsBetween = getCoordsBetween;
- var convertToCoordArray = function (args, caller, length) {
- if (length === void 0) { length = 2; }
- var coords = [];
- for (var i = 0; i < args.length; i++) {
- if (typeof args[i] === 'number' && i < args.length - 1) {
- coords.push({ x: args[i], y: args[++i] });
- }
- else {
- coords.push(args[i]);
- }
- }
- if (coords.length !== length)
- throw TypeError('Didnt give proper arguments to ' + caller);
- return coords;
- };
- exports.convertToCoordArray = convertToCoordArray;
- function getCollisionBetweenCoords() {
- var args = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- args[_i] = arguments[_i];
- }
- var _a = exports.convertToCoordArray(args, 'getCollisionBetweenCoords', 2), one = _a[0], two = _a[1];
- if (getDistance(one, two) > 50) {
- return -1;
- }
- try {
- return getCoordsBetween(one.x, one.y, two.x, two.y)
- .reduce(function (acc, cur) {
- return (acc | 0)
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.getSpotsFor = exports.findCastingSpotRange = exports.findCastingSpotSkill = exports.isBlockedBetween = exports.getCollisionBetweenCoords = exports.convertToCoordArray = exports.getCoordsBetween = exports.Collision = exports.BlockBits = void 0;
+ const sdk_1 = require("../../modules/sdk");
+ var BlockBits;
+ (function (BlockBits) {
+ BlockBits[BlockBits["BlockWall"] = 1] = "BlockWall";
+ // Simply put, if the monster should be drawn
+ BlockBits[BlockBits["LineOfSight"] = 2] = "LineOfSight";
+ // Its a bit weird but it seems if this is set if you go out of range to hit a monster
+ BlockBits[BlockBits["Ranged"] = 4] = "Ranged";
+ // This naming comes from d2bs, but not sure if its accurate
+ BlockBits[BlockBits["PlayerToWalk"] = 8] = "PlayerToWalk";
+ // This is some light setting, not usefull. Its mostly around doors and waypoints. NOTE: Also set in dungeon areas when monster is in another room
+ BlockBits[BlockBits["DarkArea"] = 16] = "DarkArea";
+ // Is it a cast blocker? Like a stone or whatever. Not 100% accurate
+ BlockBits[BlockBits["Casting"] = 32] = "Casting";
+ // Tell me if you see it!
+ BlockBits[BlockBits["Unknown_NeverSeen"] = 64] = "Unknown_NeverSeen";
+ // These are always set if you check collision between you and a monster
+ BlockBits[BlockBits["Players"] = 128] = "Players";
+ BlockBits[BlockBits["Monsters"] = 256] = "Monsters";
+ BlockBits[BlockBits["Items"] = 512] = "Items";
+ BlockBits[BlockBits["Objects"] = 1024] = "Objects";
+ // Between me / spot is a door that is closed
+ BlockBits[BlockBits["ClosedDoor"] = 2048] = "ClosedDoor";
+ // This one is odd, its nearly always set. But not for monsters that fly over lava for example
+ // Blizzard / meteor and prob other skills are only castable on spots with this set
+ BlockBits[BlockBits["IsOnFloor"] = 4096] = "IsOnFloor";
+ // Flavie, merc.
+ BlockBits[BlockBits["FriendlyNPC"] = 8192] = "FriendlyNPC";
+ BlockBits[BlockBits["Unknown_3"] = 16384] = "Unknown_3";
+ BlockBits[BlockBits["DeadBodies"] = 32768] = "DeadBodies";
+ })(BlockBits = exports.BlockBits || (exports.BlockBits = {}));
+ var Collision;
+ (function (Collision) {
+ // Collisions that cause a missile to burst
+ Collision[Collision["BLOCK_MISSILE"] = 2062] = "BLOCK_MISSILE";
+ })(Collision = exports.Collision || (exports.Collision = {}));
+ function getCoordsBetween(x1, y1, x2, y2) {
+ const abs = Math.abs, min = Math.min, max = Math.max, floor = Math.floor;
+ const A = { x: x1, y: y1 };
+ const B = { x: x2, y: y2 };
+ if (max(x1, x2) - min(x1, x2) < max(y1, y2) - min(y1, y2)) {
+ // noinspection JSSuspiciousNameCombination
+ return getCoordsBetween(y1, x1, y2, x2).map(function (_a) {
+ let x = _a.x, y = _a.y;
+ return ({ x: y, y: x });
+ });
+ }
+ function slope(a, b) {
+ if (a.x === b.x) return null;
+ return (b.y - a.y) / (b.x - a.x);
+ }
+ function intercept(point, slope) {
+ // vertical line
+ if (slope === null) return point.x;
+ return point.y - slope * point.x;
+ }
+ const m = slope(A, B);
+ const b = intercept(A, m);
+ const coordinates = [];
+ for (let x = min(A.x, B.x); x <= max(A.x, B.x); x++) {
+ let y = m * x + b;
+ coordinates.push({ x: x, y: y });
+ }
+ return coordinates.map(function (_a) {
+ let x = _a.x, y = _a.y;
+ return ({ x: floor(x), y: floor(y) });
+ })
+ .filter(function (el, idx, self) { return self.findIndex(function (other) { return other.x === el.x && other.y === el.y; }) === idx; });
+ }
+ exports.getCoordsBetween = getCoordsBetween;
+ const convertToCoordArray = function (args, caller, length) {
+ if (length === void 0) { length = 2; }
+ var coords = [];
+ for (var i = 0; i < args.length; i++) {
+ if (typeof args[i] === "number" && i < args.length - 1) {
+ coords.push({ x: args[i], y: args[++i] });
+ } else {
+ coords.push(args[i]);
+ }
+ }
+ if (coords.length !== length) throw TypeError("Didnt give proper arguments to " + caller);
+ return coords;
+ };
+ exports.convertToCoordArray = convertToCoordArray;
+ function getCollisionBetweenCoords() {
+ var args = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ var _a = exports.convertToCoordArray(args, "getCollisionBetweenCoords", 2), one = _a[0], two = _a[1];
+ if (getDistance(one, two) > 50) {
+ return -1;
+ }
+ try {
+ return getCoordsBetween(one.x, one.y, two.x, two.y)
+ .reduce(function (acc, cur) {
+ return (acc | 0)
// | (getCollision(me.area, cur.x+1, cur.y-1) | 0)
// | (getCollision(me.area, cur.x+1, cur.y) | 0)
// | (getCollision(me.area, cur.x+1, cur.y+1) | 0)
// | (getCollision(me.area, cur.x, cur.y-1) | 0)
| (getCollision(me.area, cur.x, cur.y) | 0);
- }, 0);
- }
- catch (e) {
- return -1; // Area not loaded
- }
- }
- exports.getCollisionBetweenCoords = getCollisionBetweenCoords;
- function isBlockedBetween() {
- var args = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- args[_i] = arguments[_i];
- }
- var collision = getCollisionBetweenCoords.apply(null, args);
- return !!(collision & (0
+ }, 0);
+ } catch (e) {
+ return -1; // Area not loaded
+ }
+ }
+ exports.getCollisionBetweenCoords = getCollisionBetweenCoords;
+ function isBlockedBetween() {
+ var args = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ var collision = getCollisionBetweenCoords.apply(null, args);
+ return !!(collision & (0
| BlockBits.LineOfSight
| BlockBits.Ranged
| BlockBits.Casting
| BlockBits.ClosedDoor
| BlockBits.DarkArea
| BlockBits.Objects));
- }
- exports.isBlockedBetween = isBlockedBetween;
- function checkCollisionBetween(unit1, unit2, coll) {
- var args = [];
- args.push(unit1, unit2);
- var collision = getCollisionBetweenCoords.apply(null, args);
- return !!(collision & (0 | coll));
- }
- exports.checkCollisionBetween = checkCollisionBetween;
- Room.prototype.isInRoom = function () {
- var args = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- args[_i] = arguments[_i];
- }
- var _a = exports.convertToCoordArray(args, 'isInRoom', 1)[0], x = _a[0], y = _a[1];
- return this && x >= this.x * 5 && x < this.x * 5 + this.xsize && y >= this.y * 5 && y < this.y * 5 + this.ysize;
- };
- function findCastingSpotSkill(skill, unit, minRange, thickness, collision) {
- if (minRange === void 0) { minRange = 5; }
- if (thickness === void 0) { thickness = 5; }
- if (collision === void 0) { collision = Collision.BLOCK_MISSILE; }
- var range = Skill.getRange(skill);
- console.log('Searching range for', skill, Object.keys(sdk_1.skills).find(function (el) { return sdk_1.skills[el] === skill; }), range);
- return findCastingSpotRange(range, unit, minRange, thickness, collision);
- }
- exports.findCastingSpotSkill = findCastingSpotSkill;
- function findCastingSpotRange(range, unit, minRange, thickness, collision) {
- if (minRange === void 0) { minRange = 5; }
- if (thickness === void 0) { thickness = 5; }
- if (collision === void 0) { collision = Collision.BLOCK_MISSILE; }
- var spots = getSpotsFor(collision, thickness, unit)
- .sort(function (a, b) {
- if (CollMap.checkColl(a, me, BlockBits.BlockWall, 7))
- return 1;
- return getDistance(me, a) - getDistance(me, b);
- });
- return spots.find(function (a) {
- var dist = getDistance(unit.x, unit.y, a.x, a.y);
- return dist < range && dist > minRange;
- });
- }
- exports.findCastingSpotRange = findCastingSpotRange;
- var lines = [];
- function getSpotsFor(collision, thickness, unit) {
- var spots = [];
- var fieldSize = 75;
- for (var oX = -fieldSize; oX < fieldSize; oX++) {
- for (var oY = -fieldSize; oY < fieldSize; oY++) {
- var _a = [unit.x + oX, unit.y + oY], x = _a[0], y = _a[1];
- if (getDistance(unit.x, unit.y, x, y) > 40)
- continue;
- var isCol = !!(getCollision(unit.area, x, y) & collision);
- for (var i = -2; i < 2 && !isCol; i++) {
- for (var j = -2; j < 2 && !isCol; j++) {
- isCol = isCol && !!(getCollision(unit.area, x + i, y + j) & collision);
- }
- }
- // if it isnt a collision to start with
- if (!isCol) {
- spots.push({ x: x, y: y });
- }
- }
- }
- spots = spots.filter(function (el) { return !CollMap.checkColl(el, unit, collision, thickness); });
- //lines.splice(0, lines.length);
- /*spots.map(function (_a) {
+ }
+ exports.isBlockedBetween = isBlockedBetween;
+ function checkCollisionBetween(unit1, unit2, coll) {
+ let args = [];
+ args.push(unit1, unit2);
+ let collision = getCollisionBetweenCoords.apply(null, args);
+ return !!(collision & (0 | coll));
+ }
+ exports.checkCollisionBetween = checkCollisionBetween;
+ Room.prototype.isInRoom = function () {
+ let args = [];
+ for (let _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ let _a = exports.convertToCoordArray(args, "isInRoom", 1)[0], x = _a[0], y = _a[1];
+ return this && x >= this.x * 5 && x < this.x * 5 + this.xsize && y >= this.y * 5 && y < this.y * 5 + this.ysize;
+ };
+ function findCastingSpotSkill(skill, unit, minRange, thickness, collision) {
+ if (minRange === void 0) { minRange = 5; }
+ if (thickness === void 0) { thickness = 5; }
+ if (collision === void 0) { collision = Collision.BLOCK_MISSILE; }
+ let range = Skill.getRange(skill);
+ console.log("Searching range for", skill, Object.keys(sdk_1.skills).find(function (el) { return sdk_1.skills[el] === skill; }), range);
+ return findCastingSpotRange(range, unit, minRange, thickness, collision);
+ }
+ exports.findCastingSpotSkill = findCastingSpotSkill;
+ function findCastingSpotRange(range, unit, minRange, thickness, collision) {
+ if (minRange === void 0) { minRange = 5; }
+ if (thickness === void 0) { thickness = 5; }
+ if (collision === void 0) { collision = Collision.BLOCK_MISSILE; }
+ let spots = getSpotsFor(collision, thickness, unit)
+ .sort(function (a, b) {
+ if (CollMap.checkColl(a, me, BlockBits.BlockWall, 7)) return 1;
+ return getDistance(me, a) - getDistance(me, b);
+ });
+ return spots.find(function (a) {
+ var dist = getDistance(unit.x, unit.y, a.x, a.y);
+ return dist < range && dist > minRange;
+ });
+ }
+ exports.findCastingSpotRange = findCastingSpotRange;
+ var lines = [];
+ function getSpotsFor(collision, thickness, unit) {
+ var spots = [];
+ var fieldSize = 75;
+ for (var oX = -fieldSize; oX < fieldSize; oX++) {
+ for (var oY = -fieldSize; oY < fieldSize; oY++) {
+ var _a = [unit.x + oX, unit.y + oY], x = _a[0], y = _a[1];
+ if (getDistance(unit.x, unit.y, x, y) > 40) continue;
+ var isCol = !!(getCollision(unit.area, x, y) & collision);
+ for (var i = -2; i < 2 && !isCol; i++) {
+ for (var j = -2; j < 2 && !isCol; j++) {
+ isCol = isCol && !!(getCollision(unit.area, x + i, y + j) & collision);
+ }
+ }
+ // if it isnt a collision to start with
+ if (!isCol) {
+ spots.push({ x: x, y: y });
+ }
+ }
+ }
+ spots = spots.filter(function (el) { return !CollMap.checkColl(el, unit, collision, thickness); });
+ //lines.splice(0, lines.length);
+ /*spots.map(function (_a) {
var x = _a.x, y = _a.y;
return lines.push(new Line(x + 1, y + 1, x, y, 0x70, true));
});*/
- return spots;
- }
- exports.getSpotsFor = getSpotsFor;
+ return spots;
+ }
+ exports.getSpotsFor = getSpotsFor;
});
diff --git a/libs/SoloPlay/Modules/Events.js b/libs/SoloPlay/Modules/Events.js
index 26ec7cb4..a85082b7 100644
--- a/libs/SoloPlay/Modules/Events.js
+++ b/libs/SoloPlay/Modules/Events.js
@@ -1,66 +1,106 @@
-const __spreadArray = (this && this.__spreadArray) || function (to, from) {
- for (let i = 0, il = from.length, j = to.length; i < il; i++, j++) {
- to[j] = from[i];
- }
- return to;
-};
+/**
+ * @filename Events.js
+ * @author Jaenster
+ * @desc Transpiled UMD event module. Adds prototypes ("on", "emit", "once", "off") to Unit
+ *
+ */
+
(function (factory) {
- if (typeof module === "object" && typeof module.exports === "object") {
- let 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") {
+ let 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.Events = void 0;
- let handlers = new WeakMap();
- let onceHandlers = new WeakMap();
- let Events = /** @class */ (function () {
- function Events() {
- }
- // Generic type S to give to EventHandler to typehint this function gets the same this as where the event is registered
- Events.prototype.on = function (key, handler, handlerType) {
- if (handlerType === void 0) { handlerType = handlers; }
- let map, set;
- !handlerType.has(this) ? handlerType.set(this, map = new Map) : map = handlerType.get(this);
- !map.has(key) ? map.set(key, set = []) : set = map.get(key);
- // Add this handler, since it has to be unique we dont need to check if it exists
- set.push(handler);
- return this;
- };
- Events.prototype.once = function (key, handler) {
- return this.on(key, handler, onceHandlers);
- };
- Events.prototype.off = function (key, handler) {
- let _this = this;
- [handlers, onceHandlers].forEach(function (handlerType) {
- let map, set, index;
- !handlerType.has(_this) ? handlerType.set(_this, map = new Map) : map = handlerType.get(_this);
- !map.has(key) ? map.set(key, set = []) : set = map.get(key);
- index = set.indexOf(handler);
- if (index > -1) {
- set.splice(index, 1);
- }
- });
- return this;
- };
- Events.prototype.emit = function (key) {
- let _this = this;
- let _a, _b;
- let args = [];
- for (let _i = 1; _i < arguments.length; _i++) {
- args[_i - 1] = arguments[_i];
- }
- let onceSet = ((_a = onceHandlers.get(this)) === null || _a === void 0 ? void 0 : _a.get(key));
- let restSet = ((_b = handlers.get(this)) === null || _b === void 0 ? void 0 : _b.get(key));
- // store callbacks in a set to avoid duplicate handlers
- let callbacks = __spreadArray(__spreadArray([], (onceSet && onceSet.splice(0, onceSet.length) || [])), restSet ? restSet : []);
- callbacks.forEach(function (el) { return el.apply(_this, args); });
- return this;
- };
- return Events;
- }());
- exports.Events = Events;
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.Events = void 0;
+ let handlers = new WeakMap();
+ let onceHandlers = new WeakMap();
+ const __spreadArray = (this && this.__spreadArray) || function (to, from) {
+ for (let i = 0, il = from.length, j = to.length; i < il; i++, j++) {
+ to[j] = from[i];
+ }
+ return to;
+ };
+ // eslint-disable-next-line no-var
+ var Events = /** @class */ (function () {
+ function Events () {
+ }
+ // Generic type S to give to EventHandler to typehint this function gets the same this as where the event is registered
+ Events.prototype.on = function (key, handler, handlerType) {
+ if (handlerType === void 0) {
+ handlerType = handlers;
+ }
+
+ let map, set;
+
+ !handlerType.has(this)
+ ? handlerType.set(this, map = new Map)
+ : map = handlerType.get(this);
+ !map.has(key)
+ ? map.set(key, set = [])
+ : set = map.get(key);
+ // Add this handler, since it has to be unique we dont need to check if it exists
+ set.push(handler);
+ // console.debug(set, map);
+ // console.trace();
+ return this;
+ };
+ Events.prototype.once = function (key, handler) {
+ return this.on(key, handler, onceHandlers);
+ };
+ Events.prototype.off = function (key, handler) {
+ let _this = this;
+ [handlers, onceHandlers].forEach(function (handlerType) {
+ let map, set, index;
+ !handlerType.has(_this)
+ ? handlerType.set(_this, map = new Map)
+ : map = handlerType.get(_this);
+ !map.has(key)
+ ? map.set(key, set = [])
+ : set = map.get(key);
+ index = set.indexOf(handler);
+ if (index > -1) {
+ set.splice(index, 1);
+ }
+ });
+ return this;
+ };
+ Events.prototype.emit = function (key) {
+ let _this = this;
+ let _a, _b;
+ let args = [];
+ for (let _i = 1; _i < arguments.length; _i++) {
+ args[_i - 1] = arguments[_i];
+ }
+ let onceSet = ((_a = onceHandlers.get(this)) === null || _a === void 0 ? void 0 : _a.get(key));
+ let restSet = ((_b = handlers.get(this)) === null || _b === void 0 ? void 0 : _b.get(key));
+ // store callbacks in a set to avoid duplicate handlers
+ let callbacks = __spreadArray(
+ __spreadArray(
+ [],
+ (onceSet && onceSet.splice(0, onceSet.length) || [])
+ ),
+ restSet ? restSet : []
+ );
+ callbacks.forEach(function (el) {
+ return el.apply(_this, args);
+ });
+ return this;
+ };
+ return Events;
+ }());
+
+ // @ts-ignore
+ Unit.prototype.on = Events.prototype.on;
+ // @ts-ignore
+ Unit.prototype.off = Events.prototype.off;
+ // @ts-ignore
+ Unit.prototype.once = Events.prototype.once;
+ // @ts-ignore
+ Unit.prototype.emit = Events.prototype.emit;
+
+ exports.Events = Events;
});
diff --git a/libs/SoloPlay/Modules/GameData.js b/libs/SoloPlay/Modules/GameData.js
deleted file mode 100644
index 75eebb49..00000000
--- a/libs/SoloPlay/Modules/GameData.js
+++ /dev/null
@@ -1,1931 +0,0 @@
-/* eslint-disable no-unused-vars */
-/* eslint-disable no-irregular-whitespace */
-/**
- * @filename GameData.js
- * @author Nishimura-Katsuo
- * @desc game data library
- *
- */
-// todo - remove the magic numbers here
-(function (module, require) {
- const MonsterData = require("./MonsterData");
- const AreaData = require("./AreaData");
- const MissileData = require("./MissileData");
- const Coords_1 = require("./Coords");
- const sdk = require("../../modules/sdk");
-
- function isAlive(unit) {
- return Boolean(unit && unit.hp);
- }
-
- function isEnemy(unit) {
- return Boolean(unit && isAlive(unit) && unit.getStat(sdk.stats.Alignment) !== 2 && typeof unit.classid === "number" && MonsterData[unit.classid].Killable);
- }
-
- function onGround(item) {
- return item.onGroundOrDropping;
- }
-
- const GameData = {
- myReference: me,
- townAreas: [0, 1, 40, 75, 103, 109],
- HPLookup: [["1", "1", "1"], ["7", "107", "830"], ["9", "113", "852"], ["12", "120", "875"], ["15", "125", "897"], ["17", "132", "920"], ["20", "139", "942"], ["23", "145", "965"], ["27", "152", "987"], ["31", "157", "1010"], ["35", "164", "1032"], ["36", "171", "1055"], ["40", "177", "1077"], ["44", "184", "1100"], ["48", "189", "1122"], ["52", "196", "1145"], ["56", "203", "1167"], ["60", "209", "1190"], ["64", "216", "1212"], ["68", "221", "1235"], ["73", "228", "1257"], ["78", "236", "1280"], ["84", "243", "1302"], ["89", "248", "1325"], ["94", "255", "1347"], ["100", "261", "1370"], ["106", "268", "1392"], ["113", "275", "1415"], ["120", "280", "1437"], ["126", "287", "1460"], ["134", "320", "1482"], ["142", "355", "1505"], ["150", "388", "1527"], ["158", "423", "1550"], ["166", "456", "1572"], ["174", "491", "1595"], ["182", "525", "1617"], ["190", "559", "1640"], ["198", "593", "1662"], ["206", "627", "1685"], ["215", "661", "1707"], ["225", "696", "1730"], ["234", "729", "1752"], ["243", "764", "1775"], ["253", "797", "1797"], ["262", "832", "1820"], ["271", "867", "1842"], ["281", "900", "1865"], ["290", "935", "1887"], ["299", "968", "1910"], ["310", "1003", "1932"], ["321", "1037", "1955"], ["331", "1071", "1977"], ["342", "1105", "2000"], ["352", "1139", "2030"], ["363", "1173", "2075"], ["374", "1208", "2135"], ["384", "1241", "2222"], ["395", "1276", "2308"], ["406", "1309", "2394"], ["418", "1344", "2480"], ["430", "1379", "2567"], ["442", "1412", "2653"], ["454", "1447", "2739"], ["466", "1480", "2825"], ["477", "1515", "2912"], ["489", "1549", "2998"], ["501", "1583", "3084"], ["513", "1617", "3170"], ["525", "1651", "3257"], ["539", "1685", "3343"], ["552", "1720", "3429"], ["565", "1753", "3515"], ["579", "1788", "3602"], ["592", "1821", "3688"], ["605", "1856", "3774"], ["618", "1891", "3860"], ["632", "1924", "3947"], ["645", "1959", "4033"], ["658", "1992", "4119"], ["673", "2027", "4205"], ["688", "2061", "4292"], ["702", "2095", "4378"], ["717", "2129", "4464"], ["732", "2163", "4550"], ["746", "2197", "4637"], ["761", "2232", "4723"], ["775", "2265", "4809"], ["790", "2300", "4895"], ["805", "2333", "4982"], ["821", "2368", "5068"], ["837", "2403", "5154"], ["853", "2436", "5240"], ["868", "2471", "5327"], ["884", "2504", "5413"], ["900", "2539", "5499"], ["916", "2573", "5585"], ["932", "2607", "5672"], ["948", "2641", "5758"], ["964", "2675", "5844"], ["982", "2709", "5930"], ["999", "2744", "6017"], ["1016", "2777", "6103"], ["1033", "2812", "6189"], ["1051", "2845", "6275"], ["1068", "2880", "6362"], ["1085", "2915", "6448"], ["1103", "2948", "6534"], ["1120", "2983", "6620"], ["1137", "3016", "6707"], ["10000", "10000", "10000"]],
- monsterLevel: function (monsterID, areaID) {
- return me.diff ? AreaData.hasOwnProperty(areaID) && AreaData[areaID].Level : MonsterData.hasOwnProperty(monsterID) && MonsterData[monsterID].Level; // levels on nm/hell are determined by area, not by monster data
- },
- monsterExp: function (monsterID, areaID, adjustLevel = 0) {
- return Experience.monsterExp[Math.min(Experience.monsterExp.length - 1, this.monsterLevel(monsterID, areaID) + adjustLevel)][me.diff] * MonsterData[monsterID].ExperienceModifier / 100;
- },
- eliteExp: function (monsterID, areaID) {
- return this.monsterExp(monsterID, areaID, 2) * 3;
- },
- monsterAvgHP: function (monsterID, areaID, adjustLevel = 0) {
- return this.HPLookup[Math.min(this.HPLookup.length - 1, this.monsterLevel(monsterID, areaID) + adjustLevel)][me.diff] * (getBaseStat("monstats", monsterID, "minHP") + getBaseStat("monstats", monsterID, "maxHP")) / 200;
- },
- monsterMaxHP: function (monsterID, areaID, adjustLevel = 0) {
- return this.HPLookup[Math.min(this.HPLookup.length - 1, this.monsterLevel(monsterID, areaID) + adjustLevel)][me.diff] * getBaseStat("monstats", monsterID, "maxHP") / 100;
- },
- eliteAvgHP: function (monsterID, areaID) {
- return (6 - me.diff) / 2 * this.monsterAvgHP(monsterID, areaID, 2);
- },
- monsterDamageModifier: function () {
- return 1 + (this.multiplayerModifier() - 1) * 0.0625;
- },
- monsterMaxDmg: function (monsterID, areaID, adjustLevel = 0) {
- let level = this.monsterLevel(monsterID, areaID) + adjustLevel;
- return Math.max.apply(null, [MonsterData[monsterID].Attack1MaxDmg, MonsterData[monsterID].Attack2MaxDmg, MonsterData[monsterID].Skill1MaxDmg]) * level / 100 * this.monsterDamageModifier();
- },
- // https://www.diabloii.net/forums/threads/monster-damage-increase-per-player-count.570346/
- monsterAttack1AvgDmg: function (monsterID, areaID, adjustLevel = 0) {
- let level = this.monsterLevel(monsterID, areaID) + adjustLevel;
- return ((MonsterData[monsterID].Attack1MinDmg + MonsterData[monsterID].Attack1MaxDmg) / 2) * level / 100 * this.monsterDamageModifier();
- },
- monsterAttack2AvgDmg: function (monsterID, areaID, adjustLevel = 0) {
- let level = this.monsterLevel(monsterID, areaID) + adjustLevel;
- return ((MonsterData[monsterID].Attack2MinDmg + MonsterData[monsterID].Attack2MaxDmg) / 2) * level / 100 * this.monsterDamageModifier();
- },
- monsterSkill1AvgDmg: function (monsterID, areaID, adjustLevel = 0) {
- let level = this.monsterLevel(monsterID, areaID) + adjustLevel;
- return ((MonsterData[monsterID].Skill1MinDmg + MonsterData[monsterID].Skill1MaxDmg) / 2) * level / 100 * this.monsterDamageModifier();
- },
- monsterAvgDmg: function (monsterID, areaID, adjustLevel = 0) {
- let attack1 = this.monsterAttack1AvgDmg(monsterID, areaID, adjustLevel);
- let attack2 = this.monsterAttack2AvgDmg(monsterID, areaID, adjustLevel);
- let skill1 = this.monsterSkill1AvgDmg(monsterID, areaID, adjustLevel);
- let dmgs = [attack1, attack2, skill1].filter(x => x > 0);
- // ignore 0 dmg to avoid reducing average
- if (!dmgs.length) return 0;
- return dmgs.reduce((acc, v) => acc + v) / dmgs.length;
- },
- averagePackSize: monsterID => (MonsterData[monsterID].GroupCount.Min + MonsterData[monsterID].MinionCount.Min + MonsterData[monsterID].GroupCount.Max + MonsterData[monsterID].MinionCount.Max) / 2,
- areaLevel: function (areaID) {
- // levels on nm/hell are determined by area, not by monster data
- if (me.diff) return AreaData[areaID].Level;
-
- let levels = 0, total = 0;
-
- AreaData[areaID].forEachMonsterAndMinion((mon, rarity) => {
- levels += mon.Level * rarity;
- total += rarity;
- });
-
- return Math.round(levels / total);
- },
- areaImmunities: function (areaID) {
- let resists = {Physical: 0, Magic: 0, Fire: 0, Lightning: 0, Cold: 0, Poison: 0};
-
- AreaData[areaID].forEachMonsterAndMinion(mon => {
- for (let k in resists) {
- resists[k] = Math.max(resists[k], mon[k]);
- }
- });
-
- 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(GameData.myReference);
- if (!party) return 1;
-
- count = 1;
-
- while (party.getNext()) {
- count++;
- }
- }
-
- return (count + 1) / 2;
- },
- partyModifier: function (playerID) {
- let party = getParty(GameData.myReference), level = 0, total = 0;
- if (!party) return 1;
-
- let 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(GameData.myReference);
- if (!party) return 0;
-
- let level = 0, total = 0;
- let gamesize = 0;
- let 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);
- },
- baseLevel: function (...skillIDs) {
- return skillIDs.reduce((total, skillID) => total + GameData.myReference.getSkill(skillID, 0), 0);
- },
- skillLevel: function (...skillIDs) {
- return skillIDs.reduce((total, skillID) => total + GameData.myReference.getSkill(skillID, 1), 0);
- },
- skillCooldown: function (skillID) {
- return getBaseStat("Skills", skillID, "delay") !== -1;
- },
- stagedDamage: function (l, a, b, c, d, e, f, hitshift = 0, mult = 1) {
- if (l > 28) {
- a += f * (l - 28);
- l = 28;
- }
-
- if (l > 22) {
- a += e * (l - 22);
- l = 22;
- }
-
- if (l > 16) {
- a += d * (l - 16);
- l = 16;
- }
-
- if (l > 8) {
- a += c * (l - 8);
- l = 8;
- }
-
- a += b * (Math.max(0, l) - 1);
-
- return (mult * a) << hitshift;
- },
- damageTypes: ["Physical", "Fire", "Lightning", "Magic", "Cold", "Poison", "?", "?", "?", "Physical"], // 9 is Stun, but stun isn't an element
- synergyCalc: { // TODO: add melee skill damage and synergies - they are poop
-
- // sorc fire spells
- 36: [47, 0.16, 56, 0.16], // fire bolt
- 41: [37, 0.13], // inferno
- 46: [37, 0.04, 51, 0.01], // blaze
- 47: [36, 0.14, 56, 0.14], // fire ball
- 51: [37, 0.04, 41, 0.01], // fire wall
- 52: [37, 0.09], // enchant
- 56: [36, 0.05, 47, 0.05], // meteor
- 62: [36, 0.03, 47, 0.03], // hydra
-
- // sorc lightning spells
- 38: [49, 0.06], // charged bolt
- 49: [38, 0.08, 48, 0.08, 53, 0.08], // lightning
- 53: [38, 0.04, 48, 0.04, 49, 0.04], // chain lightning
-
- // sorc cold spells
- 39: [44, 0.15, 45, 0.15, 55, 0.15, 59, 0.15, 64, 0.15], // ice bolt
- 44: [59, 0.10, 64, 0.10], // frost nova
- 45: [39, 0.08, 59, 0.08, 64, 0.08], // ice blast
- 55: [39, 0.05, 45, 0.05, 64, 0.05], // glacial spike
- 59: [39, 0.05, 45, 0.05, 55, 0.05], // blizzard
- 64: [39, 0.02], // frozen orb
-
- // assassin traps
- 251: [256, 0.09, 261, 0.09, 262, 0.09, 271, 0.09, 272, 0.09, 276, 0.09], // fireblast
- 256: [261, 0.11, 271, 0.11, 276, 0.11], // shock web
- 261: [251, 0.06, 271, 0.06, 276, 0.06], // charged bolt sentry
- 262: [251, 0.08, 272, 0.08], // wake of fire sentry
- 271: [256, 0.12, 261, 0.12, 276, 0.12], // lightning sentry
- 272: [251, 0.10, 276, 0.10, 262, 0.07], // inferno sentry
- 276: [271, 0.12], // death sentry
-
- // necro bone spells
- 67: [78, 0.15, 84, 0.15, 88, 0.15, 93, 0.15], // teeth
- 73: [83, 0.20, 92, 0.20], // poison dagger
- 83: [73, 0.15, 92, 0.15], // poison explosion
- 84: [67, 0.07, 78, 0.07, 88, 0.07, 93, 0.07], // bone spear
- 92: [73, 0.10, 83, 0.10], // poison nova
- 93: [67, 0.06, 78, 0.06, 84, 0.06, 88, 0.06], // bone spirit
-
- // barb war cry
- 154: [130, 0.06, 137, 0.06, 146, 0.06], // war cry
-
- // paladin combat spells
- 101: [112, 0.50, 121, 0.50], // holy bolt
- 112: [108, 0.14, 115, 0.14], // blessed hammer
- 121: [118, 0.07], // fist of heavens
-
- // paladin auras
- 102: [100, 0.18, 125, 0.06], // holy fire
- 114: [105, 0.15, 125, 0.07], // holy freeze
- 118: [110, 0.12, 125, 0.04], // holy shock
-
- // durid elemental skills
- 225: [229, 0.23, 234, 0.23], // firestorm
- 229: [244, 0.10, 225, 0.08], // molten boulder
- 234: [225, 0.12, 244, 0.12], // fissure (eruption)
- 244: [229, 0.12, 234, 0.12, 249, 0.12], // volcano
- 249: [225, 0.14, 229, 0.14, 244, 0.14], // armageddon
- 230: [250, 0.15, 235, 0.15], // arctic blast
- 240: [245, 0.10, 250, 0.10], // twister
- 245: [235, 0.09, 240, 0.09, 250, 0.09], // tornado
- 250: [240, 0.09, 245, 0.09], // hurricane
-
- // durid feral skills
- 238: [222, 0.18], // rabies
- 239: [225, 0.22, 229, 0.22, 234, 0.22, 244, 0.22], // fire claws
-
- // amazon bow/xbow skills
- 11: [21, 0.12], // cold arrow
- 21: [11, 0.08], // ice arrow
- 31: [11, 0.12], // freezing arrow
- 7: [16, 0.12], // fire arrow
- 16: [7, 0.12], // exploding arrow
- 27: [16, 0.10], // immolation arrow
-
- // amazon spear/javalin skills
- 14: [20, 0.10, 24, 0.10, 34, 0.10, 35, 0.10], // power strike
- 20: [14, 0.03, 24, 0.03, 34, 0.03, 35, 0.03], // lightning bolt
- 24: [14, 0.10, 20, 0.10, 34, 0.10, 35, 0.10], // charged strike
- 34: [14, 0.08, 20, 0.08, 24, 0.10, 35, 0.10], // lightning strike
- 35: [14, 0.01, 20, 0.01, 24, 0.01, 34, 0.01], // lightning fury
- 15: [25, 0.12], // poison javalin
- 25: [15, 0.10], // plague javalin
- },
- noMinSynergy: [14, 20, 24, 34, 35, 49, 53, 118, 256, 261, 271, 276],
- skillMult: {
- 15: 25,
- 25: 25,
- 41: 25,
- 46: 75,
- 51: 75,
- 73: 25,
- 83: 25,
- 92: 25,
- 222: 25,
- 225: 75,
- 230: 25,
- 238: 25,
- 272: 25 / 3
- },
- baseSkillDamage: function (skillID) { // TODO: rework skill damage to use both damage fields
- let l = this.skillLevel(skillID), m = this.skillMult[skillID] || 1;
- let dmgFields = [["MinDam", "MinLevDam1", "MinLevDam2", "MinLevDam3", "MinLevDam4", "MinLevDam5", "MaxDam", "MaxLevDam1", "MaxLevDam2", "MaxLevDam3", "MaxLevDam4", "MaxLevDam5"], ["EMin", "EMinLev1", "EMinLev2", "EMinLev3", "EMinLev4", "EMinLev5", "EMax", "EMaxLev1", "EMaxLev2", "EMaxLev3", "EMaxLev4", "EMaxLev5"]];
-
- if (skillID === 70) {
- return {
- type: "Physical",
- pmin: this.stagedDamage(l, getBaseStat("skills", skillID, dmgFields[1][0]), getBaseStat("skills", skillID, dmgFields[1][1]), getBaseStat("skills", skillID, dmgFields[1][2]), getBaseStat("skills", skillID, dmgFields[1][3]), getBaseStat("skills", skillID, dmgFields[1][4]), getBaseStat("skills", skillID, dmgFields[1][5]), getBaseStat("skills", skillID, "HitShift"), m),
- pmax: this.stagedDamage(l, getBaseStat("skills", skillID, dmgFields[1][0]), getBaseStat("skills", skillID, dmgFields[1][1]), getBaseStat("skills", skillID, dmgFields[1][2]), getBaseStat("skills", skillID, dmgFields[1][3]), getBaseStat("skills", skillID, dmgFields[1][4]), getBaseStat("skills", skillID, dmgFields[1][5]), getBaseStat("skills", skillID, "HitShift"), m),
- min: 0, max: 0
- };
- } else {
- let type = getBaseStat("skills", skillID, "EType");
-
- return {
- type: this.damageTypes[type],
- pmin: this.stagedDamage(l, getBaseStat("skills", skillID, dmgFields[0][0]), getBaseStat("skills", skillID, dmgFields[0][1]), getBaseStat("skills", skillID, dmgFields[0][2]), getBaseStat("skills", skillID, dmgFields[0][3]), getBaseStat("skills", skillID, dmgFields[0][4]), getBaseStat("skills", skillID, dmgFields[0][5]), getBaseStat("skills", skillID, "HitShift"), m),
- pmax: this.stagedDamage(l, getBaseStat("skills", skillID, dmgFields[0][6]), getBaseStat("skills", skillID, dmgFields[0][7]), getBaseStat("skills", skillID, dmgFields[0][8]), getBaseStat("skills", skillID, dmgFields[0][9]), getBaseStat("skills", skillID, dmgFields[0][10]), getBaseStat("skills", skillID, dmgFields[0][11]), getBaseStat("skills", skillID, "HitShift"), m),
- min: type ? this.stagedDamage(l, getBaseStat("skills", skillID, dmgFields[1][0]), getBaseStat("skills", skillID, dmgFields[1][1]), getBaseStat("skills", skillID, dmgFields[1][2]), getBaseStat("skills", skillID, dmgFields[1][3]), getBaseStat("skills", skillID, dmgFields[1][4]), getBaseStat("skills", skillID, dmgFields[1][5]), getBaseStat("skills", skillID, "HitShift"), m) : 0,
- max: type ? this.stagedDamage(l, getBaseStat("skills", skillID, dmgFields[1][6]), getBaseStat("skills", skillID, dmgFields[1][7]), getBaseStat("skills", skillID, dmgFields[1][8]), getBaseStat("skills", skillID, dmgFields[1][9]), getBaseStat("skills", skillID, dmgFields[1][10]), getBaseStat("skills", skillID, dmgFields[1][11]), getBaseStat("skills", skillID, "HitShift"), m) : 0
- };
- }
- },
- skillRadius: {
- //47: 8,
- //48: 5, // Nova
- 55: 3,
- 56: 12,
- 92: 24,
- 154: 12,
- 249: 24,
- 250: 24,
- 251: 3,
- },
- novaLike: {
- 44: true,
- 48: true,
- 92: true,
- 112: true,
- 154: true,
- 249: true,
- 250: true,
- },
- wolfBanned: {
- 225: true,
- 229: true,
- 230: true,
- 233: true,
- 234: true,
- 235: true,
- 240: true,
- 243: true,
- 244: true,
- 245: true,
- 250: true,
- },
- bearBanned: {
- 225: true,
- 229: true,
- 230: true,
- 232: true,
- 234: true,
- 235: true,
- 238: true,
- 240: true,
- 244: true,
- 245: true,
- 248: true,
- },
- humanBanned: {
- 232: true,
- 233: true,
- 238: true,
- 239: true,
- 242: true,
- 243: true,
- 248: true,
- },
- nonDamage: {
- // Some fakes to avoid these
-
- 54: true, // teleport
- 217: true, // scroll identify
- 218: true, // portal scroll
- 219: true, // I assume this is the book of scroll
- 220: true, // book portal. Not really a skill you want to use, do you
- 117: true, // Holy shield. Holy shield it self doesnt give damage
- 278: true, // venom adds damage, but doesnt do damage on its own
-
- // Remove all the trap skills, as we prefer to calculate this upon demand
- 261: true, // lighting bolt
- 271: true, // lighting sentry
- 276: true, // Death sentry only works on corpses, we calculate this within attack
- 262: true, // wake of fire
- 272: true, // inferno
- },
- shiftState: function () {
- if (GameData.myReference.getState(139)) return "wolf";
- if (GameData.myReference.getState(140)) return "bear";
- return "human";
- },
- bestForm: function (skillID) {
- if (this.shiftState() === "human" && this.humanBanned[skillID]) {
- let highest = {ID: 0, Level: 0};
-
- if (!this.wolfBanned[skillID] && this.skillLevel(223) > highest.Level) {
- highest.ID = 223;
- highest.Level = this.skillLevel(223);
- }
-
- if (!this.bearBanned[skillID] && this.skillLevel(228) > highest.Level) {
- highest.ID = 228;
- highest.Level = this.skillLevel(228);
- }
-
- return highest.ID;
- } else if (this.shiftState() === "wolf" && this.wolfBanned[skillID]) {
- return 223;
- } else if (this.shiftState() === "bear" && this.bearBanned[skillID]) {
- return 228;
- }
-
- return 0;
- },
- physicalAttackDamage: function (skillID) {
- let dmg = (() => {
- switch (skillID) {
- case sdk.skills.Bash:
- return 45 + (5 + GameData.myReference.getSkill(skillID, 1)) + (5 * GameData.myReference.getSkill(sdk.skills.Stun, 0));
- case sdk.skills.Stun:
- return (8 * GameData.myReference.getSkill(sdk.skills.Bash, 0));
- case sdk.skills.Concentrate:
- return (65 + (5 * GameData.myReference.getSkill(skillID, 1)) + (5 * GameData.myReference.getSkill(sdk.skills.Bash, 0)) + (10 * GameData.myReference.getSkill(sdk.skills.BattleOrders, 0)));
- case sdk.skills.LeapAttack:
- return (70 + (30 * GameData.myReference.getSkill(skillID, 1)) + (10 * GameData.myReference.getSkill(sdk.skills.Leap, 0)));
- case sdk.skills.Whirlwind:
- return (8 * GameData.myReference.getSkill(skillID, 1)) - 58;
- default:
- return 0;
- }
- })();
-
- // return (((GameData.myReference.getStat(sdk.stats.MaxDamage) + GameData.myReference.getStat(sdk.stats.MinDamage)) / 2) + (GameData.myReference.getStat(sdk.stats.Strength) * dmg)) / 100;
- return dmg;
- },
- dmgModifier: function (skillID, target) {
- let aps = (typeof target === "number" ? this.averagePackSize(target) : 1);
- let eliteBonus = (target.spectype && target.isSpecial) ? 1 : 0, hitcap = 1;
-
- switch (skillID) { // charged bolt/strike excluded, it's so unreliably random
- case 15: // poison javalin
- case 25: // plague javalin
- case 16: // exploding arrow
- case 27: // immolation arrow
- case 31: // freezing arrow
- case 35: // lightning fury
- case 44: // frost nova
- case 48: // nova
- case 56: // meteor
- case 59: // blizzard
- case 64: // frozen orb
- case 83: // poison explosion
- case 92: // poison nova
- case 112: // blessed hammer
- case 154: // war cry
- case 229: // molten boulder
- case 234: // fissure
- case 249: // armageddon
- case 244: // volcano
- case 250: // hurricane
- case 251: // fireblast
- case 261: // charged bolt sentry
- case 262: // wake of fire
- case 55: // glacial spike
- case 47: // fire ball
- case 42: // Static field.
- case 38: // charged bolt
- hitcap = Infinity;
- break;
- case 34: // lightning strike
- hitcap = 1 + this.skillLevel(34);
- break;
- case 67: // teeth
- hitcap = 1 + this.skillLevel(67);
- break;
- case 53: // chain lightning
- hitcap = 5 + ((this.skillLevel(53) / 5) | 0);
- break;
- case 24:
- hitcap = 3 + ((this.skillLevel(24) / 5) | 0);
- break;
- case 49: // lightning
- case 84: // bone spear
- case 271: // lightning sentry
- case 276: // death sentry
- hitcap = aps ? Math.sqrt(aps / Math.PI) * 2 : 1;
- break;
- default:
- hitcap = 1;
- break;
- }
-
- if (typeof target !== "number") {
- let unit = Game.getMonster();
- let radius = this.skillRadius[skillID] || 18;
-
- if (unit) {
- do {
- if (aps >= hitcap) {
- break;
- }
-
- if (target.gid !== unit.gid && getDistance(unit, this.novaLike[skillID] ? GameData.myReference : target) <= radius && isEnemy(unit)) {
- aps++;
-
- if (unit.isSpecial) {
- eliteBonus++;
- }
- }
- } while (unit.getNext());
- }
- } else {
- aps = Math.min(aps, hitcap);
- }
-
- aps += eliteBonus * (4 - me.diff) / 2;
-
- return aps;
- },
- skillDamage: function (skillID, unit) {
- // TODO: caluclate basic attack damage
- if (skillID === 0) return {type: "Physical", pmin: 2, pmax: 8, min: 0, max: 0}; // short sword, no reqs
-
- if (this.skillLevel(skillID) < 1) {
- return {
- type: this.damageTypes[getBaseStat("skills", skillID, "EType")],
- pmin: 0,
- pmax: 0,
- min: 0,
- max: 0
- };
- }
-
- let dmg = this.baseSkillDamage(skillID), mastery = 1, psynergy = 1, synergy = 1, shots = 1, sl = 0;
-
- if (this.synergyCalc[skillID]) {
- let sc = this.synergyCalc[skillID];
-
- for (let c = 0; c < sc.length; c += 2) {
- sl = this.baseLevel(sc[c]);
-
- if (skillID === 229 || skillID === 244) {
- if (sc[c] === 229 || sc[c] === 244) { // molten boulder and volcano
- psynergy += sl * sc[c + 1]; // they only synergize physical with each other
- } else {
- synergy += sl * sc[c + 1]; // all other skills synergize only fire with these skills
- }
- } else {
- psynergy += sl * sc[c + 1];
- synergy += sl * sc[c + 1];
- }
- }
- }
-
- if (skillID === 227 || skillID === 237 || skillID === 247) {
- sl = this.skillLevel(247);
- psynergy += 0.15 + sl * 0.10;
- synergy += 0.15 + sl * 0.10;
- }
-
- switch (dmg.type) {
- case "Fire": // fire mastery
- mastery = 1 + GameData.myReference.getStat(sdk.stats.PassiveFireMastery) / 100;
- dmg.min *= mastery;
- dmg.max *= mastery;
- break;
- case "Lightning": // lightning mastery
- mastery = 1 + GameData.myReference.getStat(sdk.stats.PassiveLightningMastery) / 100;
- dmg.min *= mastery;
- dmg.max *= mastery;
- break;
- case "Cold": // cold mastery
- mastery = 1 + GameData.myReference.getStat(sdk.stats.PassiveColdMastery) / 100;
- dmg.min *= mastery;
- dmg.max *= mastery;
- break;
- case "Poison": // poison mastery
- mastery = 1 + GameData.myReference.getStat(sdk.stats.PassivePoisonMastery) / 100;
- dmg.min *= mastery;
- dmg.max *= mastery;
- break;
- case "Magic": // magic mastery
- mastery = 1 + GameData.myReference.getStat(sdk.stats.PassiveMagMastery) / 100;
- dmg.min *= mastery;
- dmg.max *= mastery;
- break;
- }
-
- dmg.pmin *= psynergy;
- dmg.pmax *= psynergy;
-
- if (this.noMinSynergy.indexOf(skillID) < 0) {
- dmg.min *= synergy;
- }
-
- dmg.max *= synergy;
-
- switch (skillID) {
- case 102: // holy fire
- dmg.min *= 6; // weapon damage is 6x the aura damage
- dmg.max *= 6;
- break;
- case 114: // holy freeze
- dmg.min *= 5; // weapon damage is 5x the aura damage
- dmg.max *= 5;
- break;
- case 118: // holy shock
- dmg.min *= 6; // weapon damage is 6x the aura damage
- dmg.max *= 6;
- break;
- case 249: // armageddon
- dmg.pmin = dmg.pmax = 0;
- break;
- case 24: // charged strike
- dmg.max *= 3 + ((this.skillLevel(24) / 5) | 0);
- }
-
- dmg.pmin >>= 8;
- dmg.pmax >>= 8;
- dmg.min >>= 8;
- dmg.max >>= 8;
-
- switch (skillID) {
- case sdk.skills.ChargedBolt: // more than one bolt can hit but may calc this as splashdamage instead
- let baseId = getBaseStat("monstats", unit.classid, "baseid");
- let size = getBaseStat("monstats2", baseId, "sizex");
- (typeof size !== "number" || size < 1 || size > 3) && (size = 3);
- let dist = unit.distance;
- const modifier = size === 1 ? 0.5 : size === 3 ? 1.5 : size === 2 && dist < 5 ? 1.2 : 1;
- dmg.min *= modifier;
- dmg.max *= modifier;
-
- // need to take into account the amount of bolts released
- // the size of the unit we are targetting
- // the distance from the target
- break;
- case 59: // blizzard - on average hits twice
- dmg.min *= 2;
- dmg.max *= 2;
- break;
- case 62: // hydra - 3 heads
- dmg.min *= 3;
- dmg.max *= 3;
- break;
- case 64: // frozen orb - on average hits ~5 times
- dmg.min *= 5;
- dmg.max *= 5;
- break;
- case 70: // skeleton - a hit per skeleton
- sl = this.skillLevel(70);
- shots = sl < 4 ? sl : (2 + sl / 3) | 0;
- sl = Math.max(0, sl - 3);
- dmg.pmin = shots * (dmg.pmin + 1 + this.skillLevel(69) * 2) * (1 + sl * 0.07);
- dmg.pmax = shots * (dmg.pmax + 2 + this.skillLevel(69) * 2) * (1 + sl * 0.07);
- break;
- case 94: // fire golem
- sl = this.skillLevel(94);
- dmg.min = [10, 15, 18][me.diff] + dmg.min + (this.stagedDamage(sl + 7, 2, 1, 2, 3, 5, 7) >> 1) * 6; // basically holy fire added
- dmg.max = [27, 39, 47][me.diff] + dmg.max + (this.stagedDamage(sl + 7, 6, 1, 2, 3, 5, 7) >> 1) * 6;
- break;
- case 101: // holy bolt
- dmg.undeadOnly = true;
- break;
- case 112: // blessed hammer
- sl = this.skillLevel(113);
-
- if (sl > 0) {
- mastery = (100 + ((45 + this.skillLevel(113) * 15) >> 1)) / 100; // hammer gets half concentration dmg bonus
- dmg.min *= mastery;
- dmg.max *= mastery;
- }
-
- break;
- case 221: // raven - a hit per raven
- shots = Math.min(5, this.skillLevel(221)); // 1-5 ravens
- dmg.pmin *= shots;
- dmg.pmax *= shots;
- break;
- case 227: // spirit wolf - a hit per wolf
- shots = Math.min(5, this.skillLevel(227));
- dmg.pmin *= shots;
- dmg.pmax *= shots;
- break;
- case 237: // dire wolf - a hit per wolf
- shots = Math.min(3, this.skillLevel(237));
- dmg.pmin *= shots;
- dmg.pmax *= shots;
- break;
- case 240: // twister
- dmg.pmin *= 3;
- dmg.pmax *= 3;
- break;
- case 261: // charged bolt sentry
- case 262: // wake of fire
- case 271: // lightning sentry
- case 272: // inferno sentry
- case 276: // death sentry
- dmg.min *= 5; // can have 5 traps out at a time
- dmg.max *= 5;
- break;
-
- case sdk.skills.StaticField:
- if (!(unit instanceof Unit)) {
- break;
- }
- // No cap in classic
- let staticCap = (me.gametype === sdk.game.gametype.Classic ? 0 : [0, 33, 50][me.diff]);
- const [monsterId, areaId] = [unit.classid, unit.area];
- let percentLeft = (unit.hp * 100 / unit.hpmax);
- if (staticCap > percentLeft) {
- break;
- }
-
- const maxReal = this.monsterMaxHP(monsterId, areaId, unit.charlvl - this.monsterLevel(monsterId, areaId));
- let hpReal = maxReal / 100 * percentLeft;
- let potencialDmg = (hpReal / 100 * percentLeft) * 0.25;
-
- let tmpDmg = (maxReal / 100 * percentLeft) * (0.25);
-
- // We do need to calculate the extra damage, or less damage due to resistance
- let resist = this.monsterResist(unit, "Lightning");
- let pierce = GameData.myReference.getStat(this.pierceMap.Lightning);
-
- let conviction = this.getConviction();
- // if (conviction && !unit.getState(sdk.states.Conviction)) conviction = 0; //ToDo; enable when fixed telestomp
- resist -= (resist >= 100 ? conviction / 5 : conviction);
- resist = (resist < 100 ? Math.max(-100, resist - pierce) : 100);
- tmpDmg = potencialDmg * ((100 - resist) / 100);
- const percentageDamage = 100 / maxReal * tmpDmg;
-
- let avgDmg = tmpDmg;
- let overCap = percentLeft - staticCap - percentageDamage;
- if (overCap < 0) {
- let maxDmgPercentage = percentageDamage - Math.abs(overCap);
- avgDmg = maxReal / 100 * maxDmgPercentage;
- }
- avgDmg = avgDmg > 0 && avgDmg || 0;
- //console.log('Static will chop off -> ' + (100 / maxReal * avgDmg) + '%');
- dmg.min = avgDmg;
- dmg.max = avgDmg;
- break;
- }
-
- dmg.pmin |= 0;
- dmg.pmax |= 0;
- dmg.min |= 0;
- dmg.max |= 0;
-
- return dmg;
- },
- // todo - build me metadata - then use it to calulate a range of skills rather than redo the exact same calculations
- // example - trying to check the damage of blizard and then frozen orb
- // currently it would check our stats, then check amp and conviction - those could all be pre-built as they aren't going to change
- avgSkillDamage: function (skillID, unit) {
- if (skillID === undefined || unit === undefined || !skillID || !unit || !Skill.canUse(skillID)) return 0;
- let skillToCheck, avgDmg;
- const getTotalDmg = function (skillData, unit) {
- let ampDmg = Skill.canUse(66) ? 100 : (Skill.canUse(87) ? 50 : 0);
- let avgPDmg = (skillData.pmin + skillData.pmax) / 2, totalDmg = 0, avgDmg = (skillData.min + skillData.max) / 2;
- //let hp = GameData.monsterMaxHP(typeof unit === 'number' ? unit : unit.classid, me.area);
- let conviction = GameData.getConviction(), isUndead = (typeof unit === "number" ? MonsterData[unit].Undead : MonsterData[unit.classid].Undead);
- if (avgPDmg > 0) {
- let presist = GameData.monsterResist(unit, "Physical");
- presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
- presist = Math.max(-100, Math.min(100, presist));
- totalDmg += avgPDmg * (100 - presist) / 100;
- }
- if (avgDmg > 0 && (!isUndead || !skillData.undeadOnly)) {
- let resist = GameData.monsterResist(unit, skillData.type);
- let pierce = GameData.myReference.getStat(GameData.pierceMap[skillData.type]);
- if (GameData.convictionEligible[skillData.type]) {
- resist -= (resist >= 100 ? conviction / 5 : conviction);
- }
- resist = (resist < 100 ? Math.max(-100, resist - pierce) : 100);
- totalDmg += avgDmg * (100 - resist) / 100;
- }
- return totalDmg;
- };
- const calculateSplashDamage = function (skill, splash, target) {
- return getUnits(sdk.unittype.Monster)
- .filter((mon) => mon.attackable && getDistance(target, mon) < splash)
- .reduce(function (acc, cur) {
- let _a = GameData.skillDamage(skill, cur);
- return acc + getTotalDmg(_a, cur);
- }, 0);
- };
- const calculateChainDamage = function (skill, target) {
- skill === undefined && (skill = -1);
- let rawDmg = 0, totalDmg = 0, range = 0, hits = 0;
- switch (skill) {
- case sdk.skills.ChainLightning:
- hits = Math.round((25 + me.getSkill(sdk.skills.ChainLightning, sdk.skills.subindex.SoftPoints)) / 5);
- range = 13;
- break;
- }
- let units = getUnits(sdk.unittype.Monster)
- .filter((mon) => mon.attackable && getDistance(mon, target) < range)
- .sort((a, b) => getDistance(target, a) - getDistance(target, b));
- if (units.length === 1) {
- rawDmg = GameData.skillDamage(skill, target);
- return getTotalDmg(rawDmg, target);
- } else {
- console.log("Units to check: " + units.length);
- for (let i = 0; i < units.length; i++) {
- if (units[i] !== undefined) {
- rawDmg = GameData.skillDamage(skill, units[i]);
- totalDmg += getTotalDmg(rawDmg, units[i]);
- if (i > hits) { break; }
- } else {
- units.splice(i, 1);
- i -= 1;
- }
- }
- return totalDmg;
- }
- };
- const calculateRawStaticDamage = function (distanceUnit) {
- distanceUnit === undefined && (distanceUnit = me);
- if (!Skill.canUse(sdk.skills.StaticField)) return 0;
- let range = Skill.getRange(sdk.skills.StaticField), cap = (me.gametype === sdk.game.gametype.Classic ? 1 : [1, 25, 50][me.diff]);
- const pierce = me.getStat(sdk.stats.PierceLtng);
- return getUnits(sdk.unittype.Monster)
- .filter(function (mon) {
- return mon.attackable && getDistance(mon, distanceUnit) < range;
- }).reduce(function (acc, unit) {
- let classId = unit.classid, areaId = unit.area;
- let maxHealth = GameData.monsterAvgHP(classId, areaId, unit.charlvl - GameData.monsterLevel(classId, areaId));
- let currentHealth = maxHealth / 100 * (unit.hp * 100 / unit.hpmax), baseDamage = currentHealth * 0.25;
- // monsterRes already considers conviction state
- let monsterRes = unit.getStat(sdk.stats.LightResist);
- let totalRes = Math.min(100, Math.max(-100, monsterRes - pierce));
- // calculate the actual damage we do
- let potentialDamage = baseDamage / (100 / (100 - totalRes));
- let cappedAtHealth = maxHealth / 100 * cap;
- // cap max damage
- let actualDamage = currentHealth - Math.max(cappedAtHealth, (currentHealth - potentialDamage));
- return acc + (actualDamage);
- }, 0);
- };
- switch (skillID) {
- case sdk.skills.Blizzard:
- case sdk.skills.Meteor:
- case sdk.skills.FireBall:
- case sdk.skills.GlacialSpike:
- case sdk.skills.ChargedBolt:
- let {x, y} = unit;
-
- if (!Attack.validSpot(x, y, skillID, unit.classid)) {
- return 0;
- }
-
- return calculateSplashDamage(skillID, 4, unit);
- case sdk.skills.FrostNova:
- case sdk.skills.Nova:
- return calculateSplashDamage(skillID, 6, unit);
- case sdk.skills.StaticField:
- return calculateRawStaticDamage(unit);
- case sdk.skills.ChainLightning:
- return calculateChainDamage(skillID, unit);
- default:
- skillToCheck = this.skillDamage(skillID, unit);
- return getTotalDmg(skillToCheck, unit);
- }
- },
- allSkillDamage: function (unit) {
- let skills = {};
- let self = this;
- GameData.myReference.getSkill(4).forEach(function (skill) {
- if (self.nonDamage.hasOwnProperty(skill[0])) {
- return false; // Doesnt do damage
- }
- return skills[skill[0]] = self.skillDamage(skill[0], unit);
- });
-
- return skills;
- },
- convictionEligible: {
- Fire: true,
- Lightning: true,
- Cold: true,
- },
- lowerResistEligible: {
- Fire: true,
- Lightning: true,
- Cold: true,
- Poison: true,
- },
- resistMap: {
- Physical: 36,
- Fire: 39,
- Lightning: 41,
- Cold: 43,
- Poison: 45,
- Magic: 37,
- },
- masteryMap: {
- Fire: 329,
- Lightning: 330,
- Cold: 331,
- Poison: 332,
- Magic: 357,
- },
- pierceMap: {
- Fire: 333,
- Lightning: 334,
- Cold: 335,
- Poison: 336,
- Magic: 358,
- },
- ignoreSkill: {
- 40: true,
- 50: true,
- 60: true,
- },
- buffs: {
- 8: 1,
- 9: 1,
- 13: 1,
- 17: 1,
- 18: 1,
- 23: 1,
- 28: 1,
- 29: 1,
- 32: 1,
- 37: 1,
- 40: 2,
- 46: 1,
- 50: 2,
- 52: 1,
- 57: 1,
- 58: 1,
- 60: 2,
- 61: 1,
- 63: 1,
- 65: 1,
- 68: 1,
- 69: 1,
- 79: 1,
- 89: 1,
- 98: 3,
- 99: 3,
- 100: 3,
- 102: 3,
- 103: 3,
- 104: 3,
- 105: 3,
- 108: 3,
- 109: 3,
- 110: 3,
- 113: 3,
- 114: 3,
- 115: 3,
- 118: 3,
- 119: 3,
- 120: 3,
- 122: 3,
- 123: 3,
- 124: 3,
- 125: 3,
- 127: 1,
- 128: 1,
- 129: 1,
- 134: 1,
- 135: 1,
- 136: 1,
- 138: 1,
- 141: 1,
- 145: 1,
- 148: 1,
- 149: 1,
- 153: 1,
- 155: 1,
- 221: 1,
- 222: 4,
- 223: 5,
- 224: 1,
- 226: 6,
- 227: 7,
- 228: 5,
- 231: 4,
- 235: 1,
- 236: 6,
- 237: 7,
- 241: 4,
- 246: 6,
- 247: 7,
- 249: 1,
- 250: 1,
- 258: 8,
- 267: 8,
- 268: 9,
- 279: 9,
- },
- preAttackable: [
- sdk.skills.MagicArrow, sdk.skills.FireArrow, sdk.skills.MultipleShot, sdk.skills.ExplodingArrow, sdk.skills.IceArrow, sdk.skills.GuidedArrow, sdk.skills.ImmolationArrow, sdk.skills.Strafe,
- sdk.skills.PlagueJavelin, sdk.skills.LightningFury,
- sdk.skills.FireBolt, sdk.skills.Inferno, sdk.skills.Blaze, sdk.skills.FireBall, sdk.skills.FireWall, sdk.skills.Meteor, sdk.skills.Hydra,
- sdk.skills.ChargedBolt, sdk.skills.Nova, sdk.skills.Lightning, sdk.skills.ChainLightning,
- sdk.skills.IceBolt, sdk.skills.FrostNova, sdk.skills.IceBlast, sdk.skills.Blizzard, sdk.skills.FrozenOrb,
- 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,
- sdk.skills.Teeth, sdk.skills.BoneSpear, sdk.skills.PoisonNova,
- sdk.skills.BlessedHammer,
- sdk.skills.WarCry,
- sdk.skills.Twister, sdk.skills.Tornado,
- sdk.skills.FireBlast, sdk.skills.ShockWeb,
- ],
- monsterResist: function (unit, type) {
- let stat = this.resistMap[type];
- return stat ? (unit.getStat ? unit.getStat(stat) : MonsterData[unit][type]) : 0;
- },
- getConviction: function () {
- let merc = GameData.myReference.getMerc(), sl = this.skillLevel(123); // conviction
- if (( // Either me, or merc is wearing a conviction
- merc && merc.getItemsEx().filter(item => item.getPrefix(sdk.locale.items.Infinity)).first()
- || GameData.myReference.getItemsEx(-1, 1).filter(item => item.getPrefix(sdk.locale.items.Infinity)).first())) {
- sl = 12;
- }
- return sl > 0 ? Math.min(150, 30 + (sl - 1) * 5) : 0;
- },
- getAmp: function () {
- return this.skillLevel(66) ? 100 : (this.skillLevel(87) ? 50 : 0);
- },
- monsterEffort: function (unit, areaID, skillDamageInfo = undefined, parent = undefined, preattack = false, all = false) {
- let buffDmg = [];
- const allData = [];
- const buffDamageInfo = {};
- const newSkillDamageInfo = {};
- const eret = { effort: Infinity, skill: -1, type: "Physical" };
- const useCooldown = (typeof unit === "number" ? false : Boolean(me.skillDelay));
- const hp = this.monsterMaxHP(typeof unit === "number" ? unit : unit.classid, areaID);
- const conviction = this.getConviction(), ampDmg = this.getAmp();
- const isUndead = (typeof unit === "number" ? MonsterData[unit].Undead : MonsterData[unit.classid].Undead);
- skillDamageInfo = skillDamageInfo || this.allSkillDamage(unit);
- // if (conviction && unit instanceof Unit && !unit.getState(sdk.states.Conviction)) conviction = 0; //ToDo; enable when fixed telestomp
-
- for (let sk in skillDamageInfo) {
- if (this.buffs[sk]) {
- if (typeof unit === "number") {
- buffDmg[this.buffs[sk]] = 0;
- buffDamageInfo[sk] = skillDamageInfo[sk];
- }
- } else {
- newSkillDamageInfo[sk] = skillDamageInfo[sk];
- }
- }
-
- skillDamageInfo = newSkillDamageInfo;
-
- for (let sk in buffDamageInfo) {
- // static field has a fix'd ceiling, calculated already
- if ([sdk.skills.StaticField].indexOf(sk) !== -1) continue;
-
- let avgPDmg = (buffDamageInfo[sk].pmin + buffDamageInfo[sk].pmax) / 2;
- let avgDmg = (buffDamageInfo[sk].min + buffDamageInfo[sk].max) / 2;
- let tmpDmg = 0;
-
- if (avgPDmg > 0) {
- let presist = this.monsterResist(unit, "Physical");
-
- presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
- presist = Math.max(-100, Math.min(100, presist));
- tmpDmg += avgPDmg * (100 - presist) / 100;
- }
-
- if (avgDmg > 0 && (!isUndead || !buffDamageInfo[sk].undeadOnly) && sk !== sdk.skills.StaticField) {
- let resist = this.monsterResist(unit, buffDamageInfo[sk].type);
- let pierce = GameData.myReference.getStat(this.pierceMap[buffDamageInfo[sk].type]);
-
- if (this.convictionEligible[buffDamageInfo[sk].type]) {
- resist -= (resist >= 100 ? conviction / 5 : conviction);
- }
-
- if (resist < 100) {
- resist = Math.max(-100, resist - pierce);
- } else {
- resist = 100;
- }
-
- tmpDmg += avgDmg * (100 - resist) / 100;
- }
-
- if (this.buffs[sk] === 1) {
- buffDmg[this.buffs[sk]] += tmpDmg;
- } else {
- buffDmg[this.buffs[sk]] = Math.max(buffDmg[this.buffs[sk]], tmpDmg);
- }
- }
-
- buffDmg = buffDmg.reduce((t, v) => t + v, 0);
-
- for (let sk in skillDamageInfo) {
- if (preattack && this.preAttackable.indexOf(parseInt(sk)) === -1) continue; // cant preattack this skill
- if (!this.ignoreSkill[sk] && (!useCooldown || !this.skillCooldown(sk | 0))) {
- let avgPDmg = (skillDamageInfo[sk].pmin + skillDamageInfo[sk].pmax) / 2, totalDmg = buffDmg;
- let avgDmg = (skillDamageInfo[sk].min + skillDamageInfo[sk].max) / 2;
-
- if (avgPDmg > 0 && sk !== sdk.skills.StaticField) {
- let presist = this.monsterResist(unit, "Physical");
-
- presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
- presist = Math.max(-100, Math.min(100, presist));
- totalDmg += avgPDmg * (100 - presist) / 100;
- }
-
- if (avgDmg > 0 && (!isUndead || !skillDamageInfo[sk].undeadOnly)) {
- let resist = this.monsterResist(unit, skillDamageInfo[sk].type);
- let pierce = GameData.myReference.getStat(this.pierceMap[skillDamageInfo[sk].type]);
-
- if (this.convictionEligible[skillDamageInfo[sk].type]) {
- resist -= (resist >= 100 ? conviction / 5 : conviction);
- }
-
- if (resist < 100) {
- resist = Math.max(-100, resist - pierce);
- } else {
- resist = 100;
- }
-
- totalDmg += sk !== sdk.skills.StaticField
- && 0
- || avgDmg * (100 - resist) / 100;
-
- }
-
- let tmpEffort = Math.ceil(hp / totalDmg);
-
- tmpEffort /= this.dmgModifier(sk | 0, parent || unit);
-
- // care for mana
- if (GameData.myReference.mp < Skill.getManaCost([sk])) {
- tmpEffort *= 5; // More effort in a skill we dont have mana for
- }
-
- // check valid location
- if (sk === sdk.skills.Blizzard && !getCollision(unit.area, unit.x, unit.y & !Coords_1.BlockBits.IsOnFloor)) {
- tmpEffort *= 5;
- }
-
- // Use less cool down spells, if something better is around
- /*if (this.skillCooldown(sk | 0)) {
- console.log("tmpEffort: " + (Math.ceil(tmpEffort)) + " eretEffor: " + eret.effort);
- tmpEffort *= 5;
- }*/
- if (tmpEffort <= eret.effort) {
- eret.effort = tmpEffort;
- eret.skill = sk | 0;
- eret.type = skillDamageInfo[eret.skill].type;
- eret.name = getSkillById(eret.skill);
- eret.cooldown = this.skillCooldown(sk | 0);
- if (all) {
- allData.unshift(Misc.copy(eret));
- }
- }
- }
- }
- if (all && allData.length) return allData;
- if (eret.skill >= 0) return eret;
- return null;
- },
- effectiveMonsterEffort: function (unit, areaID) {
- if (unit === undefined) return null;
- areaID === undefined && (areaID = me.area);
- const allData = [];
- const buffDamageInfo = {};
- const newSkillDamageInfo = {};
- let buffDmg = [];
- let eret = { effort: Infinity, skill: -1, type: "Physical" };
- let hp = this.monsterMaxHP(typeof unit === "number" ? unit : unit.classid, areaID);
- let conviction = this.getConviction(), ampDmg = this.getAmp();
- let isUndead = (typeof unit === "number" ? MonsterData[unit].Undead : MonsterData[unit.classid].Undead);
- let skillDamageInfo = this.allSkillDamage(unit);
-
- for (let sk in skillDamageInfo) {
- if (this.buffs[sk]) {
- if (typeof unit === "number") {
- buffDmg[this.buffs[sk]] = 0;
- buffDamageInfo[sk] = skillDamageInfo[sk];
- }
- } else {
- newSkillDamageInfo[sk] = skillDamageInfo[sk];
- }
- }
-
- skillDamageInfo = newSkillDamageInfo;
-
- for (let sk in buffDamageInfo) {
- // static field has a fix'd ceiling, calculated already
- if ([sdk.skills.StaticField].indexOf(sk) !== -1) continue;
-
- let avgPDmg = (buffDamageInfo[sk].pmin + buffDamageInfo[sk].pmax) / 2;
- let avgDmg = (buffDamageInfo[sk].min + buffDamageInfo[sk].max) / 2;
- let tmpDmg = 0;
-
- if (avgPDmg > 0) {
- let presist = this.monsterResist(unit, "Physical");
-
- presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
- presist = Math.max(-100, Math.min(100, presist));
- tmpDmg += avgPDmg * (100 - presist) / 100;
- }
-
- if (avgDmg > 0 && (!isUndead || !buffDamageInfo[sk].undeadOnly) && sk !== sdk.skills.StaticField) {
- let resist = this.monsterResist(unit, buffDamageInfo[sk].type);
- let pierce = GameData.myReference.getStat(this.pierceMap[buffDamageInfo[sk].type]);
-
- if (this.convictionEligible[buffDamageInfo[sk].type]) {
- resist -= (resist >= 100 ? conviction / 5 : conviction);
- }
-
- if (resist < 100) {
- resist = Math.max(-100, resist - pierce);
- } else {
- resist = 100;
- }
-
- tmpDmg += avgDmg * (100 - resist) / 100;
- }
-
- if (this.buffs[sk] === 1) {
- buffDmg[this.buffs[sk]] += tmpDmg;
- } else {
- buffDmg[this.buffs[sk]] = Math.max(buffDmg[this.buffs[sk]], tmpDmg);
- }
- }
-
- buffDmg = buffDmg.reduce((t, v) => t + v, 0);
-
- for (let sk in skillDamageInfo) {
- if (!this.ignoreSkill[sk]) {
- let avgPDmg = (skillDamageInfo[sk].pmin + skillDamageInfo[sk].pmax) / 2, totalDmg = buffDmg;
- let avgDmg = (skillDamageInfo[sk].min + skillDamageInfo[sk].max) / 2;
-
- if (avgPDmg > 0 && sk !== sdk.skills.StaticField) {
- let presist = this.monsterResist(unit, "Physical");
-
- presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
- presist = Math.max(-100, Math.min(100, presist));
- totalDmg += avgPDmg * (100 - presist) / 100;
- }
-
- if (avgDmg > 0 && (!isUndead || !skillDamageInfo[sk].undeadOnly)) {
- let resist = this.monsterResist(unit, skillDamageInfo[sk].type);
- let pierce = GameData.myReference.getStat(this.pierceMap[skillDamageInfo[sk].type]);
-
- if (this.convictionEligible[skillDamageInfo[sk].type]) {
- resist -= (resist >= 100 ? conviction / 5 : conviction);
- }
-
- if (resist < 100) {
- resist = Math.max(-100, resist - pierce);
- } else {
- resist = 100;
- }
-
- totalDmg += sk !== sdk.skills.StaticField && 0 || avgDmg * (100 - resist) / 100;
-
- }
-
- let tmpEffort = Math.ceil(hp / totalDmg);
-
- tmpEffort /= this.dmgModifier(sk | 0, unit);
-
- // care for mana
- if (GameData.myReference.mp < Skill.getManaCost([sk])) {
- tmpEffort *= 5; // More effort in a skill we dont have mana for
- }
-
- // check valid location, blizzard and meteor fail over lava
- if ([sdk.skills.Blizzard, sdk.skills.Meteor].indexOf(sk) && !Attack.validSpot(unit.x, unit.y, sk, unit.classid)) {
- tmpEffort *= 5;
- }
-
- if (tmpEffort <= eret.effort) {
- eret.effort = tmpEffort;
- eret.skill = sk | 0;
- eret.type = skillDamageInfo[eret.skill].type;
- eret.name = getSkillById(eret.skill);
- eret.cooldown = this.skillCooldown(sk | 0);
- allData.unshift(Misc.copy(eret));
- }
- }
- }
- if (allData.length) return allData;
- if (eret.skill >= 0) return eret;
- return null;
- },
- areaEffort: function (areaID, skills) {
- let effortpool = 0, raritypool = 0, dmgAcc = 0;
-
- skills = skills || this.allSkillDamage();
-
- AreaData[areaID].forEachMonsterAndMinion((mon, rarity, parent) => {
- effortpool += rarity * this.monsterEffort(mon.Index, areaID, skills, parent && parent.Index).effort;
- raritypool += rarity;
-
- dmgAcc += rarity * this.monsterAvgDmg(mon.Index, areaID);
- });
-
- // console.debug('avg dmg '+ AreaData[areaID].LocaleString+' -- ' + dmgAcc+' -- ' + avgDmg);
-
- return (raritypool ? effortpool / raritypool : Infinity);
- },
- areaSoloExp: function (areaID, skills) {
- let procentageBroke = ((100 - Math.min(100, Math.max(0, (100 / (Config.LowGold || 1) * me.gold)))));
- let brokeness = 1 + (procentageBroke / 100 / 3 * 1);
- let effortpool = 0, raritypool = 0, dmgAcc = 0;
-
- skills = skills || this.allSkillDamage();
- AreaData[areaID].forEachMonsterAndMinion((mon, rarity, parent) => {
- effortpool += rarity * this.monsterExp(mon.Index, areaID) * this.levelModifier(GameData.myReference.charlvl, this.monsterLevel(mon.Index, areaID)) / this.monsterEffort(mon.Index, areaID, skills, parent && parent.Index).effort;
- raritypool += rarity;
-
- dmgAcc += (rarity * this.monsterAvgDmg(mon.Index, areaID));
- });
-
- let log = 1, avgDmg = 0;
- if (brokeness !== 1) {
- log = ((5 - Math.log(areaID)) * (brokeness * 0.6));
- avgDmg = (raritypool ? dmgAcc / raritypool : Infinity) * log;
- }
-
- return (raritypool ? effortpool / raritypool : 0) - (avgDmg);
- },
- mostUsedSkills: function (force = false) {
- if (!force && GameData.myReference.hasOwnProperty("__cachedMostUsedSkills") && GameData.myReference.__cachedMostUsedSkills) return GameData.myReference.__cachedMostUsedSkills;
-
- const effort = [], uniqueSkills = [];
- for (let i = 50; i < 120; i++) {
- try {
- effort.push(GameData.monsterEffort(i, sdk.areas.ThroneOfDestruction));
- } catch (e) {
- /*dontcare*/
- }
- }
-
- effort
- .filter(e => e !== null && typeof e === "object" && e.hasOwnProperty("skill"))
- .filter(x => GameData.myReference.getSkill(x.skill, 0)) // Only skills where we have hard points in
- .filter(x => Skills.class[x.skill] < 7) // Needs to be a skill of a class, not my class but a class
- .map(x =>
- // Search for this unique skill
- (
- uniqueSkills.find(u => u.skillId === x.skill)
- // Or add it and return the value
- || (
- (
- uniqueSkills.push({skillId: x.skill, used: 0})
- && false
- )
- || uniqueSkills[uniqueSkills.length - 1]
- )
- ).used++ && false
- // In the end always return x
- || x
- );
-
- return (GameData.myReference.__cachedMostUsedSkills = uniqueSkills.sort((a, b) => b.used - a.used));
- },
-
- attackStartingFrame: function (weaponClass, charClass = GameData.myReference.classid) {
- // amazon and sorceress only
- /*
- Weapon: hth 1hs 2hs 1ht 2ht stf bow xbw
- StartingFrame: 1 2 2 2 2 2 0 0
- */
- if (charClass === sdk.player.class.Amazon || charClass === sdk.player.class.Sorceress) {
- if (weaponClass === "hth") return 1;
- if (["1hs", "2hs", "1ht", "2ht", "stf"].includes(weaponClass)) return 2;
- }
- return 0;
- },
-
- /*weaponSpeedModifier: function (weapon1Code, charClass = GameData.myReference.classid, weapon2Code = null) {
- let weapons = new CSV("sdk/weapons.txt");
- let weapon1Data = weapons.findObject("code", weapon1Code);
- if (!weapon2Code) {
- return weapon1Data.speed;
- }
- let weapon2Data = weapons.findObject("code", weapon2Code);
- if (!weapon2Data) {
- return weapon1Data.speed;
- }
- return (weapon1Data.speed + weapon2Data.speed) / 2;
- },*/
-
- attackModeForSkill: function (skillId, charClass = GameData.myReference.classid) {
- //TODO:
- if (skillId === sdk.skills.Smite) return "S1";
- /*
- A1:
- normal attack or attack skills like
- "bow and crossbow" skills, energy strike, chain lightning strike, charged strike,
- opposing tiger strike, cobra strike, phoenix strike
- Slash, paralyze, concentrate, amok
- barbarian rage,
- mangle , fire claws , anger poison dagger
- victim, zeal, revenge, conversion
-
- A2:
- normal attack
-
- KK: kick (kick barrel) or assassin skills dragon claw, dragon tail
-
- S1: skill 1
- (evade, avoid, escape)
- shield attack smite
-
- S2: skill 2
- stationary traps, fire blast , Shock net, blade guard
-
- S3: skill 3
- Secondary blow of the barbarian with dual weapons
- Hunger, rabies
-
- S4:
- Secondary blow of the assassin with dual claws
- Secondary throw of the barbarian dual throwing
-
- TH:
- Throw
- poison throwing spear, lightning strike, plague throwing spit, flashing mischief
- */
- return "A1";
- },
-
- weaponAttackAnimationSpeed: function (baseRate, skill, weaponClass, charClass = GameData.myReference.classid, shiftState = null) {
- /*if (shiftState == "bear") {
- let framesPerDirection = this.weaponFramesPerDirection(skill, weaponClass, charClass);
- let baseSpeed = this.weaponAttackAnimationSpeed(baseRate, skill, weaponClass, charClass);
- let weaponIAS = 0;
- let weaponSpeedModifier = 0;
- let delay = baseRate * framesPerDirection / ((256 + weaponIAS - weaponSpeedModifier) * baseSpeed / 100);
- return baseRate*
- }*/
- //TODO: vampire form or werewolf
- let attackMode = this.attackModeForSkill(skill, charClass);
- switch (true) {
- case charClass === sdk.player.class.Assassin && attackMode.startsWith("A") && weaponClass.startsWith("ht") && weaponClass !== "hth":
- return 208;
- case charClass === sdk.player.class.Assassin && attackMode === "S2":
- return 128;
- case charClass === sdk.player.class.Assassin && attackMode === "S4" && weaponClass === "ht2":
- return 208;
- }
- // wolf or bear :
- //AnimationSpeed = [Hitshift * NeutralFrames / Delay]
- return 256;
- },
-
- weaponFramesPerDirection: function (skill, weaponClass, charClass = GameData.myReference.classid) {
- let attackMode = this.attackModeForSkill(skill, charClass);
- /*
- 2HT = “2 Hand Thrust” Spear
- STF = “Staff” Staff, Large Axe, Maul, Pole arm
- 2HS = “2 Hand Swing” 2-Handed Sword
- BOW = “Bow” Bow
- XBW = “Crossbow” Crossbow
- HT1 = “One Hand-to-Hand” Shield + Claws
- HT2 = “”Two Hand-to-Hand” Claws + Claws
- 1HT = “1 Hand Thrust” Shield + (Throwing potion, Knife, Throwing Knife, Javelin)
- 1HS = “1 Hand Swing” Shield + (Axe, Wand, Club, Scepter, Mace, Hammer, Sword, Throwing Axe, Orb)
- HTH = “Hand To Hand” Shield + no weapon
- 1SS = “Left Swing Right Swing” Left = 1HS, Right = 1HS
- 1JT = “Left Jab Right Thrust” Left = 1HT, Right = 1HT
- 1ST = “Left Swing Right Thrust” Left = 1HS, Right = 1HT
- 1JS = “Left Jab Right Swing” Left = 1HT, Right = 1HS
- */
-
- /*
- Amazon Assassin Barbarian Druid Necromancer Paladin Sorceress
- A1 HTH 08 13 256 06 11 256 06 12 256 08 16 256 08 15 256 07 14 256 09 16 256
- A2 HTH --- 06 12 256 --- --- --- --- 08 16 256
- A1 HTx 06 11 208
- A2 HTx 06 12 208
- A1 1HS 10 16 256 07 15 256 07 16 256 09 19 256 09 19 256 07 15 256 12 20 256
- A1 2HS 12 20 256 11 23 256 08 18 256 10 21 256 11 23 256 08 18 256 14 24 256
- A2 2HS --- --- --- --- --- 08 19 256 ---
- A1 1HT 09 15 256 07 15 256 07 16 256 08 19 256 09 19 256 08 17 256 11 19 256
- A1 2HT 11 18 256 10 23 256 09 19 256 09 23 256 10 24 256 08 20 256 13 23 256
- A2 2HT --- --- --- --- --- 09 20 256 ---
- A1 STF 12 20 256 09 19 256 09 19 256 09 17 256 11 20 256 09 18 256 11 18 256
- A1 BOW 06 14 256 07 16 256 07 15 256 08 16 256 09 18 256 08 16 256 09 17 256
- A1 XBW 09 20 256 10 21 256 10 20 256 10 20 256 11 20 256 10 20 256 11 20 256
-
- TH xxx 09 16 256 07 16 256 08 16 256 08 18 256 10 20 256 08 16 256 10 20 256
- KK xxx 04 13 256
-
- S1 xxx xx 09 256 07 12 256
- S2 xxx 04 08 128
- S3 1Jx 08 12 256
- S3 1Sx 07 12 256
- S4 1Jx 08 16 256
- S4 1Sx 09 16 256
- S4 HT2 06 12 208
-
- -------------------------------------------------- ------------------------------------------------
- Werewolf bear fetish vampire
- A1 xxx 07 13 xxx 07 12 xxx 08 12 256 09 14 176
- S3 xxx 06 10 xxx 06 10 xxx
- NU xxx xx 09 xxx xx 10 xxx
-
- -------------------------------------------------- ------------------------------------------------
- Rogue City Guard Eisenwolf Barbarian Mercenary
- A1 xxx 06 15 256 11 16 256 06 15 256 05/12 16 256
- */
- switch (true) {
- case charClass === sdk.player.class.Sorceress && attackMode.startsWith("A") && weaponClass === "hth":
- return 16;
- case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "1hs":
- return 20;
- case charClass === sdk.player.class.Sorceress && attackMode.startsWith("A") && weaponClass === "2hs":
- return 24;
- case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "1ht":
- return 19;
- case charClass === sdk.player.class.Sorceress && attackMode.startsWith("A") && weaponClass === "2ht":
- return 23;
- case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "stf":
- return 18;
- case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "bow":
- return 17;
- case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "xbw":
- return 20;
- case charClass === sdk.player.class.Sorceress && attackMode === "TH":
- return 20;
-
- case charClass === sdk.player.class.Paladin && attackMode.startsWith("A") && weaponClass === "hth":
- return 14;
- case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "1hs":
- return 15;
- case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "2hs":
- return 18;
- case charClass === sdk.player.class.Paladin && attackMode === "A2" && weaponClass === "2hs":
- return 19;
- case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "1ht":
- return 17;
- case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "2ht":
- return 20;
- case charClass === sdk.player.class.Paladin && attackMode === "A2" && weaponClass === "2ht":
- return 20;
- case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "stf":
- return 18;
- case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "bow":
- return 16;
- case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "xbw":
- return 20;
- case charClass === sdk.player.class.Paladin && attackMode === "TH":
- return 16;
- case charClass === sdk.player.class.Paladin && attackMode === "S1":
- return 12;
-
- //TODO: full trag oul set
- case charClass === sdk.player.class.Necromancer && attackMode.startsWith("A") && weaponClass === "hth":
- return 15;
- case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "1hs":
- return 19;
- case charClass === sdk.player.class.Necromancer && attackMode.startsWith("A") && weaponClass === "2hs":
- return 23;
- case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "1ht":
- return 19;
- case charClass === sdk.player.class.Necromancer && attackMode.startsWith("A") && weaponClass === "2ht":
- return 24;
- case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "stf":
- return 20;
- case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "bow":
- return 18;
- case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "xbw":
- return 20;
- case charClass === sdk.player.class.Necromancer && attackMode === "TH":
- return 20;
-
- case this.shiftState() === "wolf" && attackMode === "A1":
- return 13;
- case this.shiftState() === "wolf" && attackMode === "S3":
- return 10;
-
- case this.shiftState() === "bear" && attackMode === "A1":
- return 12;
- case this.shiftState() === "bear" && attackMode === "S3":
- return 10;
-
- case charClass === sdk.player.class.Druid && attackMode.startsWith("A") && weaponClass === "hth":
- return 16;
- case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "1hs":
- return 19;
- case charClass === sdk.player.class.Druid && attackMode.startsWith("A") && weaponClass === "2hs":
- return 21;
- case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "1ht":
- return 19;
- case charClass === sdk.player.class.Druid && attackMode.startsWith("A") && weaponClass === "2ht":
- return 23;
- case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "stf":
- return 17;
- case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "bow":
- return 16;
- case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "xbw":
- return 20;
- case charClass === sdk.player.class.Druid && attackMode === "TH":
- return 18;
-
- case charClass === sdk.player.class.Barbarian && attackMode.startsWith("A") && weaponClass === "hth":
- return 12;
- case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "1hs":
- return 16;
- case charClass === sdk.player.class.Barbarian && attackMode.startsWith("A") && weaponClass === "2hs":
- return 18;
- case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "1ht":
- return 16;
- case charClass === sdk.player.class.Barbarian && attackMode.startsWith("A") && weaponClass === "2ht":
- return 19;
- case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "stf":
- return 19;
- case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "bow":
- return 15;
- case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "xbw":
- return 20;
- case charClass === sdk.player.class.Barbarian && attackMode === "TH":
- return 16;
- case charClass === sdk.player.class.Barbarian && attackMode === "S3":
- return 12;
- case charClass === sdk.player.class.Barbarian && attackMode === "S4":
- return 16;
-
- case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass.startsWith("ht"):
- return 11;
- case charClass === sdk.player.class.Assassin && attackMode === "A2" && weaponClass.startsWith("ht"):
- return 12;
- case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "1hs":
- return 15;
- case charClass === sdk.player.class.Assassin && attackMode.startsWith("A") && weaponClass === "2hs":
- return 23;
- case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "1ht":
- return 15;
- case charClass === sdk.player.class.Assassin && attackMode.startsWith("A") && weaponClass === "2ht":
- return 23;
- case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "stf":
- return 19;
- case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "bow":
- return 16;
- case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "xbw":
- return 21;
- case charClass === sdk.player.class.Assassin && attackMode === "TH":
- return 16;
- case charClass === sdk.player.class.Assassin && attackMode === "KK":
- return 13;
- case charClass === sdk.player.class.Assassin && attackMode === "S2":
- return 8;
- case charClass === sdk.player.class.Assassin && attackMode === "S4" && weaponClass === "ht2":
- return 12;
-
- case charClass === sdk.player.class.Amazon && attackMode.startsWith("A") && weaponClass === "hth":
- return 13;
- case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "1hs":
- return 16;
- case charClass === sdk.player.class.Amazon && attackMode.startsWith("A") && weaponClass === "2hs":
- return 20;
- case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "1ht":
- return 15;
- case charClass === sdk.player.class.Amazon && attackMode.startsWith("A") && weaponClass === "2ht":
- return 18;
- case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "stf":
- return 20;
- case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "bow":
- return 14;
- case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "xbw":
- return 20;
- case charClass === sdk.player.class.Amazon && attackMode === "TH":
- return 16;
- case charClass === sdk.player.class.Amazon && attackMode === "S1":
- return 9;
- }
- return -1;
- },
-
- // attackFrames: function (skillId, weaponCode, ias = GameData.myReference.getStat(sdk.stats.Fasterattackrate), charClass = GameData.myReference.classid, weapon2Code = null) {
- // // https://diablo3.ingame.de/forum/threads/1218516-FAQ-Bewegungs-und-Animationsgeschwindigkeiten-Teil-2?s=&postid=17610874
- // /*
- // TODO
- // bear or wolf only :
-
- // frames = {(256 * framesPerDirection) / [animationSpeed * (100 + effectiveIAS + skillsIAS - weaponSpeedModifier + coldEffect) / 100]} - 1
-
- // where :
- // animationSpeed = 256 * NeutralFrames / delay
- // delay = 256 * CharFrames / ((100 + weaponIAS - weaponSpeedModifier) * CharSpeed / 100)
-
- // framesPerDirection ... The sum of all frames of our attack animation of the Werform.
- // NeutralFrames ... Sum of the frames of our neutral animation (use while standing).
- // CharFrames ... The sum of all frames of the attack animation that we would use in the unchanged state (with the exception of two-handed swords).
- // CharSpeed ... This is the animation speed of the attack animation that the unchanged character would use.
- // weaponIAS ... All IAS on our weapon or weapon base
- // */
- // let weaponData = (new CSV("sdk/weapons.txt")).findObject("code", weaponCode);
- // if (!weaponData) {
- // console.log(sdk.colors.Orange + "No weapon data found for code " + weaponCode);
- // }
- // let weaponClass = weaponData.wclass;
- // let baseRate = 100;
- // const BASE_ANIMATION_SPEED = 256;
-
- // let animationSpeed = this.weaponAttackAnimationSpeed(baseRate, weaponClass, charClass, this.shiftState());
- // let effectiveIAS = 120 * ias / (120 + ias);
- // let skillsIAS = 0; //TODO: fanaticism or other sills bonus + slowdown skills malus
- // let weaponSpeedModifier = (typeof weaponData.speed == "string") ? isNaN(parseInt(weaponData.speed)) ? 0 : parseInt(weaponData.speed) : weaponData.speed;// this.weaponSpeedModifier(weaponCode, charClass, weapon2Code);
- // // me.getState(sdk.states.Frozen) or me.getState(sdk.states.Cold) ?
- // let coldEffect = GameData.myReference.getState(sdk.states.Frozen) ? -50 : 0; // If we are affected by cold, as a player we receive a penalty of 50.
- // let acceleration = baseRate + effectiveIAS + skillsIAS - weaponSpeedModifier + coldEffect;
- // acceleration = Math.min(175, Math.max(15, acceleration));
- // let startingFrame = this.attackStartingFrame(weaponClass, charClass);
- // let framesPerDirection = this.weaponFramesPerDirection(skillId, weaponClass, charClass);
- // if (framesPerDirection < 1) {
- // console.log(sdk.colors.Orange + "wrong value for framesPerDirection, IAS calculation may be wrong");
- // }
-
- // console.log("skillId " + skillId);
- // console.log("charClass " + charClass);
- // console.log("weaponCode " + weaponCode);
- // console.log("weaponClass " + weaponClass);
- // console.log("ias " + ias);
- // console.log("effectiveIAS " + effectiveIAS);
- // console.log("skillsIAS " + skillsIAS);
- // console.log("weaponSpeedModifier " + weaponSpeedModifier);
- // console.log("coldEffect " + coldEffect);
- // console.log("acceleration " + acceleration);
- // console.log("startingFrame " + startingFrame);
- // console.log("framesPerDirection " + framesPerDirection);
- // let frames = Math.ceil(BASE_ANIMATION_SPEED * (framesPerDirection - startingFrame) / Math.floor(animationSpeed * acceleration / 100)) - 1;
- // return frames;
- // },
-
- // attackDuration: function (skillId, weaponCode, ias = GameData.myReference.getStat(sdk.stats.Fasterattackrate), charClass = GameData.myReference.classid, weapon2Code = null) {
- // // https://diablo3.ingame.de/forum/threads/1218516-FAQ-Bewegungs-und-Animationsgeschwindigkeiten-Teil-2?s=&postid=17610874
- // return this.attackFrames(skillId, weaponCode, ias, charClass) / 25;
- // },
- timeTillMissleImpact: function (skillId, monster) {
- if (monster === undefined || skillId === undefined || !monster.attackable) return 0;
- let missileName = getBaseStat("skills", skillId, "cltmissile");
- let missile = MissileData[missileName];
- if (!missile) {
- missileName = getBaseStat("skills", skillId, "srvmissile");
- missile = MissileData[missileName];
- }
- if (missile && missile.velocity > 0) {
- const missileVelocityTPS = missile.velocity;
- const missileVelocityTPF = missileVelocityTPS / 25;
- const distanceForMissile = getDistance(me, monster);
- // too far for missile to reach this position
- if (distanceForMissile > missile.range) return 0;
- const castTimeS = me.castingDuration(skillId);
- return ((distanceForMissile / ((missileVelocityTPS / 32) * 25)) + castTimeS);
- }
- return 0;
- }
- };
-
- function calculateKillableFallensByFrostNova() {
- if (!Skill.canUse(sdk.skills.FrostNova)) return 0;
- let fallens = [sdk.monsters.Fallen, sdk.monsters.Carver2, sdk.monsters.Devilkin2, sdk.monsters.DarkOne1, sdk.monsters.WarpedFallen, sdk.monsters.Carver1, sdk.monsters.Devilkin, sdk.monsters.DarkOne2];
- let area = me.area;
- return getUnits(sdk.unittype.Monster)
- .filter(unit => !!unit && fallens.includes(unit.classid) && unit.distance < 7)
- .filter(function (unit) {
- return unit.attackable
- && typeof unit.x === "number" // happens if monster despawns
- && !checkCollision(me, unit, Coords_1.Collision.BLOCK_MISSILE)
- && unit.getStat(sdk.stats.ColdResist) < 100;
- //&& !unit.getState(sdk.states.Frozen);
- })
- .reduce(function (acc, cur) {
- let classId = cur.classid, minDmg = GameData.skillDamage(sdk.skills.FrostNova, cur).min;
- //let charLvl = GameData.monsterLevel(classId, area);
- let currentHealth = GameData.monsterMaxHP(classId, area, cur.charlvl - GameData.monsterLevel(classId, area)) / 100 * (cur.hp * 100 / cur.hpmax);
- if (currentHealth < minDmg) {
- acc++;
- }
- return acc;
- }, 0);
- }
-
- function calculateKillableSummonsByNova() {
- if (!Skill.canUse(sdk.skills.Nova)) return 0;
- let summons = [
- sdk.monsters.Fallen, sdk.monsters.Carver2, sdk.monsters.Devilkin2, sdk.monsters.DarkOne1, sdk.monsters.WarpedFallen, sdk.monsters.Carver1, sdk.monsters.Devilkin, sdk.monsters.DarkOne2,
- sdk.monsters.BurningDead, sdk.monsters.Returned1, sdk.monsters.Returned2, sdk.monsters.BoneWarrior1, sdk.monsters.BoneWarrior2
- ];
- return getUnits(sdk.unittype.Monster)
- .filter(unit => !!unit && summons.includes(unit.classid) && unit.distance < 7)
- .filter(function (unit) {
- return unit.attackable
- && typeof unit.x === "number" // happens if monster despawns
- && !checkCollision(me, unit, Coords_1.Collision.BLOCK_MISSILE)
- && Attack.checkResist(unit, "lightning");
- })
- .reduce(function (acc, cur) {
- let classId = cur.classid, areaId = cur.area, minDmg = GameData.skillDamage(sdk.skills.Nova, cur).min;
- let currentHealth = GameData.monsterMaxHP(classId, areaId, cur.charlvl - GameData.monsterLevel(classId, areaId)) / 100 * (cur.hp * 100 / cur.hpmax);
- if (currentHealth < minDmg) {
- acc++;
- }
- return acc;
- }, 0);
- }
-
- Object.defineProperty(Unit.prototype, "currentVelocity", {
- get: function () {
- if (!this.isMoving || this.isFrozen) return 0;
- const velocity = this.isRunning ? MonsterData[this.classid].Run : MonsterData[this.classid].Velocity;
- if (this.isChilled) {
- let malus = MonsterData[this.classid].ColdEffect;
- (malus > 0) && (malus = malus - 256);
- return Math.max(1, ~~(velocity * (1 + malus)));
- }
- return velocity;
- }
- });
-
- function targetPointForSkill (skillId, monster) {
- if (monster === undefined || skillId === undefined || !monster.attackable) return null;
- let missileName = getBaseStat("skills", skillId, "cltmissile");
- let missile = MissileData[missileName];
- if (!missile) {
- missileName = getBaseStat("skills", skillId, "srvmissile");
- missile = MissileData[missileName];
- }
- if (missile && missile.velocity > 0) {
- if (monster.isMoving && (monster.targetx !== me.x || monster.targety !== me.y)) {
- let startX = monster.x, startY = monster.y;
- // tiles per second velocities
- // ToDo: is monster slowed by freeze or something ?
- let monsterVelocityTPS = monster.currentVelocity;
- let missileVelocityTPS = missile.velocity;
- // tiles per frame velocities
- let monsterVelocityTPF = monsterVelocityTPS / 25;
- let missileVelocityTPF = missileVelocityTPS / 25;
- //console.log("monster is moving to "+monster.targetx+", "+monster.targety + " at speed "+monsterVelocity);
- let path = getPath(monster.area, startX, startY, monster.targetx, monster.targety, 2, 1);
- if (path && path.length) {
- // path is reversed from target to monster, we will check from last path position (target) to monster position
- path.reverse();
- let [diffS, diffF, found] = [0, 0, 0];
- let time = { missile: {}, monster: {} };
- for (let i = 0; i < path.length; i++) {
- let pos = path[i];
- // ToDo : does missile spawn at me position ?
- let distanceForMissile = getDistance(me, pos);
- if (distanceForMissile > missile.range) {
- // too far for missile to reach this position
- continue;
- }
- let distanceForMonster = getDistance({ x: startX, y: startY }, pos);
- let timeForMonsterF = distanceForMonster / monsterVelocityTPF;
- // time in seconds
- // let castTimeS = GameData.castingDuration(skillId);
- // let timeForMissileS = distanceForMissile / missileVelocityTPS + castTimeS;
- // time in frames
- let castTimeF = me.castingFrames(skillId);
- let timeForMissileF = distanceForMissile / missileVelocityTPF + castTimeF;
- // let timeForMonsterS = distanceForMonster / monsterVelocityTPS;
- // Todo: missile and monster size
- // diff seconds
- // diffS = timeForMissileS-timeForMonsterS;
- // diff frames
- diffF = timeForMissileF - timeForMonsterF;
- // diff > 0 : missile will reach pos after monster
- // diff < 0 : missile will reach pos before monster
- // console.log("time for monster to reach "+pos+" = "+timeForMonster);
- // console.log("time for missile to reach "+pos+" = "+timeForMissile);
- // console.log("diff = "+diff)
- if (i === 0 && diffF >= 0) {
- // last path position and missile is late, we can't predict next monster target, shoot at last path position
- // it may fail because monster may be moving at other target while missile is arriving
- // console.log("missile will be too late");
- found = pos;
- // time.missile.seconds = timeForMissileS;
- time.missile.frames = timeForMissileF;
- // time.monster.seconds = timeForMonsterS;
- time.monster.frames = timeForMonsterF;
- break;
- }
- // the number of frames needed for unit to move 1 tile
- let timeToMoveOneTileMonsterF = 1 / monsterVelocityTPF;
- // let timeToMoveOneTileMissileF = 1 / missileVelocityTPF;
- // while missile is travelling, monster will continue to move
- // if the difference is greater than the time a monster will move 1 tile, the missile will miss
- // todo: monster size, missile size
- if (diffF >= -1 * timeToMoveOneTileMonsterF && diffF <= 1 * timeToMoveOneTileMonsterF) {
- found = pos;
- // time.missile.seconds = timeForMissileS;
- time.missile.frames = timeForMissileF;
- // time.monster.seconds = timeForMonsterS;
- time.monster.frames = timeForMonsterF;
- break;
- }
- }
- if (found) {
- // console.log("missile will hit monster in "+time.missile.seconds+" ("+time.missile.frames+") at "+found.x+", "+found.y);
- // console.log("time for monster = "+time.monster.seconds+ " ("+time.monster.frames+")")
- // console.log("diff missile-monster = "+diffS+ " ("+diffF+")");
- return found;
- }
- }
- }
- }
- return null;
- }
-
- // Export data
- GameData.isEnemy = isEnemy;
- GameData.isAlive = isAlive;
- GameData.onGround = onGround;
- GameData.calculateKillableFallensByFrostNova = calculateKillableFallensByFrostNova;
- GameData.calculateKillableSummonsByNova = calculateKillableSummonsByNova;
- GameData.targetPointForSkill = targetPointForSkill;
- module.exports = GameData;
-})(module, require);
diff --git a/libs/SoloPlay/Modules/GameData/AreaData.js b/libs/SoloPlay/Modules/GameData/AreaData.js
new file mode 100644
index 00000000..a5bfa5b8
--- /dev/null
+++ b/libs/SoloPlay/Modules/GameData/AreaData.js
@@ -0,0 +1,1346 @@
+/**
+* @filename AreaData.js
+* @author theBGuy
+* @credits Nishimura-Katsuo (orignal module), kolton (data from pather.js)
+* @desc area data library
+*
+*/
+
+(function (module, require) {
+ const MonsterData = require("./MonsterData");
+ const ShrineData = require("../../../core/GameData/ShrineData");
+ const QuestData = require("../../../core/GameData/QuestData");
+ 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 AREA_INDEX_COUNT = 137;
+
+ /**
+ * @todo Still need to handle exits
+ */
+ const AreaData = (function () {
+ /** @type {Map} */
+ const _map = new Map();
+ /** @type {Map} */
+ const wps = new Map();
+
+ /**
+ * @typedef {Object} AreaInterface
+ * @property {number[]} [previousArea]
+ * @property {number[]} [nextArea]
+ * @property {number[]} [presetMonsters]
+ * @property {number[]} [presetChests]
+ * @property {number[]} [poi]
+ * @property {function(): boolean} [preReq]
+ */
+
+ /** @type {Map} */
+ const _areaData = new Map([
+ // Act 1
+ [sdk.areas.RogueEncampment, {
+ nextArea: [sdk.areas.BloodMoor],
+ preReq: function () {
+ return true;
+ }, // always able to access
+ }],
+ [sdk.areas.BloodMoor, {
+ previousArea: [sdk.areas.RogueEncampment],
+ nextArea: [sdk.areas.ColdPlains, sdk.areas.DenofEvil],
+ presetChests: [sdk.objects.SuperChest],
+ }],
+ [sdk.areas.ColdPlains, {
+ previousArea: [sdk.areas.BloodMoor],
+ nextArea: [sdk.areas.StonyField, sdk.areas.BurialGrounds, sdk.areas.CaveLvl1],
+ }],
+ [sdk.areas.StonyField, {
+ previousArea: [sdk.areas.ColdPlains],
+ nextArea: [sdk.areas.UndergroundPassageLvl1, sdk.areas.Tristram],
+ presetMonsters: [sdk.monsters.preset.Rakanishu],
+ poi: [
+ sdk.objects.StoneAlpha, sdk.objects.StoneBeta,
+ sdk.objects.StoneGamma, sdk.objects.StoneDelta,
+ sdk.objects.StoneLambda, sdk.objects.StoneTheta,
+ sdk.objects.MoldyTome,
+ ],
+ }],
+ [sdk.areas.DarkWood, {
+ previousArea: [sdk.areas.UndergroundPassageLvl1],
+ nextArea: [sdk.areas.BlackMarsh],
+ presetMonsters: [sdk.monsters.preset.TreeheadWoodFist],
+ poi: [sdk.objects.InifussTree],
+ }],
+ [sdk.areas.BlackMarsh, {
+ previousArea: [sdk.areas.DarkWood],
+ nextArea: [sdk.areas.ForgottenTower, sdk.areas.HoleLvl1],
+ }],
+ [sdk.areas.TamoeHighland, {
+ previousArea: [sdk.areas.BlackMarsh],
+ nextArea: [sdk.areas.MonasteryGate, sdk.areas.PitLvl1],
+ }],
+ [sdk.areas.DenofEvil, {
+ previousArea: [sdk.areas.BloodMoor],
+ presetMonsters: [sdk.monsters.preset.Corpsefire],
+ }],
+ [sdk.areas.CaveLvl1, {
+ previousArea: [sdk.areas.ColdPlains],
+ nextArea: [sdk.areas.CaveLvl2],
+ presetMonsters: [sdk.monsters.preset.Coldcrow],
+ }],
+ [sdk.areas.UndergroundPassageLvl1, {
+ previousArea: [sdk.areas.StonyField],
+ nextArea: [sdk.areas.UndergroundPassageLvl2, sdk.areas.DarkWood],
+ }],
+ [sdk.areas.HoleLvl1, {
+ previousArea: [sdk.areas.BlackMarsh],
+ nextArea: [sdk.areas.HoleLvl2],
+ }],
+ [sdk.areas.PitLvl1, {
+ previousArea: [sdk.areas.TamoeHighland],
+ nextArea: [sdk.areas.PitLvl2],
+ }],
+ [sdk.areas.CaveLvl2, {
+ previousArea: [sdk.areas.CaveLvl1],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.UndergroundPassageLvl2, {
+ previousArea: [sdk.areas.UndergroundPassageLvl1],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.HoleLvl2, {
+ previousArea: [sdk.areas.HoleLvl1],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.PitLvl2, {
+ previousArea: [sdk.areas.PitLvl1],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.BurialGrounds, {
+ previousArea: [sdk.areas.ColdPlains],
+ nextArea: [sdk.areas.Crypt, sdk.areas.Mausoleum],
+ presetMonsters: [sdk.monsters.preset.BloodRaven],
+ }],
+ [sdk.areas.Crypt, {
+ previousArea: [sdk.areas.BurialGrounds],
+ presetMonsters: [sdk.monsters.preset.Bonebreak],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.Mausoleum, {
+ previousArea: [sdk.areas.BurialGrounds],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.ForgottenTower, {
+ previousArea: [sdk.areas.BlackMarsh],
+ nextArea: [sdk.areas.TowerCellarLvl1],
+ }],
+ [sdk.areas.TowerCellarLvl1, {
+ previousArea: [sdk.areas.ForgottenTower],
+ nextArea: [sdk.areas.TowerCellarLvl2],
+ }],
+ [sdk.areas.TowerCellarLvl2, {
+ previousArea: [sdk.areas.TowerCellarLvl1],
+ nextArea: [sdk.areas.TowerCellarLvl3],
+ }],
+ [sdk.areas.TowerCellarLvl3, {
+ previousArea: [sdk.areas.TowerCellarLvl2],
+ nextArea: [sdk.areas.TowerCellarLvl4],
+ }],
+ [sdk.areas.TowerCellarLvl4, {
+ previousArea: [sdk.areas.TowerCellarLvl3],
+ nextArea: [sdk.areas.TowerCellarLvl5],
+ }],
+ [sdk.areas.TowerCellarLvl5, {
+ previousArea: [sdk.areas.TowerCellarLvl4],
+ presetMonsters: [sdk.monsters.preset.TheCountess],
+ presetChests: [sdk.objects.SuperChest],
+ }],
+ [sdk.areas.MonasteryGate, {
+ previousArea: [sdk.areas.TamoeHighland],
+ nextArea: [sdk.areas.OuterCloister],
+ }],
+ [sdk.areas.OuterCloister, {
+ previousArea: [sdk.areas.MonasteryGate],
+ nextArea: [sdk.areas.Barracks],
+ }],
+ [sdk.areas.Barracks, {
+ previousArea: [sdk.areas.OuterCloister],
+ nextArea: [sdk.areas.JailLvl1],
+ presetMonsters: [sdk.monsters.preset.TheSmith],
+ poi: [sdk.quest.chest.MalusHolder],
+ }],
+ [sdk.areas.JailLvl1, {
+ previousArea: [sdk.areas.Barracks],
+ nextArea: [sdk.areas.JailLvl2],
+ }],
+ [sdk.areas.JailLvl2, {
+ previousArea: [sdk.areas.JailLvl1],
+ nextArea: [sdk.areas.JailLvl3],
+ presetMonsters: [sdk.monsters.preset.PitspawnFouldog],
+ }],
+ [sdk.areas.JailLvl3, {
+ previousArea: [sdk.areas.JailLvl2],
+ nextArea: [sdk.areas.InnerCloister],
+ }],
+ [sdk.areas.InnerCloister, {
+ previousArea: [sdk.areas.JailLvl3],
+ nextArea: [sdk.areas.Cathedral],
+ }],
+ [sdk.areas.Cathedral, {
+ previousArea: [sdk.areas.InnerCloister],
+ nextArea: [sdk.areas.CatacombsLvl1],
+ presetMonsters: [sdk.monsters.preset.BoneAsh],
+ }],
+ [sdk.areas.CatacombsLvl1, {
+ previousArea: [sdk.areas.Cathedral],
+ nextArea: [sdk.areas.CatacombsLvl2],
+ }],
+ [sdk.areas.CatacombsLvl2, {
+ previousArea: [sdk.areas.CatacombsLvl1],
+ nextArea: [sdk.areas.CatacombsLvl3],
+ }],
+ [sdk.areas.CatacombsLvl3, {
+ previousArea: [sdk.areas.CatacombsLvl2],
+ nextArea: [sdk.areas.CatacombsLvl4],
+ }],
+ [sdk.areas.CatacombsLvl4, {
+ previousArea: [sdk.areas.CatacombsLvl3],
+ presetMonsters: [sdk.monsters.Andariel],
+ }],
+ [sdk.areas.Tristram, {
+ previousArea: [sdk.areas.StonyField],
+ presetMonsters: [sdk.monsters.preset.Griswold],
+ presetChests: [sdk.quest.chest.Wirt],
+ poi: [sdk.quest.chest.CainsJail],
+ preReq: function () {
+ let quest = QuestData.get(sdk.quest.id.TheSearchForCain);
+ // what to do if its in a state of unable to complete but the portal is open because it's someone elses game?
+ return quest.complete() || quest.checkState(4, true);
+ },
+ }],
+ [sdk.areas.MooMooFarm, {
+ previousArea: [sdk.areas.RogueEncampment],
+ presetMonsters: [sdk.monsters.preset.TheCowKing],
+ }],
+ // Act 2
+ [sdk.areas.LutGholein, {
+ nextArea: [sdk.areas.A2SewersLvl1, sdk.areas.RockyWaste, sdk.areas.HaremLvl1],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.AbleToGotoActII).complete();
+ },
+ }],
+ [sdk.areas.A2SewersLvl1, {
+ previousArea: [sdk.areas.LutGholein],
+ nextArea: [sdk.areas.A2SewersLvl2],
+ }],
+ [sdk.areas.A2SewersLvl2, {
+ previousArea: [sdk.areas.A2SewersLvl1],
+ nextArea: [sdk.areas.A2SewersLvl3],
+ }],
+ [sdk.areas.A2SewersLvl3, {
+ previousArea: [sdk.areas.A2SewersLvl2],
+ presetMonsters: [sdk.monsters.preset.Radament],
+ presetChests: [sdk.quest.chest.HoradricScrollChest],
+ }],
+ [sdk.areas.RockyWaste, {
+ previousArea: [sdk.areas.LutGholein],
+ nextArea: [sdk.areas.DryHills, sdk.areas.StonyTombLvl1],
+ }],
+ [sdk.areas.DryHills, {
+ previousArea: [sdk.areas.RockyWaste],
+ nextArea: [sdk.areas.FarOasis, sdk.areas.HallsoftheDeadLvl1],
+ }],
+ [sdk.areas.FarOasis, {
+ previousArea: [sdk.areas.DryHills],
+ nextArea: [sdk.areas.LostCity, sdk.areas.MaggotLairLvl1],
+ presetMonsters: [sdk.monsters.preset.Beetleburst],
+ }],
+ [sdk.areas.LostCity, {
+ previousArea: [sdk.areas.FarOasis],
+ nextArea: [sdk.areas.ValleyofSnakes, sdk.areas.AncientTunnels],
+ presetMonsters: [sdk.monsters.preset.DarkElder],
+ presetChests: [sdk.objects.SuperChest],
+ }],
+ [sdk.areas.ValleyofSnakes, {
+ previousArea: [sdk.areas.LostCity],
+ nextArea: [sdk.areas.ClawViperTempleLvl1],
+ }],
+ [sdk.areas.ClawViperTempleLvl1, {
+ previousArea: [sdk.areas.ValleyofSnakes],
+ nextArea: [sdk.areas.ClawViperTempleLvl2],
+ }],
+ [sdk.areas.ClawViperTempleLvl2, {
+ previousArea: [sdk.areas.ClawViperTempleLvl1],
+ presetMonsters: [sdk.monsters.preset.Fangskin],
+ presetChests: [sdk.quest.chest.ViperAmuletChest],
+ }],
+ [sdk.areas.StonyTombLvl1, {
+ previousArea: [sdk.areas.RockyWaste],
+ nextArea: [sdk.areas.StonyTombLvl2],
+ }],
+ [sdk.areas.StonyTombLvl2, {
+ previousArea: [sdk.areas.StonyTombLvl1],
+ presetMonsters: [sdk.monsters.preset.CreepingFeature],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.HallsoftheDeadLvl1, {
+ previousArea: [sdk.areas.DryHills],
+ nextArea: [sdk.areas.HallsoftheDeadLvl2],
+ }],
+ [sdk.areas.HallsoftheDeadLvl2, {
+ previousArea: [sdk.areas.HallsoftheDeadLvl1],
+ nextArea: [sdk.areas.HallsoftheDeadLvl3],
+ }],
+ [sdk.areas.HallsoftheDeadLvl3, {
+ previousArea: [sdk.areas.HallsoftheDeadLvl2],
+ presetMonsters: [sdk.monsters.preset.BloodwitchtheWild],
+ presetChests: [sdk.quest.chest.HoradricCubeChest],
+ }],
+ [sdk.areas.MaggotLairLvl1, {
+ previousArea: [sdk.areas.FarOasis],
+ nextArea: [sdk.areas.MaggotLairLvl2],
+ }],
+ [sdk.areas.MaggotLairLvl2, {
+ previousArea: [sdk.areas.MaggotLairLvl1],
+ nextArea: [sdk.areas.MaggotLairLvl3],
+ }],
+ [sdk.areas.MaggotLairLvl3, {
+ previousArea: [sdk.areas.MaggotLairLvl2],
+ presetMonsters: [sdk.monsters.preset.ColdwormtheBurrower],
+ presetChests: [sdk.quest.chest.ShaftoftheHoradricStaffChest],
+ }],
+ [sdk.areas.AncientTunnels, {
+ previousArea: [sdk.areas.LostCity],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.HaremLvl1, {
+ previousArea: [sdk.areas.LutGholein],
+ nextArea: [sdk.areas.HaremLvl2],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheTaintedSun).complete();
+ },
+ }],
+ [sdk.areas.HaremLvl2, {
+ previousArea: [sdk.areas.HaremLvl1],
+ nextArea: [sdk.areas.PalaceCellarLvl1],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheTaintedSun).complete();
+ },
+ }],
+ [sdk.areas.PalaceCellarLvl1, {
+ previousArea: [sdk.areas.HaremLvl2],
+ nextArea: [sdk.areas.PalaceCellarLvl2],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheTaintedSun).complete();
+ },
+ }],
+ [sdk.areas.PalaceCellarLvl2, {
+ previousArea: [sdk.areas.PalaceCellarLvl1],
+ nextArea: [sdk.areas.PalaceCellarLvl3],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheTaintedSun).complete();
+ },
+ }],
+ [sdk.areas.PalaceCellarLvl3, {
+ previousArea: [sdk.areas.PalaceCellarLvl2],
+ nextArea: [sdk.areas.ArcaneSanctuary],
+ presetMonsters: [sdk.monsters.preset.FireEye],
+ // poi the portal - add this later
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheTaintedSun).complete();
+ },
+ }],
+ [sdk.areas.ArcaneSanctuary, {
+ previousArea: [sdk.areas.PalaceCellarLvl3],
+ nextArea: [sdk.areas.CanyonofMagic],
+ presetMonsters: [sdk.monsters.preset.TheSummoner],
+ poi: [sdk.quest.chest.Journal],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheTaintedSun).complete();
+ },
+ }],
+ [sdk.areas.CanyonofMagic, {
+ nextArea: [
+ sdk.areas.TalRashasTomb1, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb4,
+ sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7,
+ ],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ [sdk.areas.TalRashasTomb1, {
+ previousArea: [sdk.areas.CanyonofMagic],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ [sdk.areas.TalRashasTomb2, {
+ previousArea: [sdk.areas.CanyonofMagic],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ [sdk.areas.TalRashasTomb3, {
+ previousArea: [sdk.areas.CanyonofMagic],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ [sdk.areas.TalRashasTomb4, {
+ previousArea: [sdk.areas.CanyonofMagic],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ [sdk.areas.TalRashasTomb5, {
+ previousArea: [sdk.areas.CanyonofMagic],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ [sdk.areas.TalRashasTomb6, {
+ previousArea: [sdk.areas.CanyonofMagic],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ [sdk.areas.TalRashasTomb7, {
+ previousArea: [sdk.areas.CanyonofMagic],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ [sdk.areas.DurielsLair, {
+ presetMonsters: [sdk.monsters.Duriel],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheSummoner).complete();
+ },
+ }],
+ // Act 3
+ [sdk.areas.KurastDocktown, {
+ nextArea: [sdk.areas.SpiderForest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.AbleToGotoActIII).complete();
+ },
+ }],
+ [sdk.areas.SpiderForest, {
+ previousArea: [sdk.areas.KurastDocktown],
+ nextArea: [sdk.areas.GreatMarsh],
+ }],
+ [sdk.areas.GreatMarsh, {
+ previousArea: [sdk.areas.SpiderForest],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.SpiderCave, {
+ previousArea: [sdk.areas.SpiderForest],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.SpiderCavern, {
+ previousArea: [sdk.areas.SpiderForest],
+ presetMonsters: [sdk.monsters.preset.SszarktheBurning],
+ presetChests: [sdk.quest.chest.KhalimsEyeChest],
+ }],
+ [sdk.areas.FlayerJungle, {
+ previousArea: [sdk.areas.SpiderForest, sdk.areas.GreatMarsh],
+ nextArea: [sdk.areas.LowerKurast, sdk.areas.FlayerDungeonLvl1, sdk.areas.SwampyPitLvl1],
+ presetMonsters: [sdk.monsters.preset.Stormtree],
+ poi: [sdk.quest.chest.GidbinnAltar],
+ }],
+ [sdk.areas.SwampyPitLvl1, {
+ previousArea: [sdk.areas.FlayerJungle],
+ nextArea: [sdk.areas.SwampyPitLvl2],
+ }],
+ [sdk.areas.SwampyPitLvl2, {
+ previousArea: [sdk.areas.SwampyPitLvl1],
+ nextArea: [sdk.areas.SwampyPitLvl3],
+ }],
+ [sdk.areas.SwampyPitLvl3, {
+ previousArea: [sdk.areas.SwampyPitLvl2],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.FlayerDungeonLvl1, {
+ previousArea: [sdk.areas.FlayerJungle],
+ nextArea: [sdk.areas.FlayerDungeonLvl2],
+ }],
+ [sdk.areas.FlayerDungeonLvl2, {
+ previousArea: [sdk.areas.FlayerDungeonLvl1],
+ nextArea: [sdk.areas.FlayerDungeonLvl3],
+ }],
+ [sdk.areas.FlayerDungeonLvl3, {
+ previousArea: [sdk.areas.FlayerDungeonLvl2],
+ presetMonsters: [sdk.monsters.preset.WitchDoctorEndugu],
+ presetChests: [sdk.quest.chest.KhalimsBrainChest],
+ }],
+ [sdk.areas.LowerKurast, {
+ previousArea: [sdk.areas.FlayerJungle],
+ nextArea: [sdk.areas.KurastBazaar],
+ presetChests: [sdk.objects.SuperChest],
+ }],
+ [sdk.areas.KurastBazaar, {
+ previousArea: [sdk.areas.LowerKurast],
+ nextArea: [
+ sdk.areas.UpperKurast, sdk.areas.RuinedTemple,
+ sdk.areas.DisusedFane, sdk.areas.A3SewersLvl1
+ ],
+ }],
+ [sdk.areas.RuinedTemple, {
+ previousArea: [sdk.areas.KurastBazaar],
+ presetMonsters: [sdk.monsters.preset.BattlemaidSarina],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ poi: [sdk.quest.chest.LamEsensTomeHolder],
+ }],
+ [sdk.areas.DisusedFane, {
+ previousArea: [sdk.areas.KurastBazaar],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.A3SewersLvl1, {
+ previousArea: [sdk.areas.KurastBazaar],
+ nextArea: [sdk.areas.A3SewersLvl2],
+ presetMonsters: [sdk.monsters.preset.IcehawkRiftwing],
+ poi: [sdk.objects.SewerLever],
+ }],
+ [sdk.areas.A3SewersLvl2, {
+ previousArea: [sdk.areas.A3SewersLvl1],
+ presetChests: [sdk.quest.chest.KhalimsHeartChest],
+ }],
+ [sdk.areas.UpperKurast, {
+ previousArea: [sdk.areas.KurastBazaar],
+ nextArea: [
+ sdk.areas.KurastCauseway,
+ sdk.areas.ForgottenReliquary,
+ sdk.areas.ForgottenTemple],
+ }],
+ [sdk.areas.ForgottenReliquary, {
+ previousArea: [sdk.areas.UpperKurast],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.ForgottenTemple, {
+ previousArea: [sdk.areas.UpperKurast],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.KurastCauseway, {
+ previousArea: [sdk.areas.UpperKurast],
+ nextArea: [sdk.areas.Travincal, sdk.areas.RuinedFane, sdk.areas.DisusedReliquary],
+ }],
+ [sdk.areas.DisusedReliquary, {
+ previousArea: [sdk.areas.KurastCauseway],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.RuinedFane, {
+ previousArea: [sdk.areas.KurastCauseway],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.Travincal, {
+ previousArea: [sdk.areas.KurastCauseway],
+ nextArea: [sdk.areas.DuranceofHateLvl1],
+ presetMonsters: [
+ sdk.monsters.preset.IsmailVilehand,
+ sdk.monsters.preset.GelebFlamefinger,
+ sdk.monsters.preset.ToorcIcefist
+ ],
+ poi: [sdk.objects.CompellingOrb, sdk.objects.DuranceEntryStairs],
+ }],
+ [sdk.areas.DuranceofHateLvl1, {
+ previousArea: [sdk.areas.Travincal],
+ nextArea: [sdk.areas.DuranceofHateLvl2],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheBlackenedTemple).complete()
+ || QuestData.get(sdk.quest.id.KhalimsWill).complete();
+ },
+ }],
+ [sdk.areas.DuranceofHateLvl2, {
+ previousArea: [sdk.areas.DuranceofHateLvl1],
+ nextArea: [sdk.areas.DuranceofHateLvl3],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheBlackenedTemple).complete()
+ || QuestData.get(sdk.quest.id.KhalimsWill).complete();
+ },
+ }],
+ [sdk.areas.DuranceofHateLvl3, {
+ previousArea: [sdk.areas.DuranceofHateLvl2],
+ nextArea: [sdk.areas.PandemoniumFortress],
+ presetMonsters: [
+ sdk.monsters.preset.BremmSparkfist,
+ sdk.monsters.preset.WyandVoidfinger,
+ sdk.monsters.preset.MafferDragonhand,
+ sdk.monsters.Mephisto
+ ],
+ presetChests: [sdk.objects.SuperChest],
+ poi: [sdk.objects.RedPortalToAct4],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheBlackenedTemple).complete()
+ || QuestData.get(sdk.quest.id.KhalimsWill).complete();
+ },
+ }],
+ // Act 4
+ [sdk.areas.PandemoniumFortress, {
+ nextArea: [sdk.areas.OuterSteppes],
+ poi: [sdk.objects.RedPortalToAct5],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.TheGuardian).complete()
+ || QuestData.get(sdk.quest.id.AbleToGotoActIV).complete();
+ },
+ }],
+ [sdk.areas.OuterSteppes, {
+ previousArea: [sdk.areas.PandemoniumFortress],
+ nextArea: [sdk.areas.PlainsofDespair],
+ }],
+ [sdk.areas.PlainsofDespair, {
+ previousArea: [sdk.areas.OuterSteppes],
+ nextArea: [sdk.areas.CityoftheDamned],
+ presetMonsters: [sdk.monsters.preset.Izual],
+ }],
+ [sdk.areas.CityoftheDamned, {
+ previousArea: [sdk.areas.PlainsofDespair],
+ nextArea: [sdk.areas.RiverofFlame],
+ }],
+ [sdk.areas.RiverofFlame, {
+ previousArea: [sdk.areas.CityoftheDamned],
+ nextArea: [sdk.areas.ChaosSanctuary],
+ presetMonsters: [sdk.monsters.preset.Hephasto],
+ poi: [sdk.quest.chest.HellForge],
+ }],
+ [sdk.areas.ChaosSanctuary, {
+ previousArea: [sdk.areas.RiverofFlame],
+ presetMonsters: [
+ sdk.monsters.preset.GrandVizierofChaos,
+ sdk.monsters.preset.LordDeSeis,
+ sdk.monsters.preset.InfectorofSouls,
+ sdk.monsters.Diablo
+ ],
+ poi: [
+ sdk.objects.DiabloSealSeis, sdk.objects.DiabloStar,
+ sdk.objects.DiabloSealInfector, sdk.objects.DiabloSealInfector2,
+ sdk.objects.DiabloSealVizier, sdk.objects.DiabloSealVizier2,
+ ],
+ }],
+ // Act 5
+ [sdk.areas.Harrogath, {
+ nextArea: [sdk.areas.BloodyFoothills, sdk.areas.NihlathaksTemple],
+ poi: [sdk.objects.Act5Gate],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.AbleToGotoActV).complete();
+ },
+ }],
+ [sdk.areas.NihlathaksTemple, {
+ previousArea: [sdk.areas.Harrogath],
+ nextArea: [sdk.areas.HallsofAnguish],
+ presetMonsters: [sdk.monsters.preset.Pindleskin],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.PrisonofIce).complete(true);
+ },
+ }],
+ [sdk.areas.HallsofAnguish, {
+ previousArea: [sdk.areas.NihlathaksTemple],
+ nextArea: [sdk.areas.HallsofPain],
+ presetChests: [sdk.objects.LargeSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.PrisonofIce).complete(true);
+ },
+ }],
+ [sdk.areas.HallsofPain, {
+ previousArea: [sdk.areas.HallsofAnguish],
+ nextArea: [sdk.areas.HallsofVaught],
+ presetChests: [sdk.objects.LargeSparklyChest],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.PrisonofIce).complete(true);
+ },
+ }],
+ [sdk.areas.HallsofVaught, {
+ previousArea: [sdk.areas.HallsofPain],
+ presetMonsters: [sdk.monsters.preset.Nihlathak],
+ poi: [sdk.objects.NihlathaksPlatform],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.PrisonofIce).complete(true);
+ },
+ }],
+ [sdk.areas.BloodyFoothills, {
+ previousArea: [sdk.areas.Harrogath],
+ nextArea: [sdk.areas.FrigidHighlands],
+ presetMonsters: [
+ sdk.monsters.preset.DacFarren,
+ sdk.monsters.preset.ShenktheOverseer
+ ],
+ }],
+ [sdk.areas.FrigidHighlands, {
+ previousArea: [sdk.areas.BloodyFoothills],
+ nextArea: [sdk.areas.ArreatPlateau, sdk.areas.Abaddon],
+ presetMonsters: [
+ sdk.monsters.preset.EldritchtheRectifier,
+ sdk.monsters.preset.EyebacktheUnleashed,
+ sdk.monsters.preset.SharpToothSayer
+ ],
+ presetChests: [sdk.objects.LargeSparklyChest],
+ }],
+ [sdk.areas.Abaddon, {
+ previousArea: [sdk.areas.FrigidHighlands],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.ArreatPlateau, {
+ previousArea: [sdk.areas.FrigidHighlands],
+ nextArea: [sdk.areas.CrystalizedPassage, sdk.areas.PitofAcheron],
+ presetMonsters: [sdk.monsters.preset.ThreshSocket],
+ }],
+ [sdk.areas.PitofAcheron, {
+ previousArea: [sdk.areas.ArreatPlateau],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.CrystalizedPassage, {
+ previousArea: [sdk.areas.ArreatPlateau],
+ nextArea: [sdk.areas.GlacialTrail, sdk.areas.FrozenRiver],
+ }],
+ [sdk.areas.FrozenRiver, {
+ previousArea: [sdk.areas.CrystalizedPassage],
+ presetMonsters: [sdk.monsters.preset.Frozenstein],
+ poi: [sdk.objects.FrozenAnyasPlatform],
+ }],
+ [sdk.areas.GlacialTrail, {
+ previousArea: [sdk.areas.CrystalizedPassage],
+ nextArea: [sdk.areas.FrozenTundra, sdk.areas.DrifterCavern],
+ presetMonsters: [sdk.monsters.preset.BonesawBreaker],
+ presetChests: [sdk.objects.LargeSparklyChest],
+ }],
+ [sdk.areas.DrifterCavern, {
+ previousArea: [sdk.areas.GlacialTrail],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.FrozenTundra, {
+ previousArea: [sdk.areas.GlacialTrail],
+ nextArea: [sdk.areas.AncientsWay, sdk.areas.InfernalPit],
+ }],
+ [sdk.areas.InfernalPit, {
+ previousArea: [sdk.areas.FrozenTundra],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.AncientsWay, {
+ previousArea: [sdk.areas.FrozenTundra],
+ nextArea: [sdk.areas.ArreatSummit, sdk.areas.IcyCellar],
+ }],
+ [sdk.areas.IcyCellar, {
+ previousArea: [sdk.areas.AncientsWay],
+ presetMonsters: [sdk.monsters.preset.SnapchipShatter],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.ArreatSummit, {
+ previousArea: [sdk.areas.AncientsWay],
+ nextArea: [sdk.areas.WorldstoneLvl1],
+ presetMonsters: [
+ sdk.monsters.preset.TalictheDefender,
+ sdk.monsters.preset.MadawctheGuardian,
+ sdk.monsters.preset.KorlictheProtector
+ ],
+ poi: [
+ sdk.objects.KorlictheProtectorStatue,
+ sdk.objects.MadawctheGuardianStatue,
+ sdk.objects.TalictheDefenderStatue,
+ sdk.objects.AncientsAltar,
+ sdk.objects.AncientsDoor,
+ ],
+ }],
+ [sdk.areas.WorldstoneLvl1, {
+ previousArea: [sdk.areas.ArreatSummit],
+ nextArea: [sdk.areas.WorldstoneLvl2],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.RiteofPassage).complete();
+ },
+ }],
+ [sdk.areas.WorldstoneLvl2, {
+ previousArea: [sdk.areas.WorldstoneLvl1],
+ nextArea: [sdk.areas.WorldstoneLvl3],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.RiteofPassage).complete();
+ },
+ }],
+ [sdk.areas.WorldstoneLvl3, {
+ previousArea: [sdk.areas.WorldstoneLvl2],
+ nextArea: [sdk.areas.ThroneofDestruction],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.RiteofPassage).complete();
+ },
+ }],
+ [sdk.areas.ThroneofDestruction, {
+ previousArea: [sdk.areas.WorldstoneLvl3],
+ nextArea: [sdk.areas.WorldstoneChamber],
+ presetMonsters: [
+ sdk.monsters.preset.ColenzotheAnnihilator,
+ sdk.monsters.preset.AchmeltheCursed,
+ sdk.monsters.preset.BartuctheBloody,
+ sdk.monsters.preset.VentartheUnholy,
+ sdk.monsters.preset.ListertheTormentor
+ ],
+ poi: [sdk.objects.WorldstonePortal],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.RiteofPassage).complete();
+ },
+ }],
+ [sdk.areas.WorldstoneChamber, {
+ previousArea: [sdk.areas.ThroneofDestruction],
+ presetMonsters: [sdk.monsters.Baal],
+ preReq: function () {
+ return QuestData.get(sdk.quest.id.RiteofPassage).complete();
+ },
+ }],
+ [sdk.areas.MatronsDen, {
+ // previousArea: [sdk.areas.Harrogath],
+ presetMonsters: [sdk.monsters.Lilith],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.FurnaceofPain, {
+ // previousArea: [sdk.areas.Harrogath],
+ presetMonsters: [sdk.monsters.UberIzual],
+ presetChests: [sdk.objects.SmallSparklyChest],
+ }],
+ [sdk.areas.ForgottenSands, {
+ // previousArea: [sdk.areas.Harrogath],
+ presetMonsters: [sdk.monsters.UberDuriel],
+ }],
+ [sdk.areas.UberTristram, {
+ // previousArea: [sdk.areas.Harrogath],
+ presetMonsters: [
+ sdk.monsters.UberDiablo,
+ sdk.monsters.UberMephisto,
+ sdk.monsters.UberBaal
+ ],
+ }],
+ ]);
+
+ /** @type {Map this.regenTime;
+ };
+
+ /**
+ * @constructor
+ * @param {Exit} exit
+ */
+ function ExitInstance (exit) {
+ this.x = exit.x;
+ this.y = exit.y;
+ this.target = exit.target;
+ this.type = exit.type;
+ this.tileid = exit.tileid;
+ }
+
+ /**
+ * @constructor
+ * @param {number} index
+ */
+ function AreaDataInstance (index) {
+ let _aData = _areaData.get(index);
+
+ this.LocaleString = getAreaName(index);
+ this.Index = index; // why index and not area id?
+ this.Act = getBaseStat("levels", index, "Act") + 1;
+ this.Level = getBaseStat("levels", index, ["MonLvl1Ex", "MonLvl2Ex", "MonLvl3Ex"][me.diff]);
+ this.Size = (function () {
+ // frigid highlands doesn't specify size, manual measurement
+ if (index === sdk.areas.FrigidHighlands) return { x: 210, y: 710 };
+
+ // arreat plateau doesn't specify size, manual measurement
+ if (index === sdk.areas.ArreatPlateau) 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])
+ };
+ })();
+ this.SuperUnique = (_aData.presetMonsters || []);
+ this.Monsters = (MONSTER_KEYS
+ .map(function (key) {
+ return getBaseStat("levels", index, key);
+ })
+ .filter(function (key) {
+ return key !== 65535;
+ })
+ );
+ this.MonsterDensity = getBaseStat("levels", index, ["MonDen", "MonDen(N)", "MonDen(H)"][me.diff]);
+ this.ChampionPacks = {
+ Min: getBaseStat("levels", index, ["MonUMin", "MonUMin(N)", "MonUMin(H)"][me.diff]),
+ Max: getBaseStat("levels", index, ["MonUMax", "MonUMax(N)", "MonUMax(H)"][me.diff])
+ };
+ this.SuperChests = (_aData.presetChests || []);
+ this.Poi = (_aData.poi || []);
+ this.QuestPreReq = (_aData.preReq || null);
+ /**
+ * @private
+ * @type {PresetObjectUnit | null}
+ */
+ this._Waypoint = null;
+ let wp = getBaseStat("levels", index, "Waypoint");
+ if (wp !== 255) {
+ wps.set(this.Index, wp);
+ }
+ /** @type {Array} */
+ this.Shrines = [];
+ /** @type {Array} */
+ this.Chests = [];
+ /** @type {number[]} */
+ this.NextArea = (_aData.nextArea || []);
+ /** @type {number[]} */
+ this.PreviousArea = (_aData.previousArea || []);
+ /** @type {Array} */
+ this.Exits = [];
+ /** @private */
+ this._Accessible = false;
+ }
+
+ /**
+ * Check if this area has a monster of a certain type
+ * @param {number} type - monster type to check for
+ * @returns {boolean}
+ */
+ AreaDataInstance.prototype.hasMonsterType = function (type) {
+ return this.Monsters.some(function (monId) {
+ return MonsterData.get(monId).Type === type;
+ });
+ };
+ /**
+ * Iterate through each monster in this area and apply a callback function
+ * @param {function} cb - callback function to apply to each monster
+ */
+ AreaDataInstance.prototype.forEachMonster = function (cb) {
+ if (typeof cb === "function") {
+ this.Monsters.forEach(function (monID) {
+ const _monster = MonsterData.get(monID);
+ return cb(
+ _monster,
+ _monster.Rarity * (_monster.GroupCount.Min + _monster.GroupCount.Max) / 2
+ );
+ });
+ }
+ };
+ /**
+ * Iterate through each monster and minion in this area and apply a callback function
+ * @param {function} cb - callback function to apply to each monster
+ */
+ AreaDataInstance.prototype.forEachMonsterAndMinion = function (cb) {
+ if (typeof cb === "function") {
+ this.Monsters.forEach(function (monID) {
+ const _monster = MonsterData.get(monID);
+ let rarity = _monster.Rarity * (_monster.GroupCount.Min + _monster.GroupCount.Max) / 2;
+ cb(_monster, rarity, null);
+ _monster.Minions.forEach(function (minionID) {
+ // eslint-disable-next-line max-len
+ let minionrarity = (_monster.Rarity * (_monster.MinionCount.Min + _monster.MinionCount.Max) / 2 / _monster.Minions.length);
+ cb(MonsterData.get(minionID), minionrarity, _monster);
+ });
+ });
+ }
+ };
+ /**
+ * Check whether this area is accessible by quest pre-reqs
+ * @this AreaDataInstance
+ * @returns {boolean}
+ */
+ AreaDataInstance.prototype.canAccess = function () {
+ if (this._Accessible) return true;
+ let check = this.QuestPreReq || _areaData.get(sdk.areas.townOfAct(this.Act)).preReq;
+ if (check()) {
+ this._Accessible = true;
+ }
+ return this._Accessible;
+ };
+ /**
+ * Get town of area
+ */
+ AreaDataInstance.prototype.townArea = function () {
+ return _map.get(sdk.areas.townOfAct(this.Act));
+ };
+ /**
+ * Get exits of the area
+ * @function
+ */
+ AreaDataInstance.prototype.getExits = function () {
+ if (this.Exits.length) return this.Exits;
+ const _areaId = this.Index;
+ /** @type {Area} */
+ const area = Misc.poll(function () {
+ return getArea(_areaId);
+ });
+ if (!area) return [];
+ this.Exits = area.exits
+ .map(function (exit) {
+ return new ExitInstance(exit);
+ });
+ if (specialTransit.has(_areaId)) {
+ let _specialExit = specialTransit.get(_areaId)();
+ if (_specialExit) {
+ this.Exits.push(_specialExit);
+ }
+ }
+ return this.Exits;
+ };
+ /**
+ * @private
+ * @param {PresetUnit} wp
+ */
+ AreaDataInstance.prototype.setWaypoint = function (wp) {
+ if (wp && wp instanceof PresetUnit) {
+ this._Waypoint = wp.realCoords();
+ this._Waypoint.classid = wp.id;
+ }
+ };
+ /**
+ * Get wp of area if it exists
+ * @this {AreaDataInstance}
+ * @returns {PresetObjectUnit | null}
+ */
+ AreaDataInstance.prototype.waypointCoords = function () {
+ if (this._Waypoint) return this._Waypoint;
+ if (!this.hasWaypoint()) return null;
+ // check first that we are currently in the same act
+ if (me.act !== this.Act) return null;
+ // try to find the wp
+ try {
+ const _areaId = this.Index;
+ let wp = Game.getPresetObjects(_areaId)
+ .filter(function (preset) {
+ return sdk.waypoints.Ids.includes(preset.id);
+ })
+ .find(function (preset) {
+ return preset.level === _areaId;
+ });
+ if (wp) {
+ this.setWaypoint(wp);
+ return this._Waypoint;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ return null;
+ };
+ /**
+ * Check if this area as a waypoint
+ * @returns {boolean}
+ */
+ AreaDataInstance.prototype.hasWaypoint = function () {
+ return wps.has(this.Index);
+ };
+ /**
+ * Find nearest waypoint in area
+ * @returns {number}
+ * @todo Fix this, the nearest waypoint could be the next area but this only
+ * checks up to the current area.
+ */
+ AreaDataInstance.prototype.nearestWaypointArea = function () {
+ if (this.hasWaypoint() && this.waypointCoords()) return this.Index;
+ if (!Pather.plotCourse_openedWpMenu) {
+ return [].concat(this.PreviousArea, this.NextArea)
+ .find(function (area) {
+ return wps.has(area) && _map.get(area).waypointCoords();
+ });
+ }
+ // plot toward this area
+ const plot = Pather.plotCourse(this.Index, this.townArea().Index);
+
+ // get the last area that got a WP
+ return plot.course.filter(function (el) {
+ return wps.has(el);
+ }).last();
+ };
+ /**
+ * @this {AreaDataInstance}
+ * @return {PresetObjectUnit | null}
+ * @todo Check more than just the direct next area for a waypoint, consider the example of Tamoe Highland,
+ * where the waypoint isn't the direct next area (Monastery Gate) but the next area after that (Outer Cloister)
+ * we may be in a spot where we are actually closer to Outer Cloisers wp than Black Marsh wp
+ */
+ AreaDataInstance.prototype.nearestWaypointCoords = function () {
+ let waypoints = [];
+ let dist = Infinity;
+ let prev = this.PreviousArea.first();
+ let next = this.NextArea.filter(function (el) {
+ return wps.has(el);
+ }).first();
+
+ // check our current area
+ if (this._Waypoint) {
+ if (this._Waypoint.distance < 60) return this._Waypoint;
+ dist = this._Waypoint.distance;
+ waypoints.push(this._Waypoint);
+ }
+
+ // check the previous area
+ if (_map.get(prev) && _map.get(prev).waypointCoords()) {
+ let wp = _map.get(prev).waypointCoords();
+ if (wp.distance < dist) {
+ dist = wp.distance;
+ if (dist < 60) return wp;
+ waypoints.push(wp);
+ }
+ }
+
+ // check the next area
+ if (_map.get(next) && _map.get(next).waypointCoords()) {
+ let wp = _map.get(next).waypointCoords();
+ if (wp.distance < dist) {
+ dist = wp.distance;
+ if (dist < 60) return wp;
+ waypoints.push(wp);
+ }
+ }
+
+ // this returns the nearest waypoint from town to the current area
+ let wpArea = this.nearestWaypointArea();
+ if (wpArea === prev || wpArea === next) {
+ return waypoints.find(function (el) {
+ return el.distance === dist;
+ });
+ }
+
+ let check = _map.get(wpArea).waypointCoords();
+
+ if (check.distance < dist) {
+ return check;
+ }
+
+ return null;
+ };
+ /**
+ * Get the chests in this area
+ * @todo Add support for chests that are not preset objects
+ * @todo Add opened property to chests so we can ignore them as chests don't regen
+ * @returns {Array}
+ */
+ AreaDataInstance.prototype.getChests = function () {
+ if (this.Chests.length) return this.Chests;
+
+ try {
+ let chests = Game.getPresetObjects(this.Index)
+ .filter(function (preset) {
+ return sdk.objects.chestIds.includes(preset.id);
+ });
+ if (chests.length) {
+ this.Chests = chests.map(function (preset) {
+ return preset.realCoords();
+ });
+ return this.Chests;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ return [];
+ };
+
+ /**
+ * Add shrine to our list of seen shrines for this area
+ * @param {ObjectUnit} shrine
+ */
+ AreaDataInstance.prototype.addShrine = function (shrine) {
+ if (!shrine || !ShrineData.has(shrine.objtype)) return;
+ // we've already added this shrine
+ if (this.Shrines.find((s) => s.gid === shrine.gid)) return;
+ this.Shrines.push(new Shrine(shrine));
+ };
+ /**
+ * Update a shrine to our list of seen shrines for this area
+ * @param {ObjectUnit} shrine
+ */
+ AreaDataInstance.prototype.updateShrine = function (shrine) {
+ if (!shrine || !ShrineData.has(shrine.objtype)) return;
+ // We don't already have this shrine, so add it
+ if (!this.Shrines.find(s => s.gid === shrine.gid)) {
+ this.Shrines.push(new Shrine(shrine));
+ } else {
+ this.Shrines.find(s => s.gid === shrine.gid).interactedAt = getTickCount();
+ }
+ };
+ /**
+ * Get the shrines we have seen in this area
+ * @returns {Array}
+ */
+ AreaDataInstance.prototype.getShrines = function () {
+ if (this.Shrines.length) return this.Shrines;
+
+ // this only works for a1 and a2 /:
+ // try {
+ // let shrines = Game.getPresetObjects(this.Index);
+ // if (shrines) {
+ // shrines = shrines.filter(preset => sdk.shrines.Ids.includes(preset.id));
+ // if (shrines) {
+ // this.Shrines = shrines.map(preset => preset.realCoords());
+ // }
+ // }
+ // } catch (e) {
+ // console.error(e);
+
+ // return [];
+ // }
+
+ return [];
+ };
+
+ for (let i = 1; i < AREA_INDEX_COUNT; i++) {
+ _map.set(i, (new AreaDataInstance(i)));
+ }
+
+ /** @type {Array} */
+ const _nonTownWps = wps.keys()
+ .filter(function (a) {
+ return !sdk.areas.Towns.includes(a);
+ });
+
+ return {
+ wps: wps,
+
+ set: function (key, value) {
+ _map.set(key, value);
+ },
+
+ get: function (key) {
+ return _map.get(key);
+ },
+
+ has: function (key) {
+ return _map.has(key);
+ },
+
+ forEach: function (callbackFn, thisArg) {
+ thisArg = thisArg || this;
+ for (let [key, value] of _map.entries()) {
+ callbackFn.call(thisArg, value, key, this);
+ }
+ },
+
+ keys: function () {
+ return _map.keys();
+ },
+
+ entries: function () {
+ return _map.entries();
+ },
+
+ randomWpArea: function (checkValid = false) {
+ return checkValid
+ ? _nonTownWps.filter(function (a) {
+ return me.haveWaypoint(a);
+ }).random()
+ : _nonTownWps.random();
+ },
+
+ getAreasWithShrine: function (shrineType) {
+ let areas = [];
+
+ _map.forEach(function (area) {
+ if (area.getShrines().find(s => s.type === shrineType && s.useable())) {
+ areas.push(area);
+ }
+ });
+
+ return areas;
+ },
+ };
+ })();
+
+ module.exports = AreaData;
+})(module, require);
diff --git a/libs/SoloPlay/Modules/GameData/GameData.js b/libs/SoloPlay/Modules/GameData/GameData.js
new file mode 100644
index 00000000..4d17d3bf
--- /dev/null
+++ b/libs/SoloPlay/Modules/GameData/GameData.js
@@ -0,0 +1,2462 @@
+/* eslint-disable max-len */
+/* eslint-disable no-unused-vars */
+/* eslint-disable no-irregular-whitespace */
+/**
+ * @filename GameData.js
+ * @author Nishimura-Katsuo
+ * @desc game data library
+ *
+ */
+// todo - remove the magic numbers here
+(function (module, require) {
+ const MonsterData = require("./MonsterData");
+ const AreaData = require("./AreaData");
+ const MissileData = require("./MissileData");
+ const Coords_1 = require("../Coords");
+ const Vector = require("../Vector");
+ const sdk = require("../../../modules/sdk");
+ const HPLookup = [
+ ["1", "1", "1"], ["7", "107", "830"],
+ ["9", "113", "852"], ["12", "120", "875"],
+ ["15", "125", "897"], ["17", "132", "920"],
+ ["20", "139", "942"], ["23", "145", "965"],
+ ["27", "152", "987"], ["31", "157", "1010"],
+ ["35", "164", "1032"], ["36", "171", "1055"],
+ ["40", "177", "1077"], ["44", "184", "1100"],
+ ["48", "189", "1122"], ["52", "196", "1145"],
+ ["56", "203", "1167"], ["60", "209", "1190"],
+ ["64", "216", "1212"], ["68", "221", "1235"],
+ ["73", "228", "1257"], ["78", "236", "1280"],
+ ["84", "243", "1302"], ["89", "248", "1325"],
+ ["94", "255", "1347"], ["100", "261", "1370"],
+ ["106", "268", "1392"], ["113", "275", "1415"],
+ ["120", "280", "1437"], ["126", "287", "1460"],
+ ["134", "320", "1482"], ["142", "355", "1505"],
+ ["150", "388", "1527"], ["158", "423", "1550"],
+ ["166", "456", "1572"], ["174", "491", "1595"],
+ ["182", "525", "1617"], ["190", "559", "1640"],
+ ["198", "593", "1662"], ["206", "627", "1685"],
+ ["215", "661", "1707"], ["225", "696", "1730"],
+ ["234", "729", "1752"], ["243", "764", "1775"],
+ ["253", "797", "1797"], ["262", "832", "1820"],
+ ["271", "867", "1842"], ["281", "900", "1865"],
+ ["290", "935", "1887"], ["299", "968", "1910"],
+ ["310", "1003", "1932"], ["321", "1037", "1955"],
+ ["331", "1071", "1977"], ["342", "1105", "2000"],
+ ["352", "1139", "2030"], ["363", "1173", "2075"],
+ ["374", "1208", "2135"], ["384", "1241", "2222"],
+ ["395", "1276", "2308"], ["406", "1309", "2394"],
+ ["418", "1344", "2480"], ["430", "1379", "2567"],
+ ["442", "1412", "2653"], ["454", "1447", "2739"],
+ ["466", "1480", "2825"], ["477", "1515", "2912"],
+ ["489", "1549", "2998"], ["501", "1583", "3084"],
+ ["513", "1617", "3170"], ["525", "1651", "3257"],
+ ["539", "1685", "3343"], ["552", "1720", "3429"],
+ ["565", "1753", "3515"], ["579", "1788", "3602"],
+ ["592", "1821", "3688"], ["605", "1856", "3774"],
+ ["618", "1891", "3860"], ["632", "1924", "3947"],
+ ["645", "1959", "4033"], ["658", "1992", "4119"],
+ ["673", "2027", "4205"], ["688", "2061", "4292"],
+ ["702", "2095", "4378"], ["717", "2129", "4464"],
+ ["732", "2163", "4550"], ["746", "2197", "4637"],
+ ["761", "2232", "4723"], ["775", "2265", "4809"],
+ ["790", "2300", "4895"], ["805", "2333", "4982"],
+ ["821", "2368", "5068"], ["837", "2403", "5154"],
+ ["853", "2436", "5240"], ["868", "2471", "5327"],
+ ["884", "2504", "5413"], ["900", "2539", "5499"],
+ ["916", "2573", "5585"], ["932", "2607", "5672"],
+ ["948", "2641", "5758"], ["964", "2675", "5844"],
+ ["982", "2709", "5930"], ["999", "2744", "6017"],
+ ["1016", "2777", "6103"], ["1033", "2812", "6189"],
+ ["1051", "2845", "6275"], ["1068", "2880", "6362"],
+ ["1085", "2915", "6448"], ["1103", "2948", "6534"],
+ ["1120", "2983", "6620"], ["1137", "3016", "6707"],
+ ["10000", "10000", "10000"]
+ ];
+
+ /** @param {Monster} unit */
+ function isAlive (unit) {
+ return Boolean(unit && unit.hp);
+ }
+
+ /** @param {Monster} unit */
+ function isEnemy (unit) {
+ return Boolean(
+ unit && isAlive(unit)
+ && unit.getStat(sdk.stats.Alignment) !== 2
+ && typeof unit.classid === "number"
+ && MonsterData.get(unit.classid).Killable
+ );
+ }
+
+ /** @param {ItemUnit} item */
+ function onGround (item) {
+ return item.onGroundOrDropping;
+ }
+
+ const GameData = {
+ myReference: me,
+ /**
+ * @param {number} monsterID
+ * @param {number} areaID
+ */
+ monsterLevel: function (monsterID, areaID) {
+ return (me.diff
+ ? AreaData.has(areaID) && AreaData.get(areaID).Level
+ : MonsterData.has(monsterID) && MonsterData.get(monsterID).Level); // levels on nm/hell are determined by area, not by monster data
+ },
+ /**
+ * @param {number} monsterID
+ * @param {number} areaID
+ * @param {number} adjustLevel
+ */
+ monsterExp: function (monsterID, areaID, adjustLevel = 0) {
+ const mLvl = this.monsterLevel(monsterID, areaID) + adjustLevel;
+ const { ExperienceModifier } = MonsterData.get(monsterID);
+ return Experience.monsterExp[Math.min(
+ Experience.monsterExp.length - 1,
+ mLvl
+ )][me.diff] * ExperienceModifier / 100;
+ },
+ /**
+ * @param {number} monsterID
+ * @param {number} areaID
+ */
+ eliteExp: function (monsterID, areaID) {
+ return this.monsterExp(monsterID, areaID, 2) * 3;
+ },
+ /**
+ * @param {number} monsterID
+ * @param {number} areaID
+ * @param {number} adjustLevel
+ */
+ monsterAvgHP: function (monsterID, areaID, adjustLevel = 0) {
+ const { MinHp, MaxHp } = MonsterData.get(monsterID);
+ const mLvl = this.monsterLevel(monsterID, areaID) + adjustLevel;
+ return HPLookup[Math.min(HPLookup.length - 1, mLvl)][me.diff] * (MinHp + MaxHp) / 200;
+ },
+ /**
+ * @param {number} monsterID
+ * @param {number} areaID
+ * @param {number} adjustLevel
+ */
+ monsterMaxHP: function (monsterID, areaID, adjustLevel = 0) {
+ const mLvl = this.monsterLevel(monsterID, areaID) + adjustLevel;
+ const { MaxHp } = MonsterData.get(monsterID);
+ return HPLookup[Math.min(HPLookup.length - 1, mLvl)][me.diff] * MaxHp / 100;
+ },
+ eliteAvgHP: function (monsterID, areaID) {
+ return (6 - me.diff) / 2 * this.monsterAvgHP(monsterID, areaID, 2);
+ },
+ monsterDamageModifier: function () {
+ return 1 + (this.multiplayerModifier() - 1) * 0.0625;
+ },
+ monsterMaxDmg: function (monsterID, areaID, adjustLevel = 0) {
+ let level = this.monsterLevel(monsterID, areaID) + adjustLevel;
+ let { Attack1MaxDmg, Attack2MaxDmg, Skill1MaxDmg } = MonsterData.get(monsterID);
+ return Math.max.apply(
+ null, [Attack1MaxDmg, Attack2MaxDmg, Skill1MaxDmg]
+ ) * level / 100 * this.monsterDamageModifier();
+ },
+ // https://www.diabloii.net/forums/threads/monster-damage-increase-per-player-count.570346/
+ monsterAttack1AvgDmg: function (monsterID, areaID, adjustLevel = 0) {
+ let level = this.monsterLevel(monsterID, areaID) + adjustLevel;
+ let { Attack1MinDmg, Attack1MaxDmg } = MonsterData.get(monsterID);
+ return ((Attack1MinDmg + Attack1MaxDmg) / 2) * level / 100 * this.monsterDamageModifier();
+ },
+ monsterAttack2AvgDmg: function (monsterID, areaID, adjustLevel = 0) {
+ let level = this.monsterLevel(monsterID, areaID) + adjustLevel;
+ let { Attack2MinDmg, Attack2MaxDmg } = MonsterData.get(monsterID);
+ return ((Attack2MinDmg + Attack2MaxDmg) / 2) * level / 100 * this.monsterDamageModifier();
+ },
+ monsterSkill1AvgDmg: function (monsterID, areaID, adjustLevel = 0) {
+ let level = this.monsterLevel(monsterID, areaID) + adjustLevel;
+ let { Skill1MinDmg, Skill1MaxDmg } = MonsterData.get(monsterID);
+ return ((Skill1MinDmg + Skill1MaxDmg) / 2) * level / 100 * this.monsterDamageModifier();
+ },
+ monsterAvgDmg: function (monsterID, areaID, adjustLevel = 0) {
+ let attack1 = this.monsterAttack1AvgDmg(monsterID, areaID, adjustLevel);
+ let attack2 = this.monsterAttack2AvgDmg(monsterID, areaID, adjustLevel);
+ let skill1 = this.monsterSkill1AvgDmg(monsterID, areaID, adjustLevel);
+ let dmgs = [attack1, attack2, skill1]
+ .filter(function (x) {
+ return x > 0;
+ });
+ // ignore 0 dmg to avoid reducing average
+ if (!dmgs.length) return 0;
+ return dmgs.reduce(function (acc, v) {
+ return acc + v;
+ }, 0) / dmgs.length;
+ },
+ averagePackSize: function (monsterID) {
+ let { GroupCount, MinionCount } = MonsterData.get(monsterID);
+ return (GroupCount.Min + MinionCount.Min + GroupCount.Max + MinionCount.Max) / 2;
+ },
+ areaLevel: function (areaID) {
+ // levels on nm/hell are determined by area, not by monster data
+ if (me.diff) return AreaData.get(areaID).Level;
+
+ let levels = 0, total = 0;
+
+ AreaData.get(areaID).forEachMonsterAndMinion(function (mon, rarity) {
+ levels += mon.Level * rarity;
+ total += rarity;
+ });
+
+ return Math.round(levels / total);
+ },
+ areaImmunities: function (areaID) {
+ let resists = { Physical: 0, Magic: 0, Fire: 0, Lightning: 0, Cold: 0, Poison: 0 };
+
+ AreaData.get(areaID).forEachMonsterAndMinion(function (mon) {
+ for (let k in resists) {
+ resists[k] = Math.max(resists[k], mon[k]);
+ }
+ });
+
+ return Object.keys(resists)
+ .filter(function (key) {
+ return 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(GameData.myReference);
+ if (!party) return 1;
+
+ count = 1;
+
+ while (party.getNext()) {
+ count++;
+ }
+ }
+
+ return (count + 1) / 2;
+ },
+ partyModifier: function (playerID) {
+ let party = getParty(GameData.myReference);
+ let level = 0, total = 0;
+ if (!party) return 1;
+
+ let 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);
+ let party = getParty(GameData.myReference);
+ if (!party) return 0;
+
+ let level = 0, total = 0;
+ let gamesize = 0;
+ let 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
+ );
+ },
+ baseLevel: function (...skillIDs) {
+ return skillIDs.reduce(function (total, skillID) {
+ return total + GameData.myReference.getSkill(skillID, 0);
+ }, 0);
+ },
+ skillLevel: function (...skillIDs) {
+ return skillIDs.reduce(function (total, skillID) {
+ return total + GameData.myReference.getSkill(skillID, 1);
+ }, 0);
+ },
+ skillCooldown: function (skillID) {
+ return getBaseStat("Skills", skillID, "delay") !== -1;
+ },
+ stagedDamage: function (l, a, b, c, d, e, f, hitshift = 0, mult = 1) {
+ if (l > 28) {
+ a += f * (l - 28);
+ l = 28;
+ }
+
+ if (l > 22) {
+ a += e * (l - 22);
+ l = 22;
+ }
+
+ if (l > 16) {
+ a += d * (l - 16);
+ l = 16;
+ }
+
+ if (l > 8) {
+ a += c * (l - 8);
+ l = 8;
+ }
+
+ a += b * (Math.max(0, l) - 1);
+
+ return (mult * a) << hitshift;
+ },
+ damageTypes: ["Physical", "Fire", "Lightning", "Magic", "Cold", "Poison", "?", "?", "?", "Physical"], // 9 is Stun, but stun isn't an element
+ synergyCalc: { // TODO: add melee skill damage and synergies - they are poop
+ // sorc fire spells
+ 36: [47, 0.16, 56, 0.16], // fire bolt
+ 41: [37, 0.13], // inferno
+ 46: [37, 0.04, 51, 0.01], // blaze
+ 47: [36, 0.14, 56, 0.14], // fire ball
+ 51: [37, 0.04, 41, 0.01], // fire wall
+ 52: [37, 0.09], // enchant
+ 56: [36, 0.05, 47, 0.05], // meteor
+ 62: [36, 0.03, 47, 0.03], // hydra
+
+ // sorc lightning spells
+ 38: [49, 0.06], // charged bolt
+ 49: [38, 0.08, 48, 0.08, 53, 0.08], // lightning
+ 53: [38, 0.04, 48, 0.04, 49, 0.04], // chain lightning
+
+ // sorc cold spells
+ 39: [44, 0.15, 45, 0.15, 55, 0.15, 59, 0.15, 64, 0.15], // ice bolt
+ 44: [59, 0.10, 64, 0.10], // frost nova
+ 45: [39, 0.08, 59, 0.08, 64, 0.08], // ice blast
+ 55: [39, 0.05, 45, 0.05, 64, 0.05], // glacial spike
+ 59: [39, 0.05, 45, 0.05, 55, 0.05], // blizzard
+ 64: [39, 0.02], // frozen orb
+
+ // assassin traps
+ 251: [256, 0.09, 261, 0.09, 262, 0.09, 271, 0.09, 272, 0.09, 276, 0.09], // fireblast
+ 256: [261, 0.11, 271, 0.11, 276, 0.11], // shock web
+ 261: [251, 0.06, 271, 0.06, 276, 0.06], // charged bolt sentry
+ 262: [251, 0.08, 272, 0.08], // wake of fire sentry
+ 271: [256, 0.12, 261, 0.12, 276, 0.12], // lightning sentry
+ 272: [251, 0.10, 276, 0.10, 262, 0.07], // inferno sentry
+ 276: [271, 0.12], // death sentry
+
+ // necro bone spells
+ 67: [78, 0.15, 84, 0.15, 88, 0.15, 93, 0.15], // teeth
+ 73: [83, 0.20, 92, 0.20], // poison dagger
+ 83: [73, 0.15, 92, 0.15], // poison explosion
+ 84: [67, 0.07, 78, 0.07, 88, 0.07, 93, 0.07], // bone spear
+ 92: [73, 0.10, 83, 0.10], // poison nova
+ 93: [67, 0.06, 78, 0.06, 84, 0.06, 88, 0.06], // bone spirit
+
+ // barb war cry
+ 154: [130, 0.06, 137, 0.06, 146, 0.06], // war cry
+
+ // paladin combat spells
+ 101: [112, 0.50, 121, 0.50], // holy bolt
+ 112: [108, 0.14, 115, 0.14], // blessed hammer
+ 121: [118, 0.07], // fist of heavens
+
+ // paladin auras
+ 102: [100, 0.18, 125, 0.06], // holy fire
+ 114: [105, 0.15, 125, 0.07], // holy freeze
+ 118: [110, 0.12, 125, 0.04], // holy shock
+
+ // durid elemental skills
+ 225: [229, 0.23, 234, 0.23], // firestorm
+ 229: [244, 0.10, 225, 0.08], // molten boulder
+ 234: [225, 0.12, 244, 0.12], // fissure (eruption)
+ 244: [229, 0.12, 234, 0.12, 249, 0.12], // volcano
+ 249: [225, 0.14, 229, 0.14, 244, 0.14], // armageddon
+ 230: [250, 0.15, 235, 0.15], // arctic blast
+ 240: [245, 0.10, 250, 0.10], // twister
+ 245: [235, 0.09, 240, 0.09, 250, 0.09], // tornado
+ 250: [240, 0.09, 245, 0.09], // hurricane
+
+ // durid feral skills
+ 238: [222, 0.18], // rabies
+ 239: [225, 0.22, 229, 0.22, 234, 0.22, 244, 0.22], // fire claws
+
+ // amazon bow/xbow skills
+ 11: [21, 0.12], // cold arrow
+ 21: [11, 0.08], // ice arrow
+ 31: [11, 0.12], // freezing arrow
+ 7: [16, 0.12], // fire arrow
+ 16: [7, 0.12], // exploding arrow
+ 27: [16, 0.10], // immolation arrow
+
+ // amazon spear/javalin skills
+ 14: [20, 0.10, 24, 0.10, 34, 0.10, 35, 0.10], // power strike
+ 20: [14, 0.03, 24, 0.03, 34, 0.03, 35, 0.03], // lightning bolt
+ 24: [14, 0.10, 20, 0.10, 34, 0.10, 35, 0.10], // charged strike
+ 34: [14, 0.08, 20, 0.08, 24, 0.10, 35, 0.10], // lightning strike
+ 35: [14, 0.01, 20, 0.01, 24, 0.01, 34, 0.01], // lightning fury
+ 15: [25, 0.12], // poison javalin
+ 25: [15, 0.10], // plague javalin
+ },
+ noMinSynergy: [14, 20, 24, 34, 35, 49, 53, 118, 256, 261, 271, 276],
+ skillMult: {
+ 15: 25,
+ 25: 25,
+ 41: 25,
+ 46: 75,
+ 51: 75,
+ 73: 25,
+ 83: 25,
+ 92: 25,
+ 222: 25,
+ 225: 75,
+ 230: 25,
+ 238: 25,
+ 272: 25 / 3
+ },
+ baseSkillDamage: function (skillID) { // TODO: rework skill damage to use both damage fields
+ let l = this.skillLevel(skillID);
+ let m = this.skillMult[skillID] || 1;
+ let dmgFields = [
+ [
+ "MinDam", "MinLevDam1",
+ "MinLevDam2", "MinLevDam3",
+ "MinLevDam4", "MinLevDam5",
+ "MaxDam", "MaxLevDam1",
+ "MaxLevDam2", "MaxLevDam3",
+ "MaxLevDam4", "MaxLevDam5"
+ ],
+ [
+ "EMin", "EMinLev1",
+ "EMinLev2", "EMinLev3",
+ "EMinLev4", "EMinLev5",
+ "EMax", "EMaxLev1",
+ "EMaxLev2", "EMaxLev3",
+ "EMaxLev4", "EMaxLev5"
+ ]
+ ];
+
+ if (skillID === 70) {
+ return {
+ type: "Physical",
+ pmin: this.stagedDamage(
+ l,
+ getBaseStat("skills", skillID, dmgFields[1][0]),
+ getBaseStat("skills", skillID, dmgFields[1][1]),
+ getBaseStat("skills", skillID, dmgFields[1][2]),
+ getBaseStat("skills", skillID, dmgFields[1][3]),
+ getBaseStat("skills", skillID, dmgFields[1][4]),
+ getBaseStat("skills", skillID, dmgFields[1][5]),
+ getBaseStat("skills", skillID, "HitShift"),
+ m
+ ),
+ pmax: this.stagedDamage(
+ l,
+ getBaseStat("skills", skillID, dmgFields[1][0]),
+ getBaseStat("skills", skillID, dmgFields[1][1]),
+ getBaseStat("skills", skillID, dmgFields[1][2]),
+ getBaseStat("skills", skillID, dmgFields[1][3]),
+ getBaseStat("skills", skillID, dmgFields[1][4]),
+ getBaseStat("skills", skillID, dmgFields[1][5]),
+ getBaseStat("skills", skillID, "HitShift"),
+ m
+ ),
+ min: 0,
+ max: 0
+ };
+ } else {
+ let type = getBaseStat("skills", skillID, "EType");
+
+ return {
+ type: this.damageTypes[type],
+ pmin: this.stagedDamage(
+ l,
+ getBaseStat("skills", skillID, dmgFields[0][0]),
+ getBaseStat("skills", skillID, dmgFields[0][1]),
+ getBaseStat("skills", skillID, dmgFields[0][2]),
+ getBaseStat("skills", skillID, dmgFields[0][3]),
+ getBaseStat("skills", skillID, dmgFields[0][4]),
+ getBaseStat("skills", skillID, dmgFields[0][5]),
+ getBaseStat("skills", skillID, "HitShift"),
+ m
+ ),
+ pmax: this.stagedDamage(
+ l,
+ getBaseStat("skills", skillID, dmgFields[0][6]),
+ getBaseStat("skills", skillID, dmgFields[0][7]),
+ getBaseStat("skills", skillID, dmgFields[0][8]),
+ getBaseStat("skills", skillID, dmgFields[0][9]),
+ getBaseStat("skills", skillID, dmgFields[0][10]),
+ getBaseStat("skills", skillID, dmgFields[0][11]),
+ getBaseStat("skills", skillID, "HitShift"),
+ m
+ ),
+ min: type
+ ? this.stagedDamage(
+ l,
+ getBaseStat("skills", skillID, dmgFields[1][0]),
+ getBaseStat("skills", skillID, dmgFields[1][1]),
+ getBaseStat("skills", skillID, dmgFields[1][2]),
+ getBaseStat("skills", skillID, dmgFields[1][3]),
+ getBaseStat("skills", skillID, dmgFields[1][4]),
+ getBaseStat("skills", skillID, dmgFields[1][5]),
+ getBaseStat("skills", skillID, "HitShift"),
+ m
+ )
+ : 0,
+ max: type
+ ? this.stagedDamage(
+ l,
+ getBaseStat("skills", skillID, dmgFields[1][6]),
+ getBaseStat("skills", skillID, dmgFields[1][7]),
+ getBaseStat("skills", skillID, dmgFields[1][8]),
+ getBaseStat("skills", skillID, dmgFields[1][9]),
+ getBaseStat("skills", skillID, dmgFields[1][10]),
+ getBaseStat("skills", skillID, dmgFields[1][11]),
+ getBaseStat("skills", skillID, "HitShift"),
+ m
+ )
+ : 0
+ };
+ }
+ },
+ skillRadius: {
+ //47: 8,
+ //48: 5, // Nova
+ 55: 3,
+ 56: 12,
+ 92: 24,
+ 154: 12,
+ 249: 24,
+ 250: 24,
+ 251: 3,
+ },
+ novaLike: {
+ 44: true,
+ 48: true,
+ 92: true,
+ 112: true,
+ 154: true,
+ 249: true,
+ 250: true,
+ },
+ wolfBanned: {
+ 225: true,
+ 229: true,
+ 230: true,
+ 233: true,
+ 234: true,
+ 235: true,
+ 240: true,
+ 243: true,
+ 244: true,
+ 245: true,
+ 250: true,
+ },
+ bearBanned: {
+ 225: true,
+ 229: true,
+ 230: true,
+ 232: true,
+ 234: true,
+ 235: true,
+ 238: true,
+ 240: true,
+ 244: true,
+ 245: true,
+ 248: true,
+ },
+ humanBanned: {
+ 232: true,
+ 233: true,
+ 238: true,
+ 239: true,
+ 242: true,
+ 243: true,
+ 248: true,
+ },
+ nonDamage: {
+ // Some fakes to avoid these
+
+ 54: true, // teleport
+ 217: true, // scroll identify
+ 218: true, // portal scroll
+ 219: true, // I assume this is the book of scroll
+ 220: true, // book portal. Not really a skill you want to use, do you
+ 117: true, // Holy shield. Holy shield it self doesnt give damage
+ 278: true, // venom adds damage, but doesnt do damage on its own
+
+ // Remove all the trap skills, as we prefer to calculate this upon demand
+ 261: true, // lighting bolt
+ 271: true, // lighting sentry
+ 276: true, // Death sentry only works on corpses, we calculate this within attack
+ 262: true, // wake of fire
+ 272: true, // inferno
+ },
+ shiftState: function () {
+ if (GameData.myReference.getState(139)) return "wolf";
+ if (GameData.myReference.getState(140)) return "bear";
+ return "human";
+ },
+ bestForm: function (skillID) {
+ if (this.shiftState() === "human" && this.humanBanned[skillID]) {
+ let highest = { ID: 0, Level: 0 };
+
+ if (!this.wolfBanned[skillID] && this.skillLevel(223) > highest.Level) {
+ highest.ID = 223;
+ highest.Level = this.skillLevel(223);
+ }
+
+ if (!this.bearBanned[skillID] && this.skillLevel(228) > highest.Level) {
+ highest.ID = 228;
+ highest.Level = this.skillLevel(228);
+ }
+
+ return highest.ID;
+ } else if (this.shiftState() === "wolf" && this.wolfBanned[skillID]) {
+ return 223;
+ } else if (this.shiftState() === "bear" && this.bearBanned[skillID]) {
+ return 228;
+ }
+
+ return 0;
+ },
+ physicalAttackDamage: function (skillID) {
+ let dmg = (function () {
+ switch (skillID) {
+ case sdk.skills.Bash:
+ return 45 + (5 + GameData.myReference.getSkill(skillID, 1)) + (5 * GameData.myReference.getSkill(sdk.skills.Stun, 0));
+ case sdk.skills.Stun:
+ return (8 * GameData.myReference.getSkill(sdk.skills.Bash, 0));
+ case sdk.skills.Concentrate:
+ return (65 + (5 * GameData.myReference.getSkill(skillID, 1)) + (5 * GameData.myReference.getSkill(sdk.skills.Bash, 0)) + (10 * GameData.myReference.getSkill(sdk.skills.BattleOrders, 0)));
+ case sdk.skills.LeapAttack:
+ return (70 + (30 * GameData.myReference.getSkill(skillID, 1)) + (10 * GameData.myReference.getSkill(sdk.skills.Leap, 0)));
+ case sdk.skills.Whirlwind:
+ return (8 * GameData.myReference.getSkill(skillID, 1)) - 58;
+ default:
+ return 0;
+ }
+ })();
+
+ // return (((GameData.myReference.getStat(sdk.stats.MaxDamage) + GameData.myReference.getStat(sdk.stats.MinDamage)) / 2) + (GameData.myReference.getStat(sdk.stats.Strength) * dmg)) / 100;
+ return dmg;
+ },
+ dmgModifier: function (skillID, target) {
+ let aps = (typeof target === "number" ? this.averagePackSize(target) : 1);
+ let eliteBonus = (target.spectype && target.isSpecial) ? 1 : 0, hitcap = 1;
+
+ switch (skillID) { // charged bolt/strike excluded, it's so unreliably random
+ case sdk.skills.PoisonJavelin: // poison javalin
+ case sdk.skills.PlagueJavelin: // plague javalin
+ case sdk.skills.ExplodingArrow: // exploding arrow
+ case sdk.skills.ImmolationArrow: // immolation arrow
+ case sdk.skills.FreezingArrow: // freezing arrow
+ case sdk.skills.LightningFury: // lightning fury
+ case sdk.skills.FrostNova: // frost nova
+ case sdk.skills.Nova: // nova
+ case sdk.skills.Meteor: // meteor
+ case sdk.skills.Blizzard: // blizzard
+ case sdk.skills.FrozenOrb: // frozen orb
+ case sdk.skills.PoisonExplosion: // poison explosion
+ case sdk.skills.PoisonNova: // poison nova
+ case sdk.skills.BlessedHammer: // blessed hammer
+ case sdk.skills.WarCry: // war cry
+ case sdk.skills.MoltenBoulder: // molten boulder
+ case sdk.skills.Fissure: // fissure
+ case sdk.skills.Armageddon: // armageddon
+ case sdk.skills.Volcano: // volcano
+ case sdk.skills.Hurricane: // hurricane
+ case sdk.skills.FireBlast: // fireblast
+ case sdk.skills.ChargedBoltSentry: // charged bolt sentry
+ case sdk.skills.WakeofFire: // wake of fire
+ case sdk.skills.GlacialSpike: // glacial spike
+ case sdk.skills.FireBall: // fire ball
+ case sdk.skills.StaticField: // Static field.
+ case sdk.skills.ChargedBolt: // charged bolt
+ hitcap = Infinity;
+ break;
+ case sdk.skills.LightningStrike: // lightning strike
+ hitcap = 1 + this.skillLevel(sdk.skills.LightningStrike);
+ break;
+ case sdk.skills.Teeth: // teeth
+ hitcap = 1 + this.skillLevel(sdk.skills.Teeth);
+ break;
+ case sdk.skills.ChainLightning: // chain lightning
+ hitcap = 5 + ((this.skillLevel(sdk.skills.ChainLightning) / 5) | 0);
+ break;
+ case sdk.skills.ChargedStrike:
+ hitcap = 3 + ((this.skillLevel(sdk.skills.ChargedStrike) / 5) | 0);
+ break;
+ case sdk.skills.Lightning: // lightning
+ case sdk.skills.BoneSpear: // bone spear
+ case sdk.skills.LightningSentry: // lightning sentry
+ case sdk.skills.DeathSentry: // death sentry
+ hitcap = aps ? Math.sqrt(aps / Math.PI) * 2 : 1;
+ break;
+ default:
+ hitcap = 1;
+ break;
+ }
+
+ if (typeof target !== "number") {
+ let unit = Game.getMonster();
+ let radius = this.skillRadius[skillID] || 18;
+
+ if (unit) {
+ do {
+ if (aps >= hitcap) {
+ break;
+ }
+
+ if (target.gid !== unit.gid
+ && getDistance(unit, this.novaLike[skillID] ? GameData.myReference : target) <= radius
+ && isEnemy(unit)) {
+ aps++;
+
+ if (unit.isSpecial) {
+ eliteBonus++;
+ }
+ }
+ } while (unit.getNext());
+ }
+ } else {
+ aps = Math.min(aps, hitcap);
+ }
+
+ aps += eliteBonus * (4 - me.diff) / 2;
+
+ return aps;
+ },
+
+ /**
+ * @typedef skillDmgObj
+ * @property {string} type
+ * @property {number} pmin
+ * @property {number} pmax
+ * @property {number} min
+ * @property {number} max
+ * @property {boolean} [undeadOnly]
+ *
+ * @param {number} skillID
+ * @param {Monster} unit
+ * @returns {skillDmgObj}
+ */
+ skillDamage: function (skillID, unit) {
+ // TODO: caluclate basic attack damage
+ if (skillID === sdk.skills.Attack) {
+ let weapon = me.equipped.get(sdk.body.RightArm);
+ let [dexBonus, strBonus] = [0, 0];
+ const isEth = weapon.ethereal;
+ const minDmg = (GameData.myReference.getStat(sdk.stats.MinDamage) || 1);
+ const maxDmg = (GameData.myReference.getStat(sdk.stats.MaxDamage) || 2);
+ const wepED = (weapon.getStat(sdk.stats.EnhancedDamage) || 0);
+ const isDeadlyStrike = Math.random() < GameData.myReference.getStat(sdk.stats.DeadlyStrike) / 100;
+ console.log(isEth, minDmg, maxDmg, wepED, isDeadlyStrike);
+
+ switch (weapon.itemType) {
+ case sdk.items.type.AmazonBow:
+ case sdk.items.type.Crossbow:
+ case sdk.items.type.Bow:
+ dexBonus = (GameData.myReference.getStat(sdk.stats.Dexterity) / 100);
+
+ break;
+ case sdk.items.type.AmazonJavelin:
+ case sdk.items.type.AmazonSpear:
+ strBonus = (GameData.myReference.getStat(sdk.stats.Strength) / 80);
+ dexBonus = (GameData.myReference.getStat(sdk.stats.Dexterity) / 50);
+
+ break;
+ case sdk.items.type.HandtoHand:
+ case sdk.items.type.Knife:
+ case sdk.items.type.ThrowingAxe:
+ case sdk.items.type.ThrowingKnife:
+ strBonus = (GameData.myReference.getStat(sdk.stats.Strength) / 75);
+ dexBonus = (GameData.myReference.getStat(sdk.stats.Dexterity) / 75);
+
+ break;
+ case sdk.items.type.Hammer:
+ strBonus = (GameData.myReference.getStat(sdk.stats.Strength) / 110);
+
+ break;
+ default:
+ strBonus = (GameData.myReference.getStat(sdk.stats.Strength) / 100);
+
+ break;
+ }
+
+ return {
+ type: "Physical",
+ pmin: (
+ minDmg * (isEth ? 1.5 : 1)
+ // * (wepED > -1 ? (1 + wepED / 100) : 1)
+ * (1 + strBonus + dexBonus)
+ * (isDeadlyStrike ? 2 : 1)
+ ),
+ pmax: (
+ maxDmg * (isEth ? 1.5 : 1)
+ // * (wepED > -1 ? (1 + wepED / 100) : 1)
+ * (1 + strBonus + dexBonus)
+ * (isDeadlyStrike ? 2 : 1)
+ ),
+ min: 0,
+ max: 0
+ }; // short sword, no reqs
+
+ }
+
+ if (this.skillLevel(skillID) < 1) {
+ return {
+ type: this.damageTypes[getBaseStat("skills", skillID, "EType")],
+ pmin: 0,
+ pmax: 0,
+ min: 0,
+ max: 0
+ };
+ }
+
+ let dmg = this.baseSkillDamage(skillID);
+ let mastery = 1, psynergy = 1, synergy = 1, shots = 1, sl = 0;
+
+ if (this.synergyCalc[skillID]) {
+ let sc = this.synergyCalc[skillID];
+
+ for (let c = 0; c < sc.length; c += 2) {
+ sl = this.baseLevel(sc[c]);
+
+ if (skillID === 229 || skillID === 244) {
+ if (sc[c] === 229 || sc[c] === 244) { // molten boulder and volcano
+ psynergy += sl * sc[c + 1]; // they only synergize physical with each other
+ } else {
+ synergy += sl * sc[c + 1]; // all other skills synergize only fire with these skills
+ }
+ } else {
+ psynergy += sl * sc[c + 1];
+ synergy += sl * sc[c + 1];
+ }
+ }
+ }
+
+ if (skillID === 227 || skillID === 237 || skillID === 247) {
+ sl = this.skillLevel(247);
+ psynergy += 0.15 + sl * 0.10;
+ synergy += 0.15 + sl * 0.10;
+ }
+
+ switch (dmg.type) {
+ case "Fire": // fire mastery
+ case "Lightning": // lightning mastery
+ case "Cold": // cold mastery
+ case "Poison": // poison mastery
+ case "Magic": // magic mastery
+ mastery = 1 + GameData.myReference.getStat(this.masteryMap[dmg.type]) / 100;
+ dmg.min *= mastery;
+ dmg.max *= mastery;
+
+ break;
+ }
+
+ dmg.pmin *= psynergy;
+ dmg.pmax *= psynergy;
+
+ if (this.noMinSynergy.indexOf(skillID) < 0) {
+ dmg.min *= synergy;
+ }
+
+ dmg.max *= synergy;
+
+ switch (skillID) {
+ case 102: // holy fire
+ dmg.min *= 6; // weapon damage is 6x the aura damage
+ dmg.max *= 6;
+ break;
+ case 114: // holy freeze
+ dmg.min *= 5; // weapon damage is 5x the aura damage
+ dmg.max *= 5;
+ break;
+ case 118: // holy shock
+ dmg.min *= 6; // weapon damage is 6x the aura damage
+ dmg.max *= 6;
+ break;
+ case 249: // armageddon
+ dmg.pmin = dmg.pmax = 0;
+ break;
+ case 24: // charged strike
+ dmg.max *= 3 + ((this.skillLevel(24) / 5) | 0);
+ }
+
+ dmg.pmin >>= 8;
+ dmg.pmax >>= 8;
+ dmg.min >>= 8;
+ dmg.max >>= 8;
+
+ switch (skillID) {
+ case sdk.skills.ChargedBolt: // more than one bolt can hit but may calc this as splashdamage instead
+ if (unit) {
+ let baseId = getBaseStat("monstats", unit.classid, "baseid");
+ let size = getBaseStat("monstats2", baseId, "sizex");
+ (typeof size !== "number" || size < 1 || size > 3) && (size = 3);
+ let dist = unit.distance;
+ const modifier = size === 1 ? 0.5 : size === 3 ? 1.5 : size === 2 && dist < 5 ? 1.2 : 1;
+ dmg.min *= modifier;
+ dmg.max *= modifier;
+ }
+
+ // need to take into account the amount of bolts released
+ // the size of the unit we are targetting
+ // the distance from the target
+ break;
+ case 59: // blizzard - on average hits twice
+ dmg.min *= 2;
+ dmg.max *= 2;
+ break;
+ case 62: // hydra - 3 heads
+ dmg.min *= 3;
+ dmg.max *= 3;
+ break;
+ case 64: // frozen orb - on average hits ~5 times
+ dmg.min *= 5;
+ dmg.max *= 5;
+ break;
+ case 70: // skeleton - a hit per skeleton
+ sl = this.skillLevel(70);
+ shots = sl < 4 ? sl : (2 + sl / 3) | 0;
+ sl = Math.max(0, sl - 3);
+ dmg.pmin = shots * (dmg.pmin + 1 + this.skillLevel(69) * 2) * (1 + sl * 0.07);
+ dmg.pmax = shots * (dmg.pmax + 2 + this.skillLevel(69) * 2) * (1 + sl * 0.07);
+ break;
+ case 94: // fire golem
+ sl = this.skillLevel(94);
+ dmg.min = [10, 15, 18][me.diff] + dmg.min + (this.stagedDamage(sl + 7, 2, 1, 2, 3, 5, 7) >> 1) * 6; // basically holy fire added
+ dmg.max = [27, 39, 47][me.diff] + dmg.max + (this.stagedDamage(sl + 7, 6, 1, 2, 3, 5, 7) >> 1) * 6;
+ break;
+ case 101: // holy bolt
+ dmg.undeadOnly = true;
+ break;
+ case 112: // blessed hammer
+ sl = this.skillLevel(113);
+
+ if (sl > 0) {
+ mastery = (100 + ((45 + this.skillLevel(113) * 15) >> 1)) / 100; // hammer gets half concentration dmg bonus
+ dmg.min *= mastery;
+ dmg.max *= mastery;
+ }
+
+ break;
+ case sdk.skills.Raven: // raven - a hit per raven
+ shots = Math.min(5, this.skillLevel(221)); // 1-5 ravens
+ dmg.pmin *= shots;
+ dmg.pmax *= shots;
+ break;
+ case sdk.skills.SummonSpiritWolf: // spirit wolf - a hit per wolf
+ shots = Math.min(5, this.skillLevel(227));
+ dmg.pmin *= shots;
+ dmg.pmax *= shots;
+ break;
+ case sdk.skills.SummonDireWolf: // dire wolf - a hit per wolf
+ shots = Math.min(3, this.skillLevel(237));
+ dmg.pmin *= shots;
+ dmg.pmax *= shots;
+ break;
+ case sdk.skills.Twister: // twister
+ dmg.pmin *= 3;
+ dmg.pmax *= 3;
+ break;
+ case 261: // charged bolt sentry
+ case 262: // wake of fire
+ case 271: // lightning sentry
+ case 272: // inferno sentry
+ case 276: // death sentry
+ dmg.min *= 5; // can have 5 traps out at a time
+ dmg.max *= 5;
+ break;
+ case sdk.skills.StaticField:
+ if (!(unit instanceof Unit)) {
+ break;
+ }
+ // No cap in classic
+ let staticCap = (me.gametype === sdk.game.gametype.Classic
+ ? 0 : [0, 33, 50][me.diff]);
+ const [monsterId, areaId] = [unit.classid, unit.area];
+ let percentLeft = (unit.hp * 100 / unit.hpmax);
+ if (staticCap > percentLeft) {
+ break;
+ }
+
+ const maxReal = this.monsterMaxHP(monsterId, areaId, unit.charlvl - this.monsterLevel(monsterId, areaId));
+ let hpReal = maxReal / 100 * percentLeft;
+ let potencialDmg = (hpReal / 100 * percentLeft) * 0.25;
+
+ let tmpDmg = (maxReal / 100 * percentLeft) * (0.25);
+
+ // We do need to calculate the extra damage, or less damage due to resistance
+ let resist = this.monsterResist(unit, "Lightning");
+ let pierce = GameData.myReference.getStat(this.pierceMap.Lightning);
+
+ let conviction = this.getConviction();
+ // if (conviction && !unit.getState(sdk.states.Conviction)) conviction = 0; //ToDo; enable when fixed telestomp
+ resist -= (resist >= 100 ? conviction / 5 : conviction);
+ resist = (resist < 100 ? Math.max(-100, resist - pierce) : 100);
+ tmpDmg = potencialDmg * ((100 - resist) / 100);
+ const percentageDamage = 100 / maxReal * tmpDmg;
+
+ let avgDmg = tmpDmg;
+ let overCap = percentLeft - staticCap - percentageDamage;
+ if (overCap < 0) {
+ let maxDmgPercentage = percentageDamage - Math.abs(overCap);
+ avgDmg = maxReal / 100 * maxDmgPercentage;
+ }
+ avgDmg = avgDmg > 0 && avgDmg || 0;
+ //console.log('Static will chop off -> ' + (100 / maxReal * avgDmg) + '%');
+ dmg.min = avgDmg;
+ dmg.max = avgDmg;
+ break;
+ }
+
+ dmg.pmin |= 0;
+ dmg.pmax |= 0;
+ dmg.min |= 0;
+ dmg.max |= 0;
+
+ return dmg;
+ },
+
+ /**
+ * Calculate actual average damage this skill does taking into account splash/range of skill
+ * @param {number} skillID
+ * @param {Monster | number | string} unit
+ * @returns {number}
+ * @todo
+ * - build me metadata - then use it to calulate a range of skills rather than redo the exact same calculations.
+ * example: trying to check the damage of blizard and then frozen orb,
+ * currently it would check our stats, then check amp and conviction - those could all be pre-built as they aren't going to change
+ */
+ avgSkillDamage: function (skillID, unit) {
+ if (!skillID || !unit || !Skill.canUse(skillID)) return 0;
+ const ampDmg = Skill.canUse(sdk.skills.AmplifyDamage)
+ ? 100
+ : (Skill.canUse(sdk.skills.Decrepify)
+ ? 50
+ : 0);
+
+ /**
+ * @param {skillDmgObj} skillData
+ * @param {Monster | number | string} unit
+ * @returns
+ */
+ const getTotalDmg = function (skillData, unit) {
+ const mon = (typeof unit === "number"
+ ? MonsterData.get(unit)
+ : MonsterData.get(unit.classid));
+ const isUndead = mon.Undead;
+ const conviction = GameData.getConviction();
+ let totalDmg = 0;
+ let avgPDmg = (skillData.pmin + skillData.pmax) / 2;
+ let avgDmg = (skillData.min + skillData.max) / 2;
+
+ if (avgPDmg > 0) {
+ let presist = GameData.monsterResist(unit, "Physical");
+ presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
+ presist = Math.max(-100, Math.min(100, presist));
+ totalDmg += avgPDmg * (100 - presist) / 100;
+ }
+ if (avgDmg > 0 && (!isUndead || !skillData.undeadOnly)) {
+ let resist = GameData.monsterResist(unit, skillData.type);
+ let pierce = GameData.myReference.getStat(GameData.pierceMap[skillData.type]);
+ if (GameData.convictionEligible[skillData.type]) {
+ resist -= (resist >= 100 ? conviction / 5 : conviction);
+ }
+ resist = (resist < 100 ? Math.max(-100, resist - pierce) : 100);
+ totalDmg += avgDmg * (100 - resist) / 100;
+ }
+ if (typeof unit === "object") {
+ let { classid, area, charlvl } = unit;
+ let adjustLevel = charlvl - GameData.monsterLevel(classid, area);
+ let currentHealth = GameData.monsterMaxHP(classid, area, adjustLevel) / 100 * (unit.hp * 100 / unit.hpmax);
+ if (currentHealth < totalDmg) {
+ totalDmg = currentHealth;
+ }
+ }
+ return totalDmg;
+ };
+
+ /**
+ * @param {number} skill
+ * @param {number} splash
+ * @param {Monster} target
+ * @returns {number}
+ */
+ const calculateSplashDamage = function (skill, splash, target) {
+ return getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && getDistance(target, mon) < splash;
+ })
+ .reduce(function (acc, cur) {
+ let _a = GameData.skillDamage(skill, cur);
+ return acc + getTotalDmg(_a, cur);
+ }, 0);
+ };
+
+ /**
+ * Orb travels a 9 1/3 yard straight path and sends out bolts covering a 20yard radius. We need to create
+ * a box and check all monsters who are within that box. We can then calculate the damage of the orb
+ * Each bolt can hit a monster only once. Maximum of 46 bolts can be fired.
+ * @param {Monster} target
+ */
+ const calculateOrbPathDamage = function (target) {
+ let totalDmg = 0;
+ const meVec = new Vector(me.x, me.y);
+ const targetVec = new Vector(target.x, target.y);
+ const orbVec = targetVec.subtract(meVec).normalize().multiply(9.33);
+ const orbPath = Vector.path(meVec, orbVec);
+ let units = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ if (!mon.attackable) return false;
+ const distInPath = orbPath
+ .toSorted(function (a, b) {
+ return getDistance(mon, a) - getDistance(mon, b);
+ }).first();
+ if (!distInPath) return false;
+ const distanceFromPath = getDistance(mon, distInPath);
+ if (distanceFromPath > 20) {
+ mon._modifier = 5;
+ } else if (distanceFromPath > 15) {
+ mon._modifier = 4;
+ } else if (distanceFromPath > 10) {
+ mon._modifier = 3;
+ } else if (distanceFromPath > 0) {
+ mon._modifier = 1;
+ }
+ return distanceFromPath < 25;
+ })
+ .sort(function (a, b) {
+ const distAFromTarget = getDistance(target, a);
+ const distAFromMe = getDistance(me, a);
+ const distBFromTarget = getDistance(target, b);
+ const distBFromMe = getDistance(me, b);
+
+ return (distAFromTarget + distAFromMe) - (distBFromTarget + distBFromMe);
+ });
+ for (let i = 0; i < units.length; i++) {
+ if (units[i] !== undefined) {
+ let _a = GameData.skillDamage(sdk.skills.FrozenOrb, units[i]);
+ totalDmg += getTotalDmg(_a, units[i]) / units[i]._modifier;
+ }
+ }
+ return totalDmg;
+ };
+
+ /**
+ * @param {number} skill
+ * @param {Monster} target
+ * @returns {number}
+ */
+ const calculateChainDamage = function (skill, target) {
+ skill === undefined && (skill = -1);
+ let rawDmg = 0, totalDmg = 0, range = 0, hits = 0;
+ switch (skill) {
+ case sdk.skills.ChainLightning:
+ hits = Math.round((25 + me.getSkill(sdk.skills.ChainLightning, sdk.skills.subindex.SoftPoints)) / 5);
+ range = 13;
+ break;
+ }
+ let units = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && getDistance(mon, target) < range;
+ })
+ .sort(function (a, b) {
+ return getDistance(target, a) - getDistance(target, b);
+ });
+ if (units.length === 1) {
+ rawDmg = GameData.skillDamage(skill, target);
+ return getTotalDmg(rawDmg, target);
+ } else {
+ // console.log("Units to check: " + units.length);
+ for (let i = 0; i < units.length; i++) {
+ if (units[i] !== undefined) {
+ rawDmg = GameData.skillDamage(skill, units[i]);
+ totalDmg += getTotalDmg(rawDmg, units[i]);
+ if (i > hits) { break; }
+ } else {
+ units.splice(i, 1);
+ i -= 1;
+ }
+ }
+ return totalDmg;
+ }
+ };
+
+ const calculateRawStaticDamage = function (distanceUnit) {
+ distanceUnit === undefined && (distanceUnit = me);
+ if (!Skill.canUse(sdk.skills.StaticField)) return 0;
+ const range = Skill.getRange(sdk.skills.StaticField);
+ const cap = (me.gametype === sdk.game.gametype.Classic ? 1 : [1, 25, 50][me.diff]);
+ const pierce = me.getStat(sdk.stats.PierceLtng);
+ return getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && getDistance(mon, distanceUnit) < range;
+ }).reduce(function (acc, unit) {
+ let classId = unit.classid, areaId = unit.area;
+ let maxHealth = GameData.monsterAvgHP(classId, areaId, unit.charlvl - GameData.monsterLevel(classId, areaId));
+ let currentHealth = maxHealth / 100 * (unit.hp * 100 / unit.hpmax), baseDamage = currentHealth * 0.25;
+ // monsterRes already considers conviction state
+ let monsterRes = unit.getStat(sdk.stats.LightResist);
+ let totalRes = Math.min(100, Math.max(-100, monsterRes - pierce));
+ // calculate the actual damage we do
+ let potentialDamage = baseDamage / (100 / (100 - totalRes));
+ let cappedAtHealth = maxHealth / 100 * cap;
+ // cap max damage
+ let actualDamage = currentHealth - Math.max(cappedAtHealth, (currentHealth - potentialDamage));
+ return acc + (actualDamage);
+ }, 0);
+ };
+
+ /**
+ * @param {number} skill
+ * @param {Monster} target
+ * @returns {number}
+ */
+ const calculateThroughDamage = function (skill, target) {
+ // determine maximum potential distance of this missile
+ // build points from me -> monster -> max distance
+ // iterate points checking if any monsters are in the path
+ // check collision at each point and break if we encounter a missisle blocker
+ // special considerations for molten boulder:
+ // - check monster size, based on size the boulder may knock back or go through them
+ // - if we encounter a collision that causes the boulder to burst, add explosion damage
+ //
+ const range = Skill.getRange(skill);
+ let totalDmg = 0;
+ const meVec = new Vector(me.x, me.y);
+ const targetVec = new Vector(target.x, target.y);
+ const misVec = targetVec.subtract(meVec).normalize().multiply(range);
+ const missilePath = Vector.path(meVec, misVec);
+ missilePath.sort(function (a, b) {
+ return getDistance(me, a) - getDistance(me, b);
+ });
+ // check if we run into any cast-blockers
+ let cBlockIdx = missilePath.findIndex(function (point) {
+ let coll = getCollision(me.area, point.x, point.y);
+ return (coll & sdk.collision.BlockMissile);
+ });
+ if (cBlockIdx > -1) {
+ missilePath.splice(cBlockIdx);
+ }
+ let units = getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ if (!mon.attackable) return false;
+ const distInPath = missilePath
+ .toSorted(function (a, b) {
+ return getDistance(mon, a) - getDistance(mon, b);
+ }).first();
+ if (!distInPath) return false;
+ const distanceFromPath = getDistance(mon, distInPath);
+ return distanceFromPath <= 3;
+ })
+ .sort(function (a, b) {
+ return getDistance(me, a) - getDistance(me, b);
+ });
+ for (let i = 0; i < units.length; i++) {
+ if (units[i] !== undefined) {
+ let _a = GameData.skillDamage(skill, units[i]);
+ totalDmg += getTotalDmg(_a, units[i]);
+ }
+ }
+ return totalDmg;
+ };
+
+ /**
+ *
+ * @param {Monster} unit
+ */
+ const calcVolcanoDamage = function (unit) {
+ let velocity = unit.currentVelocity;
+ /** @type {skillDmgObj} */
+ let baseDmg = GameData.skillDamage(sdk.skills.Volcano, unit);
+ // since these are random, lets take them into account but not at their full value
+ let missleDmg = Object.assign({}, baseDmg);
+ missleDmg.pmin /= 2;
+ missleDmg.pmax /= 2;
+ missleDmg.min /= 2;
+ missleDmg.max /= 2;
+ // sorta guess work for now, needs improvment to really figure out on average how many times the actual
+ // volcano damages the monster cast on based on size/speed
+ let modifier = (!unit.isMoving || velocity === 1) ? 5 : velocity === 2 ? 3 : 1;
+ if (modifier > 1) {
+ baseDmg.pmin *= modifier;
+ baseDmg.pmax *= modifier;
+ baseDmg.min *= modifier;
+ baseDmg.max *= modifier;
+ }
+
+ // sum the total in the range of the volcano missiles
+ // what about monsters just directly on the volcano?
+ return getUnits(sdk.unittype.Monster)
+ .filter(function (mon) {
+ return mon.attackable && getDistance(unit, mon) < 15;
+ })
+ .reduce(function (acc, cur) {
+ return acc + getTotalDmg(missleDmg, cur);
+ }, getTotalDmg(baseDmg, unit));
+ };
+
+ /**
+ * @todo some skills need special handling
+ * - Bone spear and Lightning both pierce enemies in a straight path, need to calculate include monsters in that path
+ * to the total damage done as this can make the difference between wanting to use this skill vs another
+ * - Fire Wall is similar only seems to be a random angle
+ * - Inferno/Artic blast same as bonespear/lightning
+ * - Molten boulder needs to take into account monster size and angle of cast
+ * - Zeal, can hit same enemy multiple times or multiple enemies, would change total damage applied based on enemy targetted so needs to be handled
+ * Others?
+ */
+ switch (skillID) {
+ case sdk.skills.FrozenOrb:
+ return calculateOrbPathDamage(unit);
+ case sdk.skills.Blizzard:
+ case sdk.skills.Meteor:
+ case sdk.skills.FireBall:
+ case sdk.skills.GlacialSpike:
+ case sdk.skills.ChargedBolt:
+ case sdk.skills.Fissure:
+ let { x, y } = unit;
+ let rad = skillID === sdk.skills.Volcano ? 15 : skillID === sdk.skills.Fissure ? 10 : 5;
+
+ if (!Attack.validSpot(x, y, skillID, unit.classid)) {
+ return 0;
+ }
+
+ return calculateSplashDamage(skillID, rad, unit);
+ case sdk.skills.Volcano:
+ if (!Attack.validSpot(unit.x, unit.y, skillID, unit.classid)) {
+ return 0;
+ }
+
+ return calcVolcanoDamage(unit);
+ case sdk.skills.FrostNova:
+ case sdk.skills.Nova:
+ return calculateSplashDamage(skillID, 6, unit);
+ case sdk.skills.StaticField:
+ return calculateRawStaticDamage(unit);
+ case sdk.skills.ChainLightning:
+ return calculateChainDamage(skillID, unit);
+ default:
+ return getTotalDmg(this.skillDamage(skillID, unit), unit);
+ }
+ },
+ allSkillDamage: function (unit) {
+ let skills = {};
+ let self = this;
+ GameData.myReference.getSkill(4).forEach(function (skill) {
+ if (self.nonDamage.hasOwnProperty(skill[0])) {
+ return false; // Doesnt do damage
+ }
+ return skills[skill[0]] = self.skillDamage(skill[0], unit);
+ });
+
+ return skills;
+ },
+ convictionEligible: {
+ Fire: true,
+ Lightning: true,
+ Cold: true,
+ },
+ lowerResistEligible: {
+ Fire: true,
+ Lightning: true,
+ Cold: true,
+ Poison: true,
+ },
+ resistMap: {
+ Physical: 36,
+ Fire: 39,
+ Lightning: 41,
+ Cold: 43,
+ Poison: 45,
+ Magic: 37,
+ },
+ masteryMap: {
+ Fire: 329,
+ Lightning: 330,
+ Cold: 331,
+ Poison: 332,
+ Magic: 357,
+ },
+ pierceMap: {
+ Fire: 333,
+ Lightning: 334,
+ Cold: 335,
+ Poison: 336,
+ Magic: 358,
+ },
+ ignoreSkill: {
+ 40: true,
+ 50: true,
+ 60: true,
+ },
+ buffs: {
+ 8: 1,
+ 9: 1,
+ 13: 1,
+ 17: 1,
+ 18: 1,
+ 23: 1,
+ 28: 1,
+ 29: 1,
+ 32: 1,
+ 37: 1,
+ 40: 2,
+ 46: 1,
+ 50: 2,
+ 52: 1,
+ 57: 1,
+ 58: 1,
+ 60: 2,
+ 61: 1,
+ 63: 1,
+ 65: 1,
+ 68: 1,
+ 69: 1,
+ 79: 1,
+ 89: 1,
+ 98: 3,
+ 99: 3,
+ 100: 3,
+ 102: 3,
+ 103: 3,
+ 104: 3,
+ 105: 3,
+ 108: 3,
+ 109: 3,
+ 110: 3,
+ 113: 3,
+ 114: 3,
+ 115: 3,
+ 118: 3,
+ 119: 3,
+ 120: 3,
+ 122: 3,
+ 123: 3,
+ 124: 3,
+ 125: 3,
+ 127: 1,
+ 128: 1,
+ 129: 1,
+ 134: 1,
+ 135: 1,
+ 136: 1,
+ 138: 1,
+ 141: 1,
+ 145: 1,
+ 148: 1,
+ 149: 1,
+ 153: 1,
+ 155: 1,
+ 221: 1,
+ 222: 4,
+ 223: 5,
+ 224: 1,
+ 226: 6,
+ 227: 7,
+ 228: 5,
+ 231: 4,
+ 235: 1,
+ 236: 6,
+ 237: 7,
+ 241: 4,
+ 246: 6,
+ 247: 7,
+ 249: 1,
+ 250: 1,
+ 258: 8,
+ 267: 8,
+ 268: 9,
+ 279: 9,
+ },
+ preAttackable: [
+ sdk.skills.MagicArrow, sdk.skills.FireArrow,
+ sdk.skills.MultipleShot, sdk.skills.ExplodingArrow,
+ sdk.skills.IceArrow, sdk.skills.GuidedArrow,
+ sdk.skills.ImmolationArrow, sdk.skills.Strafe,
+ sdk.skills.PlagueJavelin, sdk.skills.LightningFury,
+ sdk.skills.FireBolt, sdk.skills.Inferno,
+ sdk.skills.Blaze, sdk.skills.FireBall,
+ sdk.skills.FireWall, sdk.skills.Meteor, sdk.skills.Hydra,
+ sdk.skills.ChargedBolt, sdk.skills.Nova,
+ sdk.skills.Lightning, sdk.skills.ChainLightning,
+ sdk.skills.IceBolt, sdk.skills.FrostNova,
+ sdk.skills.IceBlast, sdk.skills.Blizzard, sdk.skills.FrozenOrb,
+ 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,
+ sdk.skills.Teeth, sdk.skills.BoneSpear, sdk.skills.PoisonNova,
+ sdk.skills.BlessedHammer,
+ sdk.skills.WarCry,
+ sdk.skills.Twister, sdk.skills.Tornado,
+ sdk.skills.FireBlast, sdk.skills.ShockWeb,
+ ],
+ monsterResist: function (unit, type) {
+ let stat = this.resistMap[type];
+ return stat ? (unit.getStat ? unit.getStat(stat) : MonsterData.get(unit)[type]) : 0;
+ },
+ getConviction: function () {
+ let merc = GameData.myReference.getMerc();
+ let sl = this.skillLevel(123); // conviction
+ /** @param {ItemUnit} item */
+ function isInfinity (item) {
+ return item.prefixnum === sdk.locale.items.Infinity;
+ }
+ if (( // Either me, or merc is wearing a conviction
+ merc && merc.getItemsEx().filter(isInfinity).first()
+ || GameData.myReference.getItemsEx(-1, 1).filter(isInfinity).first())) {
+ sl = 12;
+ }
+ return sl > 0 ? Math.min(150, 30 + (sl - 1) * 5) : 0;
+ },
+ getAmp: function () {
+ return this.skillLevel(66) ? 100 : (this.skillLevel(87) ? 50 : 0);
+ },
+ monsterEffort: function (unit, areaID, skillDamageInfo = undefined, parent = undefined, preattack = false, all = false) {
+ let buffDmg = [];
+ const allData = [];
+ const buffDamageInfo = {};
+ const newSkillDamageInfo = {};
+ const eret = { effort: Infinity, skill: -1, type: "Physical" };
+ const useCooldown = (typeof unit === "number" ? false : Boolean(me.skillDelay));
+ const hp = this.monsterMaxHP(typeof unit === "number" ? unit : unit.classid, areaID);
+ const conviction = this.getConviction(), ampDmg = this.getAmp();
+ const isUndead = (typeof unit === "number" ? MonsterData.get(unit).Undead : MonsterData.get(unit.classid).Undead);
+ skillDamageInfo = skillDamageInfo || this.allSkillDamage(unit);
+ console.debug(unit, "---", hp);
+ // if (conviction && unit instanceof Unit && !unit.getState(sdk.states.Conviction)) conviction = 0; //ToDo; enable when fixed telestomp
+
+ for (let sk in skillDamageInfo) {
+ if (this.buffs[sk]) {
+ if (typeof unit === "number") {
+ buffDmg[this.buffs[sk]] = 0;
+ buffDamageInfo[sk] = skillDamageInfo[sk];
+ }
+ } else {
+ newSkillDamageInfo[sk] = skillDamageInfo[sk];
+ }
+ }
+
+ skillDamageInfo = newSkillDamageInfo;
+
+ for (let sk in buffDamageInfo) {
+ // static field has a fix'd ceiling, calculated already
+ if ([sdk.skills.StaticField].indexOf(sk) !== -1) continue;
+
+ let avgPDmg = (buffDamageInfo[sk].pmin + buffDamageInfo[sk].pmax) / 2;
+ let avgDmg = (buffDamageInfo[sk].min + buffDamageInfo[sk].max) / 2;
+ let tmpDmg = 0;
+
+ if (avgPDmg > 0) {
+ let presist = this.monsterResist(unit, "Physical");
+
+ presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
+ presist = Math.max(-100, Math.min(100, presist));
+ tmpDmg += avgPDmg * (100 - presist) / 100;
+ }
+
+ if (avgDmg > 0 && (!isUndead || !buffDamageInfo[sk].undeadOnly) && sk !== sdk.skills.StaticField) {
+ let resist = this.monsterResist(unit, buffDamageInfo[sk].type);
+ let pierce = GameData.myReference.getStat(this.pierceMap[buffDamageInfo[sk].type]);
+
+ if (this.convictionEligible[buffDamageInfo[sk].type]) {
+ resist -= (resist >= 100 ? conviction / 5 : conviction);
+ }
+
+ if (resist < 100) {
+ resist = Math.max(-100, resist - pierce);
+ } else {
+ resist = 100;
+ }
+
+ tmpDmg += avgDmg * (100 - resist) / 100;
+ }
+
+ if (this.buffs[sk] === 1) {
+ buffDmg[this.buffs[sk]] += tmpDmg;
+ } else {
+ buffDmg[this.buffs[sk]] = Math.max(buffDmg[this.buffs[sk]], tmpDmg);
+ }
+ }
+
+ buffDmg = buffDmg.reduce(function (t, v) {
+ return t + v;
+ }, 0);
+
+ for (let sk in skillDamageInfo) {
+ if (preattack && this.preAttackable.indexOf(parseInt(sk)) === -1) continue; // cant preattack this skill
+ if (!this.ignoreSkill[sk] && (!useCooldown || !this.skillCooldown(sk | 0))) {
+ let avgPDmg = (skillDamageInfo[sk].pmin + skillDamageInfo[sk].pmax) / 2, totalDmg = buffDmg;
+ let avgDmg = (skillDamageInfo[sk].min + skillDamageInfo[sk].max) / 2;
+
+ if (avgPDmg > 0 && sk !== sdk.skills.StaticField) {
+ let presist = this.monsterResist(unit, "Physical");
+
+ presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
+ presist = Math.max(-100, Math.min(100, presist));
+ totalDmg += avgPDmg * (100 - presist) / 100;
+ }
+
+ if (avgDmg > 0 && (!isUndead || !skillDamageInfo[sk].undeadOnly)) {
+ let resist = this.monsterResist(unit, skillDamageInfo[sk].type);
+ let pierce = GameData.myReference.getStat(this.pierceMap[skillDamageInfo[sk].type]);
+
+ if (this.convictionEligible[skillDamageInfo[sk].type]) {
+ resist -= (resist >= 100 ? conviction / 5 : conviction);
+ }
+
+ if (resist < 100) {
+ resist = Math.max(-100, resist - pierce);
+ } else {
+ resist = 100;
+ }
+
+ totalDmg += sk !== sdk.skills.StaticField
+ && 0
+ || avgDmg * (100 - resist) / 100;
+
+ }
+ console.debug(hp, "---/", totalDmg);
+ let tmpEffort = Math.ceil(hp / totalDmg);
+
+ tmpEffort /= this.dmgModifier(sk | 0, parent || unit);
+
+ // care for mana
+ if (GameData.myReference.mp < Skill.getManaCost(sk)) {
+ tmpEffort *= 5; // More effort in a skill we dont have mana for
+ }
+
+ // check valid location, blizzard and meteor fail over lava
+ if (typeof unit === "object") {
+ if ([sdk.skills.Blizzard, sdk.skills.Meteor].indexOf(sk) && !Attack.validSpot(unit.x, unit.y, sk, unit.classid)) {
+ tmpEffort *= 5;
+ }
+ }
+
+ // Use less cool down spells, if something better is around
+ /* if (this.skillCooldown(sk | 0)) {
+ console.log("tmpEffort: " + (Math.ceil(tmpEffort)) + " eretEffor: " + eret.effort);
+ tmpEffort *= 5;
+ } */
+ if (tmpEffort <= eret.effort) {
+ eret.effort = tmpEffort;
+ eret.skill = sk | 0;
+ eret.type = skillDamageInfo[eret.skill].type;
+ eret.name = getSkillById(eret.skill);
+ eret.cooldown = this.skillCooldown(sk | 0);
+ if (all) {
+ allData.unshift(copyObj(eret));
+ }
+ }
+ }
+ }
+ console.log(eret, allData);
+ if (all && allData.length) return allData;
+ if (eret.skill >= 0) return eret;
+ return null;
+ },
+ effectiveMonsterEffort: function (unit, areaID) {
+ if (unit === undefined) return null;
+ areaID === undefined && (areaID = me.area);
+ const allData = [];
+ const buffDamageInfo = {};
+ const newSkillDamageInfo = {};
+ let buffDmg = [];
+ let eret = { effort: Infinity, skill: -1, type: "Physical" };
+ let hp = this.monsterMaxHP(typeof unit === "number" ? unit : unit.classid, areaID);
+ let conviction = this.getConviction(), ampDmg = this.getAmp();
+ let isUndead = (typeof unit === "number" ? MonsterData.get(unit).Undead : MonsterData.get(unit.classid).Undead);
+ let skillDamageInfo = this.allSkillDamage(unit);
+
+ for (let sk in skillDamageInfo) {
+ if (this.buffs[sk]) {
+ if (typeof unit === "number") {
+ buffDmg[this.buffs[sk]] = 0;
+ buffDamageInfo[sk] = skillDamageInfo[sk];
+ }
+ } else {
+ newSkillDamageInfo[sk] = skillDamageInfo[sk];
+ }
+ }
+
+ skillDamageInfo = newSkillDamageInfo;
+
+ for (let sk in buffDamageInfo) {
+ // static field has a fix'd ceiling, calculated already
+ if ([sdk.skills.StaticField].indexOf(sk) !== -1) continue;
+
+ let avgPDmg = (buffDamageInfo[sk].pmin + buffDamageInfo[sk].pmax) / 2;
+ let avgDmg = (buffDamageInfo[sk].min + buffDamageInfo[sk].max) / 2;
+ let tmpDmg = 0;
+
+ if (avgPDmg > 0) {
+ let presist = this.monsterResist(unit, "Physical");
+
+ presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
+ presist = Math.max(-100, Math.min(100, presist));
+ tmpDmg += avgPDmg * (100 - presist) / 100;
+ }
+
+ if (avgDmg > 0 && (!isUndead || !buffDamageInfo[sk].undeadOnly) && sk !== sdk.skills.StaticField) {
+ let resist = this.monsterResist(unit, buffDamageInfo[sk].type);
+ let pierce = GameData.myReference.getStat(this.pierceMap[buffDamageInfo[sk].type]);
+
+ if (this.convictionEligible[buffDamageInfo[sk].type]) {
+ resist -= (resist >= 100 ? conviction / 5 : conviction);
+ }
+
+ if (resist < 100) {
+ resist = Math.max(-100, resist - pierce);
+ } else {
+ resist = 100;
+ }
+
+ tmpDmg += avgDmg * (100 - resist) / 100;
+ }
+
+ if (this.buffs[sk] === 1) {
+ buffDmg[this.buffs[sk]] += tmpDmg;
+ } else {
+ buffDmg[this.buffs[sk]] = Math.max(buffDmg[this.buffs[sk]], tmpDmg);
+ }
+ }
+
+ buffDmg = buffDmg.reduce(function (t, v) {
+ return t + v;
+ }, 0);
+
+ for (let sk in skillDamageInfo) {
+ if (!this.ignoreSkill[sk]) {
+ let avgPDmg = (skillDamageInfo[sk].pmin + skillDamageInfo[sk].pmax) / 2, totalDmg = buffDmg;
+ let avgDmg = (skillDamageInfo[sk].min + skillDamageInfo[sk].max) / 2;
+
+ if (avgPDmg > 0 && sk !== sdk.skills.StaticField) {
+ let presist = this.monsterResist(unit, "Physical");
+
+ presist -= (presist >= 100 ? ampDmg / 5 : ampDmg);
+ presist = Math.max(-100, Math.min(100, presist));
+ totalDmg += avgPDmg * (100 - presist) / 100;
+ }
+
+ if (avgDmg > 0 && (!isUndead || !skillDamageInfo[sk].undeadOnly)) {
+ let resist = this.monsterResist(unit, skillDamageInfo[sk].type);
+ let pierce = GameData.myReference.getStat(this.pierceMap[skillDamageInfo[sk].type]);
+
+ if (this.convictionEligible[skillDamageInfo[sk].type]) {
+ resist -= (resist >= 100 ? conviction / 5 : conviction);
+ }
+
+ if (resist < 100) {
+ resist = Math.max(-100, resist - pierce);
+ } else {
+ resist = 100;
+ }
+
+ totalDmg += sk !== sdk.skills.StaticField && 0 || avgDmg * (100 - resist) / 100;
+
+ }
+
+ let tmpEffort = Math.ceil(hp / totalDmg);
+
+ tmpEffort /= this.dmgModifier(sk | 0, unit);
+
+ // care for mana
+ if (GameData.myReference.mp < Skill.getManaCost(sk)) {
+ tmpEffort *= 5; // More effort in a skill we dont have mana for
+ }
+
+ // check valid location, blizzard and meteor fail over lava
+ if ([sdk.skills.Blizzard, sdk.skills.Meteor].indexOf(sk) && !Attack.validSpot(unit.x, unit.y, sk, unit.classid)) {
+ tmpEffort *= 5;
+ }
+
+ if (tmpEffort <= eret.effort) {
+ eret.effort = tmpEffort;
+ eret.skill = sk | 0;
+ eret.type = skillDamageInfo[eret.skill].type;
+ eret.name = getSkillById(eret.skill);
+ eret.cooldown = this.skillCooldown(sk | 0);
+ allData.unshift(copyObj(eret));
+ }
+ }
+ }
+ if (allData.length) return allData;
+ if (eret.skill >= 0) return eret;
+ return null;
+ },
+ areaEffort: function (areaID, skills) {
+ let effortpool = 0, raritypool = 0, dmgAcc = 0;
+
+ skills = skills || this.allSkillDamage();
+
+ AreaData.get(areaID).forEachMonsterAndMinion(function (mon, rarity, parent) {
+ effortpool += rarity * GameData.monsterEffort(mon.Index, areaID, skills, parent && parent.Index).effort;
+ raritypool += rarity;
+
+ dmgAcc += rarity * GameData.monsterAvgDmg(mon.Index, areaID);
+ });
+
+ // console.debug('avg dmg '+ AreaData.get(areaID).LocaleString+' -- ' + dmgAcc+' -- ' + avgDmg);
+
+ return (raritypool ? effortpool / raritypool : Infinity);
+ },
+ areaSoloExp: function (areaID, skills) {
+ let procentageBroke = ((100 - Math.min(100, Math.max(0, (100 / (Config.LowGold || 1) * me.gold)))));
+ let brokeness = 1 + (procentageBroke / 100 / 3 * 1);
+ let effortpool = 0, raritypool = 0, dmgAcc = 0;
+
+ skills = skills || this.allSkillDamage();
+ AreaData.get(areaID).forEachMonsterAndMinion(function (mon, rarity, parent) {
+ let monExp = GameData.monsterExp(mon.Index, areaID);
+ let lvlMod = GameData.levelModifier(GameData.myReference.charlvl, GameData.monsterLevel(mon.Index, areaID));
+ let monEffort = GameData.monsterEffort(mon.Index, areaID, skills, parent && parent.Index).effort;
+ effortpool += rarity * monExp * lvlMod / monEffort;
+ raritypool += rarity;
+
+ dmgAcc += (rarity * GameData.monsterAvgDmg(mon.Index, areaID));
+ });
+
+ let log = 1, avgDmg = 0;
+ if (brokeness !== 1) {
+ log = ((5 - Math.log(areaID)) * (brokeness * 0.6));
+ avgDmg = (raritypool ? dmgAcc / raritypool : Infinity) * log;
+ }
+
+ return (raritypool ? effortpool / raritypool : 0) - (avgDmg);
+ },
+ mostUsedSkills: function (force = false) {
+ if (!force && GameData.myReference.hasOwnProperty("__cachedMostUsedSkills")
+ && GameData.myReference.__cachedMostUsedSkills) {
+ return GameData.myReference.__cachedMostUsedSkills;
+ }
+
+ const effort = [], uniqueSkills = [];
+ for (let i = 50; i < 120; i++) {
+ try {
+ effort.push(GameData.monsterEffort(i, sdk.areas.ThroneofDestruction));
+ } catch (e) {
+ /*dontcare*/
+ }
+ }
+
+ effort
+ .filter(e => e !== null && typeof e === "object" && e.hasOwnProperty("skill"))
+ .filter(x => GameData.myReference.getSkill(x.skill, 0)) // Only skills where we have hard points in
+ .filter(x => Skills.class[x.skill] < 7) // Needs to be a skill of a class, not my class but a class
+ .map(x =>
+ // Search for this unique skill
+ (
+ uniqueSkills.find(u => u.skillId === x.skill)
+ // Or add it and return the value
+ || (
+ (
+ uniqueSkills.push({ skillId: x.skill, used: 0 })
+ && false
+ )
+ || uniqueSkills[uniqueSkills.length - 1]
+ )
+ ).used++ && false
+ // In the end always return x
+ || x
+ );
+
+ return (GameData.myReference.__cachedMostUsedSkills = uniqueSkills.sort((a, b) => b.used - a.used));
+ },
+
+ attackStartingFrame: function (weaponClass, charClass = GameData.myReference.classid) {
+ // amazon and sorceress only
+ /*
+ Weapon: hth 1hs 2hs 1ht 2ht stf bow xbw
+ StartingFrame: 1 2 2 2 2 2 0 0
+ */
+ if (charClass === sdk.player.class.Amazon || charClass === sdk.player.class.Sorceress) {
+ if (weaponClass === "hth") return 1;
+ if (["1hs", "2hs", "1ht", "2ht", "stf"].includes(weaponClass)) return 2;
+ }
+ return 0;
+ },
+
+ /*weaponSpeedModifier: function (weapon1Code, charClass = GameData.myReference.classid, weapon2Code = null) {
+ let weapons = new CSV("sdk/txt/weapons.txt");
+ let weapon1Data = weapons.findObject("code", weapon1Code);
+ if (!weapon2Code) {
+ return weapon1Data.speed;
+ }
+ let weapon2Data = weapons.findObject("code", weapon2Code);
+ if (!weapon2Data) {
+ return weapon1Data.speed;
+ }
+ return (weapon1Data.speed + weapon2Data.speed) / 2;
+ },*/
+
+ attackModeForSkill: function (skillId, charClass = GameData.myReference.classid) {
+ //TODO:
+ if (skillId === sdk.skills.Smite) return "S1";
+ /*
+ A1:
+ normal attack or attack skills like
+ "bow and crossbow" skills, energy strike, chain lightning strike, charged strike,
+ opposing tiger strike, cobra strike, phoenix strike
+ Slash, paralyze, concentrate, amok
+ barbarian rage,
+ mangle , fire claws , anger poison dagger
+ victim, zeal, revenge, conversion
+
+ A2:
+ normal attack
+
+ KK: kick (kick barrel) or assassin skills dragon claw, dragon tail
+
+ S1: skill 1
+ (evade, avoid, escape)
+ shield attack smite
+
+ S2: skill 2
+ stationary traps, fire blast , Shock net, blade guard
+
+ S3: skill 3
+ Secondary blow of the barbarian with dual weapons
+ Hunger, rabies
+
+ S4:
+ Secondary blow of the assassin with dual claws
+ Secondary throw of the barbarian dual throwing
+
+ TH:
+ Throw
+ poison throwing spear, lightning strike, plague throwing spit, flashing mischief
+ */
+ return "A1";
+ },
+
+ weaponAttackAnimationSpeed: function (baseRate, skill, weaponClass, charClass = GameData.myReference.classid, shiftState = null) {
+ /*if (shiftState == "bear") {
+ let framesPerDirection = this.weaponFramesPerDirection(skill, weaponClass, charClass);
+ let baseSpeed = this.weaponAttackAnimationSpeed(baseRate, skill, weaponClass, charClass);
+ let weaponIAS = 0;
+ let weaponSpeedModifier = 0;
+ let delay = baseRate * framesPerDirection / ((256 + weaponIAS - weaponSpeedModifier) * baseSpeed / 100);
+ return baseRate*
+ }*/
+ //TODO: vampire form or werewolf
+ let attackMode = this.attackModeForSkill(skill, charClass);
+ switch (true) {
+ case charClass === sdk.player.class.Assassin && attackMode.startsWith("A") && weaponClass.startsWith("ht") && weaponClass !== "hth":
+ return 208;
+ case charClass === sdk.player.class.Assassin && attackMode === "S2":
+ return 128;
+ case charClass === sdk.player.class.Assassin && attackMode === "S4" && weaponClass === "ht2":
+ return 208;
+ }
+ // wolf or bear :
+ //AnimationSpeed = [Hitshift * NeutralFrames / Delay]
+ return 256;
+ },
+
+ weaponFramesPerDirection: function (skill, weaponClass, charClass = GameData.myReference.classid) {
+ let attackMode = this.attackModeForSkill(skill, charClass);
+ /*
+ 2HT = “2 Hand Thrust” Spear
+ STF = “Staff” Staff, Large Axe, Maul, Pole arm
+ 2HS = “2 Hand Swing” 2-Handed Sword
+ BOW = “Bow” Bow
+ XBW = “Crossbow” Crossbow
+ HT1 = “One Hand-to-Hand” Shield + Claws
+ HT2 = “”Two Hand-to-Hand” Claws + Claws
+ 1HT = “1 Hand Thrust” Shield + (Throwing potion, Knife, Throwing Knife, Javelin)
+ 1HS = “1 Hand Swing” Shield + (Axe, Wand, Club, Scepter, Mace, Hammer, Sword, Throwing Axe, Orb)
+ HTH = “Hand To Hand” Shield + no weapon
+ 1SS = “Left Swing Right Swing” Left = 1HS, Right = 1HS
+ 1JT = “Left Jab Right Thrust” Left = 1HT, Right = 1HT
+ 1ST = “Left Swing Right Thrust” Left = 1HS, Right = 1HT
+ 1JS = “Left Jab Right Swing” Left = 1HT, Right = 1HS
+ */
+
+ /*
+ Amazon Assassin Barbarian Druid Necromancer Paladin Sorceress
+ A1 HTH 08 13 256 06 11 256 06 12 256 08 16 256 08 15 256 07 14 256 09 16 256
+ A2 HTH --- 06 12 256 --- --- --- --- 08 16 256
+ A1 HTx 06 11 208
+ A2 HTx 06 12 208
+ A1 1HS 10 16 256 07 15 256 07 16 256 09 19 256 09 19 256 07 15 256 12 20 256
+ A1 2HS 12 20 256 11 23 256 08 18 256 10 21 256 11 23 256 08 18 256 14 24 256
+ A2 2HS --- --- --- --- --- 08 19 256 ---
+ A1 1HT 09 15 256 07 15 256 07 16 256 08 19 256 09 19 256 08 17 256 11 19 256
+ A1 2HT 11 18 256 10 23 256 09 19 256 09 23 256 10 24 256 08 20 256 13 23 256
+ A2 2HT --- --- --- --- --- 09 20 256 ---
+ A1 STF 12 20 256 09 19 256 09 19 256 09 17 256 11 20 256 09 18 256 11 18 256
+ A1 BOW 06 14 256 07 16 256 07 15 256 08 16 256 09 18 256 08 16 256 09 17 256
+ A1 XBW 09 20 256 10 21 256 10 20 256 10 20 256 11 20 256 10 20 256 11 20 256
+
+ TH xxx 09 16 256 07 16 256 08 16 256 08 18 256 10 20 256 08 16 256 10 20 256
+ KK xxx 04 13 256
+
+ S1 xxx xx 09 256 07 12 256
+ S2 xxx 04 08 128
+ S3 1Jx 08 12 256
+ S3 1Sx 07 12 256
+ S4 1Jx 08 16 256
+ S4 1Sx 09 16 256
+ S4 HT2 06 12 208
+
+ -------------------------------------------------- ------------------------------------------------
+ Werewolf bear fetish vampire
+ A1 xxx 07 13 xxx 07 12 xxx 08 12 256 09 14 176
+ S3 xxx 06 10 xxx 06 10 xxx
+ NU xxx xx 09 xxx xx 10 xxx
+
+ -------------------------------------------------- ------------------------------------------------
+ Rogue City Guard Eisenwolf Barbarian Mercenary
+ A1 xxx 06 15 256 11 16 256 06 15 256 05/12 16 256
+ */
+ switch (true) {
+ case charClass === sdk.player.class.Sorceress && attackMode.startsWith("A") && weaponClass === "hth":
+ return 16;
+ case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "1hs":
+ return 20;
+ case charClass === sdk.player.class.Sorceress && attackMode.startsWith("A") && weaponClass === "2hs":
+ return 24;
+ case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "1ht":
+ return 19;
+ case charClass === sdk.player.class.Sorceress && attackMode.startsWith("A") && weaponClass === "2ht":
+ return 23;
+ case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "stf":
+ return 18;
+ case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "bow":
+ return 17;
+ case charClass === sdk.player.class.Sorceress && attackMode === "A1" && weaponClass === "xbw":
+ return 20;
+ case charClass === sdk.player.class.Sorceress && attackMode === "TH":
+ return 20;
+
+ case charClass === sdk.player.class.Paladin && attackMode.startsWith("A") && weaponClass === "hth":
+ return 14;
+ case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "1hs":
+ return 15;
+ case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "2hs":
+ return 18;
+ case charClass === sdk.player.class.Paladin && attackMode === "A2" && weaponClass === "2hs":
+ return 19;
+ case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "1ht":
+ return 17;
+ case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "2ht":
+ return 20;
+ case charClass === sdk.player.class.Paladin && attackMode === "A2" && weaponClass === "2ht":
+ return 20;
+ case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "stf":
+ return 18;
+ case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "bow":
+ return 16;
+ case charClass === sdk.player.class.Paladin && attackMode === "A1" && weaponClass === "xbw":
+ return 20;
+ case charClass === sdk.player.class.Paladin && attackMode === "TH":
+ return 16;
+ case charClass === sdk.player.class.Paladin && attackMode === "S1":
+ return 12;
+
+ //TODO: full trag oul set
+ case charClass === sdk.player.class.Necromancer && attackMode.startsWith("A") && weaponClass === "hth":
+ return 15;
+ case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "1hs":
+ return 19;
+ case charClass === sdk.player.class.Necromancer && attackMode.startsWith("A") && weaponClass === "2hs":
+ return 23;
+ case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "1ht":
+ return 19;
+ case charClass === sdk.player.class.Necromancer && attackMode.startsWith("A") && weaponClass === "2ht":
+ return 24;
+ case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "stf":
+ return 20;
+ case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "bow":
+ return 18;
+ case charClass === sdk.player.class.Necromancer && attackMode === "A1" && weaponClass === "xbw":
+ return 20;
+ case charClass === sdk.player.class.Necromancer && attackMode === "TH":
+ return 20;
+
+ case this.shiftState() === "wolf" && attackMode === "A1":
+ return 13;
+ case this.shiftState() === "wolf" && attackMode === "S3":
+ return 10;
+
+ case this.shiftState() === "bear" && attackMode === "A1":
+ return 12;
+ case this.shiftState() === "bear" && attackMode === "S3":
+ return 10;
+
+ case charClass === sdk.player.class.Druid && attackMode.startsWith("A") && weaponClass === "hth":
+ return 16;
+ case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "1hs":
+ return 19;
+ case charClass === sdk.player.class.Druid && attackMode.startsWith("A") && weaponClass === "2hs":
+ return 21;
+ case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "1ht":
+ return 19;
+ case charClass === sdk.player.class.Druid && attackMode.startsWith("A") && weaponClass === "2ht":
+ return 23;
+ case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "stf":
+ return 17;
+ case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "bow":
+ return 16;
+ case charClass === sdk.player.class.Druid && attackMode === "A1" && weaponClass === "xbw":
+ return 20;
+ case charClass === sdk.player.class.Druid && attackMode === "TH":
+ return 18;
+
+ case charClass === sdk.player.class.Barbarian && attackMode.startsWith("A") && weaponClass === "hth":
+ return 12;
+ case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "1hs":
+ return 16;
+ case charClass === sdk.player.class.Barbarian && attackMode.startsWith("A") && weaponClass === "2hs":
+ return 18;
+ case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "1ht":
+ return 16;
+ case charClass === sdk.player.class.Barbarian && attackMode.startsWith("A") && weaponClass === "2ht":
+ return 19;
+ case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "stf":
+ return 19;
+ case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "bow":
+ return 15;
+ case charClass === sdk.player.class.Barbarian && attackMode === "A1" && weaponClass === "xbw":
+ return 20;
+ case charClass === sdk.player.class.Barbarian && attackMode === "TH":
+ return 16;
+ case charClass === sdk.player.class.Barbarian && attackMode === "S3":
+ return 12;
+ case charClass === sdk.player.class.Barbarian && attackMode === "S4":
+ return 16;
+
+ case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass.startsWith("ht"):
+ return 11;
+ case charClass === sdk.player.class.Assassin && attackMode === "A2" && weaponClass.startsWith("ht"):
+ return 12;
+ case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "1hs":
+ return 15;
+ case charClass === sdk.player.class.Assassin && attackMode.startsWith("A") && weaponClass === "2hs":
+ return 23;
+ case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "1ht":
+ return 15;
+ case charClass === sdk.player.class.Assassin && attackMode.startsWith("A") && weaponClass === "2ht":
+ return 23;
+ case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "stf":
+ return 19;
+ case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "bow":
+ return 16;
+ case charClass === sdk.player.class.Assassin && attackMode === "A1" && weaponClass === "xbw":
+ return 21;
+ case charClass === sdk.player.class.Assassin && attackMode === "TH":
+ return 16;
+ case charClass === sdk.player.class.Assassin && attackMode === "KK":
+ return 13;
+ case charClass === sdk.player.class.Assassin && attackMode === "S2":
+ return 8;
+ case charClass === sdk.player.class.Assassin && attackMode === "S4" && weaponClass === "ht2":
+ return 12;
+
+ case charClass === sdk.player.class.Amazon && attackMode.startsWith("A") && weaponClass === "hth":
+ return 13;
+ case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "1hs":
+ return 16;
+ case charClass === sdk.player.class.Amazon && attackMode.startsWith("A") && weaponClass === "2hs":
+ return 20;
+ case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "1ht":
+ return 15;
+ case charClass === sdk.player.class.Amazon && attackMode.startsWith("A") && weaponClass === "2ht":
+ return 18;
+ case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "stf":
+ return 20;
+ case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "bow":
+ return 14;
+ case charClass === sdk.player.class.Amazon && attackMode === "A1" && weaponClass === "xbw":
+ return 20;
+ case charClass === sdk.player.class.Amazon && attackMode === "TH":
+ return 16;
+ case charClass === sdk.player.class.Amazon && attackMode === "S1":
+ return 9;
+ }
+ return -1;
+ },
+
+ // attackFrames: function (skillId, weaponCode, ias = GameData.myReference.getStat(sdk.stats.Fasterattackrate), charClass = GameData.myReference.classid, weapon2Code = null) {
+ // // https://diablo3.ingame.de/forum/threads/1218516-FAQ-Bewegungs-und-Animationsgeschwindigkeiten-Teil-2?s=&postid=17610874
+ // /*
+ // TODO
+ // bear or wolf only :
+
+ // frames = {(256 * framesPerDirection) / [animationSpeed * (100 + effectiveIAS + skillsIAS - weaponSpeedModifier + coldEffect) / 100]} - 1
+
+ // where :
+ // animationSpeed = 256 * NeutralFrames / delay
+ // delay = 256 * CharFrames / ((100 + weaponIAS - weaponSpeedModifier) * CharSpeed / 100)
+
+ // framesPerDirection ... The sum of all frames of our attack animation of the Werform.
+ // NeutralFrames ... Sum of the frames of our neutral animation (use while standing).
+ // CharFrames ... The sum of all frames of the attack animation that we would use in the unchanged state (with the exception of two-handed swords).
+ // CharSpeed ... This is the animation speed of the attack animation that the unchanged character would use.
+ // weaponIAS ... All IAS on our weapon or weapon base
+ // */
+ // let weaponData = (new CSV("sdk/txt/weapons.txt")).findObject("code", weaponCode);
+ // if (!weaponData) {
+ // console.log(sdk.colors.Orange + "No weapon data found for code " + weaponCode);
+ // }
+ // let weaponClass = weaponData.wclass;
+ // let baseRate = 100;
+ // const BASE_ANIMATION_SPEED = 256;
+
+ // let animationSpeed = this.weaponAttackAnimationSpeed(baseRate, weaponClass, charClass, this.shiftState());
+ // let effectiveIAS = 120 * ias / (120 + ias);
+ // let skillsIAS = 0; //TODO: fanaticism or other sills bonus + slowdown skills malus
+ // let weaponSpeedModifier = (typeof weaponData.speed == "string") ? isNaN(parseInt(weaponData.speed)) ? 0 : parseInt(weaponData.speed) : weaponData.speed;// this.weaponSpeedModifier(weaponCode, charClass, weapon2Code);
+ // // me.getState(sdk.states.Frozen) or me.getState(sdk.states.Cold) ?
+ // let coldEffect = GameData.myReference.getState(sdk.states.Frozen) ? -50 : 0; // If we are affected by cold, as a player we receive a penalty of 50.
+ // let acceleration = baseRate + effectiveIAS + skillsIAS - weaponSpeedModifier + coldEffect;
+ // acceleration = Math.min(175, Math.max(15, acceleration));
+ // let startingFrame = this.attackStartingFrame(weaponClass, charClass);
+ // let framesPerDirection = this.weaponFramesPerDirection(skillId, weaponClass, charClass);
+ // if (framesPerDirection < 1) {
+ // console.log(sdk.colors.Orange + "wrong value for framesPerDirection, IAS calculation may be wrong");
+ // }
+
+ // console.log("skillId " + skillId);
+ // console.log("charClass " + charClass);
+ // console.log("weaponCode " + weaponCode);
+ // console.log("weaponClass " + weaponClass);
+ // console.log("ias " + ias);
+ // console.log("effectiveIAS " + effectiveIAS);
+ // console.log("skillsIAS " + skillsIAS);
+ // console.log("weaponSpeedModifier " + weaponSpeedModifier);
+ // console.log("coldEffect " + coldEffect);
+ // console.log("acceleration " + acceleration);
+ // console.log("startingFrame " + startingFrame);
+ // console.log("framesPerDirection " + framesPerDirection);
+ // let frames = Math.ceil(BASE_ANIMATION_SPEED * (framesPerDirection - startingFrame) / Math.floor(animationSpeed * acceleration / 100)) - 1;
+ // return frames;
+ // },
+
+ // attackDuration: function (skillId, weaponCode, ias = GameData.myReference.getStat(sdk.stats.Fasterattackrate), charClass = GameData.myReference.classid, weapon2Code = null) {
+ // // https://diablo3.ingame.de/forum/threads/1218516-FAQ-Bewegungs-und-Animationsgeschwindigkeiten-Teil-2?s=&postid=17610874
+ // return this.attackFrames(skillId, weaponCode, ias, charClass) / 25;
+ // },
+ timeTillMissleImpact: function (skillId, monster) {
+ if (monster === undefined || skillId === undefined || !monster.attackable) return 0;
+ let missileName = getBaseStat("skills", skillId, "cltmissile");
+ let missile = MissileData[missileName];
+ if (!missile) {
+ missileName = getBaseStat("skills", skillId, "srvmissile");
+ missile = MissileData[missileName];
+ }
+ if (missile && missile.velocity > 0) {
+ const missileVelocityTPS = missile.velocity;
+ const missileVelocityTPF = missileVelocityTPS / 25;
+ const distanceForMissile = getDistance(me, monster);
+ // too far for missile to reach this position
+ if (distanceForMissile > missile.range) return 0;
+ const castTimeS = me.castingDuration(skillId);
+ return ((distanceForMissile / ((missileVelocityTPS / 32) * 25)) + castTimeS);
+ }
+ return 0;
+ }
+ };
+
+ function calculateKillableFallensByFrostNova () {
+ if (!Skill.canUse(sdk.skills.FrostNova)) return 0;
+ const fallens = [
+ sdk.monsters.Fallen, sdk.monsters.Carver2,
+ sdk.monsters.Devilkin2, sdk.monsters.DarkOne1,
+ sdk.monsters.WarpedFallen, sdk.monsters.Carver1,
+ sdk.monsters.Devilkin, sdk.monsters.DarkOne2
+ ];
+ let area = me.area;
+ return getUnits(sdk.unittype.Monster)
+ .filter(function (unit) {
+ return !!unit && fallens.includes(unit.classid) && unit.distance < 7;
+ })
+ .filter(function (unit) {
+ return unit.attackable
+ && typeof unit.x === "number" // happens if monster despawns
+ && !checkCollision(me, unit, Coords_1.Collision.BLOCK_MISSILE)
+ && unit.getStat(sdk.stats.ColdResist) < 100;
+ //&& !unit.getState(sdk.states.Frozen);
+ })
+ .reduce(function (acc, cur) {
+ let classId = cur.classid, minDmg = GameData.skillDamage(sdk.skills.FrostNova, cur).min;
+ //let charLvl = GameData.monsterLevel(classId, area);
+ let currentHealth = GameData.monsterMaxHP(classId, area, cur.charlvl - GameData.monsterLevel(classId, area)) / 100 * (cur.hp * 100 / cur.hpmax);
+ if (currentHealth < minDmg) {
+ acc++;
+ }
+ return acc;
+ }, 0);
+ }
+
+ function calculateKillableSummonsByNova () {
+ if (!Skill.canUse(sdk.skills.Nova)) return 0;
+ const summons = [
+ sdk.monsters.Fallen, sdk.monsters.Carver2,
+ sdk.monsters.Devilkin2, sdk.monsters.DarkOne1,
+ sdk.monsters.WarpedFallen, sdk.monsters.Carver1,
+ sdk.monsters.Devilkin, sdk.monsters.DarkOne2,
+ sdk.monsters.BurningDead, sdk.monsters.Returned1,
+ sdk.monsters.Returned2, sdk.monsters.BoneWarrior1, sdk.monsters.BoneWarrior2
+ ];
+ return getUnits(sdk.unittype.Monster)
+ .filter(function (unit) {
+ return !!unit && summons.includes(unit.classid) && unit.distance < 7;
+ })
+ .filter(function (unit) {
+ return unit.attackable
+ && typeof unit.x === "number" // happens if monster despawns
+ && !checkCollision(me, unit, Coords_1.Collision.BLOCK_MISSILE)
+ && Attack.checkResist(unit, "lightning");
+ })
+ .reduce(function (acc, cur) {
+ let classId = cur.classid, areaId = cur.area, minDmg = GameData.skillDamage(sdk.skills.Nova, cur).min;
+ let currentHealth = GameData.monsterMaxHP(classId, areaId, cur.charlvl - GameData.monsterLevel(classId, areaId)) / 100 * (cur.hp * 100 / cur.hpmax);
+ if (currentHealth < minDmg) {
+ acc++;
+ }
+ return acc;
+ }, 0);
+ }
+
+ Object.defineProperty(Unit.prototype, "currentVelocity", {
+ get: function () {
+ if (!this.isMoving || this.isFrozen) return 0;
+ const velocity = this.isRunning
+ ? MonsterData.get(this.classid).Run
+ : MonsterData.get(this.classid).Velocity;
+ if (this.isChilled) {
+ let malus = MonsterData.get(this.classid).ColdEffect;
+ (malus > 0) && (malus = malus - 256);
+ return Math.max(1, ~~(velocity * (1 + malus)));
+ }
+ return velocity;
+ }
+ });
+
+ /**
+ * @param {number} skillId
+ * @param {Monster} monster
+ * @returns {PathNode}
+ */
+ function targetPointForSkill (skillId, monster) {
+ if (!monster || skillId === undefined || !monster.attackable) return null;
+ let missileName = getBaseStat("skills", skillId, "cltmissile");
+ let missile = MissileData[missileName];
+ if (!missile) {
+ missileName = getBaseStat("skills", skillId, "srvmissile");
+ missile = MissileData[missileName];
+ }
+ if (!missile || missile.velocity <= 0) return null;
+ if (monster.isMoving && (monster.targetx !== me.x || monster.targety !== me.y)) {
+ let startX = monster.x;
+ let startY = monster.y;
+ // tiles per second velocities
+ // ToDo: is monster slowed by freeze or something ?
+ let monsterVelocityTPS = monster.currentVelocity;
+ let missileVelocityTPS = missile.velocity;
+ // tiles per frame velocities
+ let monsterVelocityTPF = monsterVelocityTPS / 25;
+ let missileVelocityTPF = missileVelocityTPS / 25;
+ //console.log("monster is moving to "+monster.targetx+", "+monster.targety + " at speed "+monsterVelocity);
+ let path = getPath(monster.area, startX, startY, monster.targetx, monster.targety, 2, 1);
+ if (path && path.length) {
+ // path is reversed from target to monster, we will check from last path position (target) to monster position
+ path.reverse();
+ let [diffS, diffF, found] = [0, 0, 0];
+ let time = { missile: {}, monster: {} };
+ for (let i = 0; i < path.length; i++) {
+ let pos = path[i];
+ // ToDo : does missile spawn at me position ?
+ let distanceForMissile = getDistance(me, pos);
+ if (distanceForMissile > missile.range) {
+ // too far for missile to reach this position
+ continue;
+ }
+ let distanceForMonster = getDistance({ x: startX, y: startY }, pos);
+ let timeForMonsterF = distanceForMonster / monsterVelocityTPF;
+ // time in seconds
+ // let castTimeS = GameData.castingDuration(skillId);
+ // let timeForMissileS = distanceForMissile / missileVelocityTPS + castTimeS;
+ // time in frames
+ let castTimeF = me.castingFrames(skillId);
+ let timeForMissileF = distanceForMissile / missileVelocityTPF + castTimeF;
+ // let timeForMonsterS = distanceForMonster / monsterVelocityTPS;
+ // Todo: missile and monster size
+ // diff seconds
+ // diffS = timeForMissileS-timeForMonsterS;
+ // diff frames
+ diffF = timeForMissileF - timeForMonsterF;
+ // diff > 0 : missile will reach pos after monster
+ // diff < 0 : missile will reach pos before monster
+ // console.log("time for monster to reach "+pos+" = "+timeForMonster);
+ // console.log("time for missile to reach "+pos+" = "+timeForMissile);
+ // console.log("diff = "+diff)
+ if (i === 0 && diffF >= 0) {
+ // last path position and missile is late, we can't predict next monster target, shoot at last path position
+ // it may fail because monster may be moving at other target while missile is arriving
+ // console.log("missile will be too late");
+ found = pos;
+ // time.missile.seconds = timeForMissileS;
+ time.missile.frames = timeForMissileF;
+ // time.monster.seconds = timeForMonsterS;
+ time.monster.frames = timeForMonsterF;
+ break;
+ }
+ // the number of frames needed for unit to move 1 tile
+ let timeToMoveOneTileMonsterF = 1 / monsterVelocityTPF;
+ // let timeToMoveOneTileMissileF = 1 / missileVelocityTPF;
+ // while missile is travelling, monster will continue to move
+ // if the difference is greater than the time a monster will move 1 tile, the missile will miss
+ // todo: monster size, missile size
+ if (diffF >= -1 * timeToMoveOneTileMonsterF && diffF <= 1 * timeToMoveOneTileMonsterF) {
+ found = pos;
+ // time.missile.seconds = timeForMissileS;
+ time.missile.frames = timeForMissileF;
+ // time.monster.seconds = timeForMonsterS;
+ time.monster.frames = timeForMonsterF;
+ break;
+ }
+ }
+ if (found) {
+ // console.log("missile will hit monster in "+time.missile.seconds+" ("+time.missile.frames+") at "+found.x+", "+found.y);
+ // console.log("time for monster = "+time.monster.seconds+ " ("+time.monster.frames+")")
+ // console.log("diff missile-monster = "+diffS+ " ("+diffF+")");
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ // Export data
+ GameData.isEnemy = isEnemy;
+ GameData.isAlive = isAlive;
+ GameData.onGround = onGround;
+ GameData.calculateKillableFallensByFrostNova = calculateKillableFallensByFrostNova;
+ GameData.calculateKillableSummonsByNova = calculateKillableSummonsByNova;
+ GameData.targetPointForSkill = targetPointForSkill;
+ module.exports = GameData;
+})(module, require);
diff --git a/libs/SoloPlay/Modules/GameData/LocaleStringID.js b/libs/SoloPlay/Modules/GameData/LocaleStringID.js
new file mode 100644
index 00000000..96332bc3
--- /dev/null
+++ b/libs/SoloPlay/Modules/GameData/LocaleStringID.js
@@ -0,0 +1,7806 @@
+/**
+* @filename LocaleStringID.js
+* @author Nishimura-Katsuo
+* @desc locale string indexes from NameStr ids
+*/
+(function (module) {
+ let LocaleStringID = {
+ "WarrivAct1IntroGossip1": 0,
+ "WarrivAct1IntroPalGossip1": 1,
+ "WarrivGossip1": 2,
+ "WarrivGossip2": 3,
+ "WarrivGossip3": 4,
+ "WarrivGossip4": 5,
+ "WarrivGossip5": 6,
+ "WarrivGossip6": 7,
+ "WarrivGossip7": 8,
+ "WarrivGossip8": 9,
+ "WarrivGossip9": 10,
+ "AkaraIntroGossip1": 11,
+ "AkaraIntroSorGossip1": 12,
+ "AkaraGossip1": 13,
+ "AkaraGossip2": 14,
+ "AkaraGossip3": 15,
+ "AkaraGossip4": 16,
+ "AkaraGossip5": 17,
+ "AkaraGossip6": 18,
+ "AkaraGossip7": 19,
+ "AkaraGossip8": 20,
+ "AkaraGossip9": 21,
+ "AkaraGossip10": 22,
+ "AkaraGossip11": 23,
+ "KashyaIntroGossip1": 24,
+ "KashyaIntroAmaGossip1": 25,
+ "KashyaGossip1": 26,
+ "KashyaGossip2": 27,
+ "KashyaGossip3": 28,
+ "KashyaGossip4": 29,
+ "KashyaGossip5": 30,
+ "KashyaGossip6": 31,
+ "KashyaGossip7": 32,
+ "KashyaGossip8": 33,
+ "KashyaGossip9": 34,
+ "KashyaGossip10": 35,
+ "CharsiIntroGossip1": 36,
+ "CharsiIntroBarGossip1": 37,
+ "CharsiGossip1": 38,
+ "CharsiGossip2": 39,
+ "CharsiGossip3": 40,
+ "CharsiGossip4": 41,
+ "CharsiGossip5": 42,
+ "CharsiGossip6": 43,
+ "CharsiGossip7": 44,
+ "GheedIntroGossip1": 45,
+ "GheedIntroNecGossip1": 46,
+ "GheedGossip1": 47,
+ "GheedGossip2": 48,
+ "GheedGossip3": 49,
+ "GheedGossip4": 50,
+ "GheedGossip5": 51,
+ "GheedGossip6": 52,
+ "GheedGossip7": 53,
+ "CainGossip1": 54,
+ "CainGossip2": 55,
+ "CainGossip3": 56,
+ "CainGossip4": 57,
+ "CainGossip5": 58,
+ "RogueSignpostGossip1": 59,
+ "RogueSignpostGossip2": 60,
+ "RogueSignpostGossip3": 61,
+ "RogueSignpostGossip4": 62,
+ "RogueSignpostGossip5": 63,
+ "A1Q1InitAkara": 64,
+ "A1Q1AfterInitAkara": 65,
+ "A1Q1AfterInitKashya": 66,
+ "A1Q1AfterInitCharsiMain": 67,
+ "A1Q1AfterInitCharsiAlt": 68,
+ "A1Q1AfterInitGheed": 69,
+ "A1Q1AfterInitWarriv": 70,
+ "A1Q1EarlyReturnAkara": 71,
+ "A1Q1EarlyReturnKashya": 72,
+ "A1Q1EarlyReturnCharsi": 73,
+ "A1Q1EarlyReturnGheed": 74,
+ "A1Q1EarlyReturnWarriv": 75,
+ "A1Q1SuccessfulAkara": 76,
+ "A1Q1SuccessfulKashya": 77,
+ "A1Q1SuccessfulCharsi": 78,
+ "A1Q1SuccessfulGheed": 79,
+ "A1Q1SuccessfulWarriv": 80,
+ "A1Q2InitKashya": 81,
+ "A1Q2AfterInitKashya": 82,
+ "A1Q2AfterInitCharsi": 83,
+ "A1Q2AfterInitGheed": 84,
+ "A1Q2AfterInitAkara": 85,
+ "A1Q2AfterInitWarriv": 86,
+ "A1Q2EarlyReturnKashya": 87,
+ "A1Q2EarlyReturnAkara": 88,
+ "A1Q2EarlyReturnCharsi": 89,
+ "A1Q2EarlyReturnGheed": 90,
+ "A1Q2EarlyReturnWarriv": 91,
+ "A1Q2SuccessfulKashya": 92,
+ "A1Q2SuccessfulAkara": 93,
+ "A1Q2SuccessfulCharsi": 94,
+ "A1Q2SuccessfulGheed": 95,
+ "A1Q2SuccessfulWarriv": 96,
+ "A1Q4InitAkara": 97,
+ "A1Q4AfterInitScrollKashya": 98,
+ "A1Q4AfterInitScrollAkara": 99,
+ "A1Q4AfterInitScrollCharsi": 100,
+ "A1Q4AfterInitScrollWarriv": 101,
+ "A1Q4AfterInitScrollGheed": 102,
+ "A1Q4InstructionsCharsi": 103,
+ "A1Q4EarlyReturnSAkara": 104,
+ "A1Q4EarlyReturnSKashya": 105,
+ "A1Q4EarlyReturnSGheed": 106,
+ "A1Q4EarlyReturnSWarriv": 107,
+ "A1Q4SuccessfulScrollKashya": 108,
+ "A1Q4SuccessfulScrollCharsi": 109,
+ "A1Q4SuccessfulScrollGheed": 110,
+ "A1Q4SuccessfulScrollWarriv": 111,
+ "A1Q4InstructionsAkara": 112,
+ "A1Q4EarlyReturnKashya": 113,
+ "A1Q4EarlyReturnCharsi": 114,
+ "A1Q4EarlyReturnGheed": 115,
+ "A1Q4EarlyReturnWarriv": 116,
+ "A1Q4EarlyReturnAkara": 117,
+ "A1Q4QuestSuccessfulAkara": 118,
+ "A1Q4QuestSuccessfulKashya": 119,
+ "A1Q4QuestSuccessfulGheed": 120,
+ "A1Q4QuestSuccessfulCharsi": 121,
+ "A1Q4QuestSuccessfulWarriv": 122,
+ "A1Q4QuestSuccessfulCain": 123,
+ "A1Q4RescuedByHeroCain": 124,
+ "A1Q4RescuedByRoguesCain": 125,
+ "A1Q4TragedyOfTristramCain": 126,
+ "A1Q5InitQuestTome": 127,
+ "A1Q5AfterInitGheed": 128,
+ "A1Q5AfterInitCharsi": 129,
+ "A1Q5AfterInitAkara": 130,
+ "A1Q5AfterInitCain": 131,
+ "A1Q5AfterInitWarriv": 132,
+ "A1Q5AfterInitKashya": 133,
+ "A1Q5EarlyReturnKashya": 134,
+ "A1Q5EarlyReturnCain": 135,
+ "A1Q5EarlyReturnWarriv": 136,
+ "A1Q5EarlyReturnCharsi": 137,
+ "A1Q5EarlyReturnAkara": 138,
+ "A1Q5EarlyReturnGheed": 139,
+ "A1Q5SuccessfulKashya": 140,
+ "A1Q5SuccessfulWarriv": 141,
+ "A1Q5SuccessfulGheed": 142,
+ "A1Q5SuccessfulAkara": 143,
+ "A1Q5SuccessfulCharsi": 144,
+ "A1Q5SuccessfulCain": 145,
+ "A1Q3InitCharsi": 146,
+ "A1Q3AfterInitCain": 147,
+ "A1Q3AfterInitAkara": 148,
+ "A1Q3AfterInitKashya": 149,
+ "A1Q3AfterInitCharsi": 150,
+ "A1Q3AfterInitGheed": 151,
+ "A1Q3AfterInitGheedAlt": 152,
+ "A1Q3AfterInitWarriv": 153,
+ "A1Q3EarlyReturnCain": 154,
+ "A1Q3EarlyReturnAkara": 155,
+ "A1Q3EarlyReturnKashya": 156,
+ "A1Q3EarlyReturnCharsi": 157,
+ "A1Q3EarlyReturnGheed": 158,
+ "A1Q3EarlyReturnWarriv": 159,
+ "A1Q3SuccessfulCain": 160,
+ "A1Q3SuccessfulAkara": 161,
+ "A1Q3SuccessfulKashya": 162,
+ "A1Q3SuccessfulCharsi": 163,
+ "A1Q3SuccessfulGheed": 164,
+ "A1Q3SuccessfulWarriv": 165,
+ "A1Q6InitCain": 166,
+ "A1Q6AfterInitCain": 167,
+ "A1Q6AfterInitAkara": 168,
+ "A1Q6AfterInitCharsi": 169,
+ "A1Q6AfterInitGheed": 170,
+ "A1Q6AfterInitWarriv": 171,
+ "A1Q6AfterInitKashya": 172,
+ "A1Q6EarlyReturnCain": 173,
+ "A1Q6EarlyReturnAkara": 174,
+ "A1Q6EarlyReturnGheed": 175,
+ "A1Q6EarlyReturnCharsi": 176,
+ "A1Q6EarlyReturnWarriv": 177,
+ "A1Q6EarlyReturn2Kashya": 178,
+ "A1Q6SuccessfulAkara": 179,
+ "A1Q6SuccessfulCharsi": 180,
+ "A1Q6SuccessfulKashya": 181,
+ "A1Q6SuccessfulGheed": 182,
+ "A1Q6SuccessfulWarriv": 183,
+ "A1Q6SuccessfulCain": 184,
+ "PalaceGuardGossip1": 185,
+ "PalaceGuardGossip2": 186,
+ "PalaceGuardGossip3": 187,
+ "PalaceGuardGossip4": 188,
+ "PalaceGuardGossip5": 189,
+ "GriezIntroGossip1": 190,
+ "GriezGossip1": 191,
+ "GriezGossip2": 192,
+ "GriezGossip3": 193,
+ "GriezGossip4": 194,
+ "GriezGossip5": 195,
+ "GriezGossip6": 196,
+ "GriezGossip7": 197,
+ "GriezGossip8": 198,
+ "GriezGossip9": 199,
+ "GriezGossip10": 200,
+ "GriezGossip11": 201,
+ "GriezGossip12": 202,
+ "ElzixIntroGossip1": 203,
+ "ElzixIntroNecGossip1": 204,
+ "ElzixGossip1": 205,
+ "ElzixGossip2": 206,
+ "ElzixGossip3": 207,
+ "ElzixGossip4": 208,
+ "ElzixGossip5": 209,
+ "ElzixGossip6": 210,
+ "ElzixGossip7": 211,
+ "ElzixGossip8": 212,
+ "ElzixGossip9": 213,
+ "ElzixGossip10": 214,
+ "WarrivAct2IntroGossip1": 215,
+ "WarrivAct2Gossip1": 216,
+ "WarrivAct2Gossip2": 217,
+ "WarrivAct2Gossip3": 218,
+ "WarrivAct2Gossip4": 219,
+ "WarrivAct2Gossip5": 220,
+ "AtmaIntroGossip1": 221,
+ "AtmaGossip1": 222,
+ "AtmaGossip2": 223,
+ "AtmaGossip3": 224,
+ "AtmaGossip4": 225,
+ "AtmaGossip5": 226,
+ "AtmaGossip6": 227,
+ "AtmaGossip7": 228,
+ "AtmaGossip8": 229,
+ "GeglashIntroGossip1": 230,
+ "GeglashIntroBarGossip1": 231,
+ "GeglashGossip1": 232,
+ "GeglashGossip2": 233,
+ "GeglashGossip3": 234,
+ "GeglashGossip4": 235,
+ "GeglashGossip5": 236,
+ "GeglashGossip6": 237,
+ "GeglashGossip7": 238,
+ "GeglashGossip8": 239,
+ "GeglashGossip9": 240,
+ "MeshifIntroGossip1": 241,
+ "MeshifIntroAmaGossip1": 242,
+ "MeshifGossip1": 243,
+ "MeshifGossip2": 244,
+ "MeshifGossip3": 245,
+ "MeshifGossip4": 246,
+ "MeshifGossip5": 247,
+ "MeshifGossip6": 248,
+ "MeshifGossip7": 249,
+ "MeshifGossip8": 250,
+ "MeshifGossip9": 251,
+ "MeshifGossip10": 252,
+ "JerhynActIntroGossip1": 253,
+ "JerhynActIntroMoreGossip1": 254,
+ "JerhynIntroGossip1": 255,
+ "JerhynGossip1": 256,
+ "JerhynGossip2": 257,
+ "JerhynGossip3": 258,
+ "JerhynGossip4": 259,
+ "JerhynGossip5": 260,
+ "JerhynGossip6": 261,
+ "JerhynGossip7": 262,
+ "FaraIntroGossip1": 263,
+ "FaraIntroPalGossip1": 264,
+ "FaraGossip1": 265,
+ "FaraGossip2": 266,
+ "FaraGossip3": 267,
+ "FaraGossip4": 268,
+ "FaraGossip5": 269,
+ "FaraGossip6": 270,
+ "FaraGossip7": 271,
+ "FaraGossip8": 272,
+ "FaraGossip9": 273,
+ "LysanderIntroGossip1": 274,
+ "LysanderGossip1": 275,
+ "LysanderGossip2": 276,
+ "LysanderGossip3": 277,
+ "LysanderGossip4": 278,
+ "LysanderGossip5": 279,
+ "LysanderGossip6": 280,
+ "LysanderGossip7": 281,
+ "LysanderGossip8": 282,
+ "LysanderGossip9": 283,
+ "LysanderGossip10": 284,
+ "DrognanIntroGossip1": 285,
+ "DrognanIntroSorGossip1": 286,
+ "DrognanGossip1": 287,
+ "DrognanGossip2": 288,
+ "DrognanGossip3": 289,
+ "DrognanGossip4": 290,
+ "DrognanGossip5": 291,
+ "DrognanGossip6": 292,
+ "DrognanGossip7": 293,
+ "DrognanGossip8": 294,
+ "DrognanGossip9": 295,
+ "DrognanGossip10": 296,
+ "CainAct2Gossip1": 297,
+ "CainAct2Gossip2": 298,
+ "CainAct2Gossip3": 299,
+ "CainAct2Gossip4": 300,
+ "CainAct2Gossip5": 301,
+ "TyraelGossip1": 302,
+ "Desert2GuardGossip1": 303,
+ "A2Q1InitAtma": 304,
+ "A2Q1AfterInitGreiz": 305,
+ "A2Q1AfterInitElzix": 306,
+ "A2Q1AfterInitWarrivAct2": 307,
+ "A2Q1AfterInitGeglash": 308,
+ "A2Q1AfterInitFara": 309,
+ "A2Q1AfterInitAtma": 310,
+ "A2Q1AfterInitMeshif": 311,
+ "A2Q1AfterInitDrognan": 312,
+ "A2Q1AfterInitLysander": 313,
+ "A2Q1AfterInitCain": 314,
+ "A2Q1EarlyReturnWarrivAct2": 315,
+ "A2Q1EarlyReturnMeshif": 316,
+ "A2Q1EarlyReturnAtma": 317,
+ "A2Q1EarlyReturnGreiz": 318,
+ "A2Q1EarlyReturnGeglash": 319,
+ "A2Q1EarlyReturnElzix": 320,
+ "A2Q1EarlyReturnLysander": 321,
+ "A2Q1EarlyReturnDrognan": 322,
+ "A2Q1EarlyReturnFara": 323,
+ "A2Q1EarlyReturnCain": 324,
+ "A2Q1SuccessfulGreiz": 325,
+ "A2Q1SuccessfulDrognan": 326,
+ "A2Q1SuccessfulLysander": 327,
+ "A2Q1SuccessfulMeshif": 328,
+ "A2Q1SuccessfulGeglash": 329,
+ "A2Q1SuccessfulElzix": 330,
+ "A2Q1SuccessfulWarrivAct2": 331,
+ "A2Q1SuccessfulFara": 332,
+ "A2Q1SuccessfulCain": 333,
+ "A2Q1SuccessfulAtma": 334,
+ "A2Q2EarlyReturnScrollCain": 335,
+ "A2Q2EarlyReturnCapCain": 336,
+ "A2Q2EarlyReturnStaveCain": 337,
+ "A2Q2EarlyReturnCubeCain": 338,
+ "A2Q2SuccessfulStaffCain": 339,
+ "A2Q3AfterInitJerhyn": 340,
+ "A2Q3AfterInitGreiz": 341,
+ "A2Q3AfterInitElzix": 342,
+ "A2Q3AfterInitWarrivAct2": 343,
+ "A2Q3AfterInitAtma": 344,
+ "A2Q3AfterInitGeglash": 345,
+ "A2Q3AfterInitFara": 346,
+ "A2Q3AfterInitLysander": 347,
+ "A2Q3AfterInitDrognan": 348,
+ "A2Q3AfterInitMeshif": 349,
+ "A2Q3AfterInitCain": 350,
+ "A2Q3EarlyReturnJerhyn": 351,
+ "A2Q3EarlyReturnGreiz": 352,
+ "A2Q3EarlyReturnWarrivAct2": 353,
+ "A2Q3EarlyReturnGeglash": 354,
+ "A2Q3EarlyReturnMeshif": 355,
+ "A2Q3EarlyReturnFara": 356,
+ "A2Q3EarlyReturnLysander": 357,
+ "A2Q3EarlyReturnDrognan": 358,
+ "A2Q3EarlyReturnElzix": 359,
+ "A2Q3EarlyReturnCain": 360,
+ "A2Q3EarlyReturnAtma": 361,
+ "A2Q3SuccessfulJerhyn": 362,
+ "A2Q3SuccessfulGreiz": 363,
+ "A2Q3SuccessfulElzix": 364,
+ "A2Q3SuccessfulGeglash": 365,
+ "A2Q3SuccessfulWarrivAct2": 366,
+ "A2Q3SuccessfulMeshif": 367,
+ "A2Q3SuccessfulAtma": 368,
+ "A2Q3SuccessfulFara": 369,
+ "A2Q3SuccessfulLysander": 370,
+ "A2Q3SuccessfulDrognan": 371,
+ "A2Q3SuccessfulCain": 372,
+ "A2Q4InitDrognan": 373,
+ "A2Q4AfterInitFara": 374,
+ "A2Q4AfterInitGreiz": 375,
+ "A2Q4AfterInitElzix": 376,
+ "A2Q4AfterInitJerhyn": 377,
+ "A2Q4AfterInitCain": 378,
+ "A2Q4AfterInitGeglash": 379,
+ "A2Q4AfterInitAtma": 380,
+ "A2Q4AfterInitWarrivAct2": 381,
+ "A2Q4AfterInitLysander": 382,
+ "A2Q4AfterInitDrognan": 383,
+ "A2Q4AfterInitMeshif": 384,
+ "A2Q4EarlyReturnElzix": 385,
+ "A2Q4EarlyReturnJerhyn": 386,
+ "A2Q4EarlyReturnGreiz": 387,
+ "A2Q4EarlyReturnDrognan": 388,
+ "A2Q4EarlyReturnLysander": 389,
+ "A2Q4EarlyReturnFara": 390,
+ "A2Q4EarlyReturnGeglash": 391,
+ "A2Q4EarlyReturnMeshif": 392,
+ "A2Q4EarlyReturnAtma": 393,
+ "A2Q4EarlyReturnWarrivAct2": 394,
+ "A2Q4EarlyReturnCain": 395,
+ "A2Q4SuccessfulNarrator": 396,
+ "A2Q4SuccessfulGriez": 397,
+ "A2Q4SuccessfulJerhyn": 398,
+ "A2Q4SuccessfulDrognan": 399,
+ "A2Q4SuccessfulElzix": 400,
+ "A2Q4SuccessfulGeglash": 401,
+ "A2Q4SuccessfulMeshif": 402,
+ "A2Q4SuccessfulWarrivAct2": 403,
+ "A2Q4SuccessfulFara": 404,
+ "A2Q4SuccessfulLysander": 405,
+ "A2Q4SuccessfulAtma": 406,
+ "A2Q4SuccessfulCain": 407,
+ "A2Q5EarlyReturnGreiz": 408,
+ "A2Q5EarlyReturnJerhyn": 409,
+ "A2Q5EarlyReturnDrognan": 410,
+ "A2Q5EarlyReturnLysander": 411,
+ "A2Q5EarlyReturnMeshif": 412,
+ "A2Q5EarlyReturnWarrivAct2": 413,
+ "A2Q5EarlyReturnAtma": 414,
+ "A2Q5EarlyReturnGeglash": 415,
+ "A2Q5EarlyReturnFara": 416,
+ "A2Q5EarlyReturnElzix": 417,
+ "A2Q5EarlyReturnCain": 418,
+ "A2Q5SuccessfulGreiz": 419,
+ "A2Q5SuccessfulGeglash": 420,
+ "A2Q5SuccessfulJerhyn": 421,
+ "A2Q5SuccessfulDrognan": 422,
+ "A2Q5SuccessfulElzix": 423,
+ "A2Q5SuccessfulWarrivAct2": 424,
+ "A2Q5SuccessfulMeshif": 425,
+ "A2Q5SuccessfulLysander": 426,
+ "A2Q5SuccessfulAtma": 427,
+ "A2Q5SuccessfulFara": 428,
+ "A2Q5SuccessfulCain": 429,
+ "A2Q6InitJerhyn": 430,
+ "A2Q6AfterInitJerhyn": 431,
+ "A2Q6AfterInitElzix": 432,
+ "A2Q6AfterInitWarrivAct2": 433,
+ "A2Q6AfterInitAtma": 434,
+ "A2Q6AfterInitGeglash": 435,
+ "A2Q6AfterInitMeshif": 436,
+ "A2Q6AfterInitFara": 437,
+ "A2Q6AfterInitLysander": 438,
+ "A2Q6AfterInitDrognan": 439,
+ "A2Q6AfterInitCain": 440,
+ "A2Q6AfterInitGreiz": 441,
+ "A2Q6SuccessfulJerhyn": 442,
+ "A2Q6SuccessfulElzix": 443,
+ "A2Q6SuccessfulLysander": 444,
+ "A2Q6SuccessfulAtma": 445,
+ "A2Q6SuccessfulWarrivAct2": 446,
+ "A2Q6SuccessfulFara": 447,
+ "A2Q6SuccessfulGeglash": 448,
+ "A2Q6SuccessfulDrognan": 449,
+ "A2Q6SuccessfulMeshif": 450,
+ "A2Q6SuccessfulGreiz": 451,
+ "A2Q6SuccessfulCain": 452,
+ "NatalyaIntroGossip1": 453,
+ "NatalyaGossip1": 454,
+ "NatalyaGossip2": 455,
+ "NatalyaGossip3": 456,
+ "NatalyaGossip4": 457,
+ "CainAct3IntroGossip1": 458,
+ "CainAct3Gossip1": 459,
+ "CainAct3Gossip2": 460,
+ "CainAct3Gossip3": 461,
+ "CainAct3Gossip4": 462,
+ "CainAct3Gossip5": 463,
+ "CainAct3Gossip6": 464,
+ "HratliActIntroGossip1": 465,
+ "HratliActIntroSorGossip1": 466,
+ "HratliGossip1": 467,
+ "HratliGossip2": 468,
+ "HratliGossip3": 469,
+ "HratliGossip4": 470,
+ "HratliGossip5": 471,
+ "HratliGossip6": 472,
+ "HratliGossip7": 473,
+ "HratliGossip8": 474,
+ "HratliGossip9": 475,
+ "HratliGossip10": 476,
+ "HratliGossip11": 477,
+ "MeshifAct3IntroGossip1": 478,
+ "MeshifAct3IntroBarGossip1": 479,
+ "MeshifAct3Gossip1": 480,
+ "MeshifAct3Gossip2": 481,
+ "MeshifAct3Gossip3": 482,
+ "MeshifAct3Gossip4": 483,
+ "MeshifAct3Gossip5": 484,
+ "MeshifAct3Gossip6": 485,
+ "MeshifAct3Gossip7": 486,
+ "MeshifAct3Gossip8": 487,
+ "MeshifAct3Gossip9": 488,
+ "MeshifAct3Gossip10": 489,
+ "AshearaIntroGossip1": 490,
+ "AshearaIntroAmaGossip1": 491,
+ "AshearaGossip1": 492,
+ "AshearaGossip2": 493,
+ "AshearaGossip3": 494,
+ "AshearaGossip4": 495,
+ "AshearaGossip5": 496,
+ "AshearaGossip6": 497,
+ "AshearaGossip7": 498,
+ "AshearaGossip8": 499,
+ "AshearaGossip9": 500,
+ "AlkorIntroGossip1": 501,
+ "AlkorIntroNecGossip1": 502,
+ "AlkorGossip1": 503,
+ "AlkorGossip2": 504,
+ "AlkorGossip3": 505,
+ "AlkorGossip4": 506,
+ "AlkorGossip5": 507,
+ "AlkorGossip6": 508,
+ "AlkorGossip7": 509,
+ "AlkorGossip8": 510,
+ "AlkorGossip9": 511,
+ "AlkorGossip10": 512,
+ "AlkorGossip11": 513,
+ "OrmusIntroGossip1": 514,
+ "OrmusIntroPalGossip1": 515,
+ "OrmusGossip1": 516,
+ "OrmusGossip2": 517,
+ "OrmusGossip3": 518,
+ "OrmusGossip4": 519,
+ "OrmusGossip5": 520,
+ "OrmusGossip6": 521,
+ "OrmusGossip7": 522,
+ "OrmusGossip8": 523,
+ "OrmusGossip9": 524,
+ "OrmusGossip10": 525,
+ "OrmusGossip11": 526,
+ "A3Q4Init1CainAct3": 527,
+ "A3Q4Init1Asheara": 528,
+ "A3Q4Init2MeshifAct3": 529,
+ "A3Q4Init2Natalya": 530,
+ "A3Q4Init3CainAct3": 531,
+ "A3Q4Init3Hratli": 532,
+ "A3Q4Init3Asheara": 533,
+ "A3Q4AfterInitAlkor": 534,
+ "A3Q4AfterInitOrmus": 535,
+ "A3Q4AfterInitHratli": 536,
+ "A3Q4AfterInitNatalya": 537,
+ "A3Q4SuccessfulAlkor": 538,
+ "A3Q4SuccessfulMeshifAct3": 539,
+ "A3Q4SuccessfulCainAct3": 540,
+ "A3Q4SuccessfulOrmus": 541,
+ "A3Q4SuccessfulNatalya": 542,
+ "A3Q2InitCain": 543,
+ "A3Q2EarlyReturnHeartCain": 544,
+ "A3Q2EarlyReturnEyeCain": 545,
+ "A3Q2EarlyReturnBrainCain": 546,
+ "A3Q2EarlyReturnFlailCain": 547,
+ "A3Q2SuccessfulCain": 548,
+ "A3Q1InitAlkor": 549,
+ "A3Q1AfterInitAlkor": 550,
+ "A3Q1AfterInitOrmus": 551,
+ "A3Q1AfterInitMeshifAct3": 552,
+ "A3Q1AfterInitAsheara": 553,
+ "A3Q1AfterInitHratli": 554,
+ "A3Q1AfterInitCainAct3": 555,
+ "A3Q1AfterInitNatalya": 556,
+ "A3Q1EarlyReturnAlkor": 557,
+ "A3Q1EarlyReturnOrmus": 558,
+ "A3Q1EarlyReturnMeshifAct3": 559,
+ "A3Q1EarlyReturnAsheara": 560,
+ "A3Q1EarlyReturnHratli": 561,
+ "A3Q1EarlyReturnCainAct3": 562,
+ "A3Q1EarlyReturnNatalya": 563,
+ "A3Q1SuccessfulAlkor": 564,
+ "A3Q1SuccessfulOrmus": 565,
+ "A3Q1SuccessfulMeshifAct3": 566,
+ "A3Q1SuccessfulAsheara": 567,
+ "A3Q1SuccessfulHratli": 568,
+ "A3Q1SuccessfulCainAct3": 569,
+ "A3Q1SuccessfulNatalya": 570,
+ "A3Q3InitHratli": 571,
+ "A3Q3AfterInitAlkor": 572,
+ "A3Q3AfterInitOrmus": 573,
+ "A3Q3AfterInitMeshifAct3": 574,
+ "A3Q3AfterInitAsheara": 575,
+ "A3Q3AfterInitHratli": 576,
+ "A3Q3AfterInitCainAct3": 577,
+ "A3Q3AfterInitNatalya": 578,
+ "A3Q3EarlyReturnAlkor": 579,
+ "A3Q3EarlyReturnOrmus": 580,
+ "A3Q3EarlyReturnMeshifAct3": 581,
+ "A3Q3EarlyReturnAsheara": 582,
+ "A3Q3EarlyReturnHratli": 583,
+ "A3Q3EarlyReturnCainAct3": 584,
+ "A3Q3EarlyReturnNatalya": 585,
+ "A3Q3SuccessfulAlkor": 586,
+ "A3Q3SuccessfulOrmus": 587,
+ "A3Q3SuccessfulMeshifAct3": 588,
+ "A3Q3SuccessfulAsheara": 589,
+ "A3Q3SuccessfulHratli": 590,
+ "A3Q3SuccessfulCainAct3": 591,
+ "A3Q3SuccessfulNatalya": 592,
+ "A3Q3RewardOrmus": 593,
+ "A3Q5InitOrmus": 594,
+ "A3Q5AfterInitAlkor": 595,
+ "A3Q5AfterInitAlkorVA": 596,
+ "A3Q5AfterInitOrmus": 597,
+ "A3Q5AfterInitOrmusVA": 598,
+ "A3Q5AfterInitMeshifAct3": 599,
+ "A3Q5AfterInitMeshifAct3VA": 600,
+ "A3Q5AfterInitAsheara": 601,
+ "A3Q5AfterInitAshearaVA": 602,
+ "A3Q5AfterInitHratli": 603,
+ "A3Q5AfterInitHratliVA": 604,
+ "A3Q5AfterInitCainAct3": 605,
+ "A3Q5AfterInitCainAct3VA": 606,
+ "A3Q5AfterInitNatalya": 607,
+ "A3Q5AfterInitNatalyaVA": 608,
+ "A3Q5EarlyReturnAlkor": 609,
+ "A3Q5EarlyReturnAlkorVA": 610,
+ "A3Q5EarlyReturnOrmus": 611,
+ "A3Q5EarlyReturnMeshifAct3": 612,
+ "A3Q5EarlyReturnMeshifAct3VA": 613,
+ "A3Q5EarlyReturnAsheara": 614,
+ "A3Q5EarlyReturnAshearaVA": 615,
+ "A3Q5EarlyReturnHratli": 616,
+ "A3Q5EarlyReturnHratliVA": 617,
+ "A3Q5EarlyReturnCainAct3": 618,
+ "A3Q5EarlyReturnNatalya": 619,
+ "A3Q5EarlyReturnNatalyaVA": 620,
+ "A3Q5SuccessfulAlkor": 621,
+ "A3Q5SuccessfulOrmus": 622,
+ "A3Q5SuccessfulMeshifAct3": 623,
+ "A3Q5SuccessfulAsheara": 624,
+ "A3Q5SuccessfulHratli": 625,
+ "A3Q5SuccessfulCainAct3": 626,
+ "A3Q5SuccessfulNatalya": 627,
+ "A3Q6InitOrmus": 628,
+ "A3Q6AfterInitAlkor": 629,
+ "A3Q6AfterInitAlkorVA": 630,
+ "A3Q6AfterInitOrmus": 631,
+ "A3Q6AfterInitOrmusVA": 632,
+ "A3Q6AfterInitMeshifAct3": 633,
+ "A3Q6AfterInitMeshifAct3VA": 634,
+ "A3Q6AfterInitAsheara": 635,
+ "A3Q6AfterInitAshearaVA": 636,
+ "A3Q6AfterInitHratli": 637,
+ "A3Q6AfterInitHratliVA": 638,
+ "A3Q6AfterInitCainAct3": 639,
+ "A3Q6AfterInitCainAct3VA": 640,
+ "A3Q6AfterInitNatalya": 641,
+ "A3Q6AfterInitNatalyaVA": 642,
+ "A3Q6EarlyReturnAlkor": 643,
+ "A3Q6EarlyReturnAlkorVA": 644,
+ "A3Q6EarlyReturnOrmus": 645,
+ "A3Q6EarlyReturnOrmusVA": 646,
+ "A3Q6EarlyReturnMeshifAct3": 647,
+ "A3Q6EarlyReturnMeshifAct3VA": 648,
+ "A3Q6EarlyReturnAsheara": 649,
+ "A3Q6EarlyReturnAshearaVA": 650,
+ "A3Q6EarlyReturnHratli": 651,
+ "A3Q6EarlyReturnHratliVA": 652,
+ "A3Q6EarlyReturnCainAct3": 653,
+ "A3Q6EarlyReturnCainAct3VA": 654,
+ "A3Q6EarlyReturnNatalya": 655,
+ "A3Q6EarlyReturnNatalyaVA": 656,
+ "A3Q6SuccessfulAlkor": 657,
+ "A3Q6SuccessfulOrmus": 658,
+ "A3Q6SuccessfulMeshifAct3": 659,
+ "A3Q6SuccessfulAsheara": 660,
+ "A3Q6SuccessfulHratli": 661,
+ "A3Q6SuccessfulCainAct3": 662,
+ "A3Q6SuccessfulNatalya": 663,
+ "TyraelActIntroGossip1": 664,
+ "TyraelAct4Gossip1": 665,
+ "CainAct4IntroGossip1": 666,
+ "CainAct4Gossip1": 667,
+ "HellsAngelGossip1": 668,
+ "HellsAngelGossip2": 669,
+ "A4Q1InitTyrael": 670,
+ "A4Q1AfterInitTyrael": 671,
+ "A4Q1AfterInitCain": 672,
+ "A4Q1EarlyReturnTyrael": 673,
+ "A4Q1EarlyReturnCain": 674,
+ "A4Q1SuccessfulIzual": 675,
+ "A4Q1SuccessfulTyrael": 676,
+ "A4Q1SuccessfulCain": 677,
+ "A4Q3InitHasStoneCain": 678,
+ "A4Q3InitNoStoneCain": 679,
+ "A4Q3SuccessfulCain": 680,
+ "A4Q2InitTyrael": 681,
+ "A4Q2AfterInitCain": 682,
+ "A4Q2AfterInitTyrael": 683,
+ "A4Q2SuccessfulTyrael": 684,
+ "A4Q2SuccessfulCain": 685,
+ "D2bnetHelp50": 686,
+ "D2bnetHelp": 687,
+ "D2bnetHelp2a": 688,
+ "D2bnetHelpa": 689,
+ "D2bnetHelp1": 690,
+ "D2bnetHelp2": 691,
+ "D2bnetHelp3": 692,
+ "D2bnetHelp4": 693,
+ "D2bnetHelp5": 694,
+ "D2bnetHelp5a": 695,
+ "D2bnetHelp6": 696,
+ "D2bnetHelp7": 697,
+ "D2bnetHelp8": 698,
+ "D2bnetHelp9": 699,
+ "D2bnetHelp10": 700,
+ "D2bnetHelp11": 701,
+ "D2bnetHelp36": 702,
+ "D2bnetHelp36a": 703,
+ "D2bnetHelp37": 704,
+ "D2bnetHelp37a": 705,
+ "D2bnetHelp38": 706,
+ "D2bnetHelp39": 707,
+ "D2bnetHelp40": 708,
+ "D2bnetHelp41": 709,
+ "D2bnetHelp42": 710,
+ "D2bnetHelp42a": 711,
+ "D2bnetHelp43": 712,
+ "D2bnetHelp44": 713,
+ "D2bnetHelp44ab": 714,
+ "D2bnetHelp44a": 715,
+ "D2bnetHelp45": 716,
+ "D2bnetHelp45b": 717,
+ "D2bnetHelp45a": 718,
+ "D2bnetHel46": 719,
+ "D2bnetHelp46a": 720,
+ "D2bnetHelp47": 721,
+ "D2bnetHelp48": 722,
+ "D2bnetHelp49": 723,
+ "D2bnetHelp12": 724,
+ "D2bnetHelp12c": 725,
+ "D2bnetHelp12b": 726,
+ "D2bnetHelp12a": 727,
+ "D2bnetHelp13": 728,
+ "D2bnetHelp13b": 729,
+ "D2bnetHelp13a": 730,
+ "D2bnetHelp14": 731,
+ "D2bnetHelp14a": 732,
+ "D2bnetHelp15": 733,
+ "D2bnetHelp15b": 734,
+ "D2bnetHelp15a": 735,
+ "D2bnetHelp16": 736,
+ "D2bnetHelp16b": 737,
+ "D2bnetHelp16a": 738,
+ "D2bnetHelp17": 739,
+ "D2bnetHelp17a": 740,
+ "D2bnetHelp18": 741,
+ "D2bnetHelp18a": 742,
+ "D2bnetHelp19": 743,
+ "D2bnetHelp19a": 744,
+ "D2bnetHelp20": 745,
+ "D2bnetHelp20a": 746,
+ "D2bnetHelp21": 747,
+ "D2bnetHelp21a": 748,
+ "D2bnetHelp22": 749,
+ "D2bnetHelp22a": 750,
+ "D2bnetHelp23": 751,
+ "D2bnetHelp23a": 752,
+ "D2bnetHelp24": 753,
+ "D2bnetHelp24a": 754,
+ "D2bnetHelp25": 755,
+ "D2bnetHelp25a": 756,
+ "D2bnetHelp26": 757,
+ "D2bnetHelp26b": 758,
+ "D2bnetHelp26a": 759,
+ "D2bnetHelp27": 760,
+ "D2bnetHelp27a": 761,
+ "D2bnetHelp28": 762,
+ "D2bnetHelp28a": 763,
+ "D2bnetHelp29": 764,
+ "D2bnetHelp29a": 765,
+ "D2bnetHelp30": 766,
+ "D2bnetHelp30a": 767,
+ "D2bnetHelp31": 768,
+ "D2bnetHelp31a": 769,
+ "D2bnetHelp32": 770,
+ "D2bnetHelp32a": 771,
+ "D2bnetHelp33": 772,
+ "D2bnetHelp34": 773,
+ "D2bnetHelp35": 774,
+ "D2bnetHelp51": 775,
+ "D2bnetHelp52": 776,
+ "D2bnetHelp53": 777,
+ "D2bnetHelp54": 778,
+ "D2bnetHelp55": 779,
+ "D2bnetHelp56": 780,
+ "D2bnetHelp57": 781,
+ "D2bnetHelp58": 782,
+ "D2bnetHelp59": 783,
+ "D2bnetHelp60": 784,
+ "D2bnetHelp61": 785,
+ "D2bnetHelp62": 786,
+ "D2bnetHelp63": 787,
+ "Moo Moo Farm": 788,
+ "Chaos Sanctum": 789,
+ "The Pandemonium Fortress": 790,
+ "River of Flame": 791,
+ "Outer Steppes": 792,
+ "Plains of Despair": 793,
+ "City of the Damned": 794,
+ "Durance of Hate Level 3": 795,
+ "Durance of Hate Level 2": 796,
+ "Durance of Hate Level 1": 797,
+ "Disused Reliquary": 798,
+ "Ruined Fane": 799,
+ "Forgotten Temple": 800,
+ "Forgotten Reliquary": 801,
+ "Disused Fane": 802,
+ "Ruined Temple": 803,
+ "Flayer Dungeon Level 3": 804,
+ "Flayer Dungeon Level 2": 805,
+ "Flayer Dungeon Level 1": 806,
+ "Swampy Pit Level 3": 807,
+ "Swampy Pit Level 2": 808,
+ "Swampy Pit Level 1": 809,
+ "Spider Cave": 810,
+ "Spider Cavern": 811,
+ "Travincal": 812,
+ "Kurast Causeway": 813,
+ "Upper Kurast": 814,
+ "Kurast Bazaar": 815,
+ "Lower Kurast": 816,
+ "Flayer Jungle": 817,
+ "Great Marsh": 818,
+ "Spider Forest": 819,
+ "Kurast Docktown": 820,
+ "Durance of Hate": 821,
+ "Flayer Dungeon": 822,
+ "Swampy Pit": 823,
+ "Arcane Sanctuary": 824,
+ "Duriel's Lair": 825,
+ "Tal Rasha's Tomb": 826,
+ "Ancient Tunnels": 827,
+ "Maggot Lair Level 3": 828,
+ "Maggot Lair Level 2": 829,
+ "Maggot Lair Level 1": 830,
+ "Claw Viper Temple Level 2": 831,
+ "Halls of the Dead Level 3": 832,
+ "Stony Tomb Level 2": 833,
+ "Claw Viper Temple Level 1": 834,
+ "Halls of the Dead Level 2": 835,
+ "Halls of the Dead Level 1": 836,
+ "Stony Tomb Level 1": 837,
+ "Palace Cellar Level 3": 838,
+ "Palace Cellar Level 2": 839,
+ "Palace Cellar Level 1 \tPalace Cellar Level 1": 840,
+ "Harem Level 2": 841,
+ "Harem Level 1": 842,
+ "Sewers Level 3": 843,
+ "Sewers Level 2": 844,
+ "Sewers Level 1": 845,
+ "Canyon of the Magi": 846,
+ "Valley of Snakes": 847,
+ "Lost City": 848,
+ "Far Oasis": 849,
+ "Dry Hills": 850,
+ "Rocky Waste": 851,
+ "Lut Gholein": 852,
+ "Maggot Lair": 853,
+ "Claw Viper Temple": 854,
+ "Halls of the Dead": 855,
+ "Stony Tomb": 856,
+ "Palace Cellar": 857,
+ "Harem": 858,
+ "Sewers": 859,
+ "To The Moo Moo Farm": 860,
+ "To Chaos Sanctum": 861,
+ "To The River of Flame": 862,
+ "To The Outer Steppes": 863,
+ "To The Plains of Despair": 864,
+ "To The City of the Damned": 865,
+ "To The Pandemonium Fortress": 866,
+ "To The Durance of Hate Level 3": 867,
+ "To The Durance of Hate Level 2": 868,
+ "To The Durance of Hate Level 1": 869,
+ "To The Disused Reliquary": 870,
+ "To The Ruined Fane": 871,
+ "To The Forgotten Temple": 872,
+ "To The Forgotten Reliquary": 873,
+ "To The Disused Fane": 874,
+ "To The Ruined Temple": 875,
+ "To The Flayer Dungeon Level 1": 876,
+ "To The Flayer Dungeon Level 2": 877,
+ "To The Flayer Dungeon Level 3": 878,
+ "To The Swampy Pit Level 3": 879,
+ "To The Swampy Pit Level 2": 880,
+ "To The Swampy Pit Level 1": 881,
+ "To The Spider Cave": 882,
+ "To The Spider Cavern": 883,
+ "To Travincal": 884,
+ "To The Kurast Causeway": 885,
+ "To Upper Kurast": 886,
+ "To The Kurast Bazaar": 887,
+ "To Lower Kurast": 888,
+ "To The Flayer Jungle": 889,
+ "To The Great Marsh": 890,
+ "To The Spider Forest": 891,
+ "To The Kurast Docktown": 892,
+ "To The Arcane Sanctuary": 893,
+ "To Duriel's Lair": 894,
+ "To Tal Rasha's Tomb": 895,
+ "To The Ancient Tunnels": 896,
+ "To The Maggot Lair Level 3": 897,
+ "To The Maggot Lair Level 2": 898,
+ "To The Maggot Lair Level 1": 899,
+ "To The Claw Viper Temple Level 2": 900,
+ "To The Halls of the Dead Level 3": 901,
+ "To The Stony Tomb Level 2": 902,
+ "To The Claw Viper Temple Level 1": 903,
+ "To The Halls of the Dead Level 2": 904,
+ "To The Halls of the Dead Level 1": 905,
+ "To The Stony Tomb Level 1": 906,
+ "To The Palace Cellar Level 3": 907,
+ "To The Palace Cellar Level 2": 908,
+ "To The Palace Cellar Level 1 \tTo The Palace Cellar Level 1 ": 909,
+ "To The Harem Level 2": 910,
+ "To The Harem Level 1": 911,
+ "To The Sewers Level 3": 912,
+ "To The Sewers Level 2": 913,
+ "To The Sewers Level 1": 914,
+ "To The Canyon of the Magi": 915,
+ "To The Valley of Snakes": 916,
+ "To The Lost City": 917,
+ "To The Far Oasis": 918,
+ "To The Dry Hills": 919,
+ "To The Rocky Waste": 920,
+ "To Lut Gholein": 921,
+ "qstsa2q0": 922,
+ "qstsa2q1": 923,
+ "qstsa2q2": 924,
+ "qstsa2q3": 925,
+ "qstsa2q4": 926,
+ "qstsa2q5": 927,
+ "qstsa2q6": 928,
+ "qstsa3q0": 929,
+ "qstsa3q1": 930,
+ "qstsa3q2": 931,
+ "qstsa3q3": 932,
+ "qstsa3q4": 933,
+ "qstsa3q5": 934,
+ "qstsa3q6": 935,
+ "qstsa4q0": 936,
+ "qstsa4q1": 937,
+ "qstsa4q2": 938,
+ "qstsa4q3": 939,
+ "qstsa2q01": 940,
+ "qstsa2q11": 941,
+ "qstsa2q12": 942,
+ "qstsa2q13": 943,
+ "qstsa2q21": 944,
+ "qstsa2q22": 945,
+ "qstsa2q23": 946,
+ "qstsa2q24": 947,
+ "qstsa2q25": 948,
+ "qstsa2q31": 949,
+ "qstsa2q31a": 950,
+ "qstsa2q32": 951,
+ "qstsa2q33": 952,
+ "qstsa2q41": 953,
+ "qstsa2q41a": 954,
+ "qstsa2q42": 955,
+ "qstsa2q43": 956,
+ "qstsa2q51": 957,
+ "qstsa2q52": 958,
+ "qstsa2q53": 959,
+ "qstsa2q61": 960,
+ "qstsa2q61a": 961,
+ "qstsa2q62": 962,
+ "qstsa2q63": 963,
+ "qstsa2q63a": 964,
+ "qstsa2q64": 965,
+ "qstsa2q65": 966,
+ "qstsa3q01": 967,
+ "qstsa3q11": 968,
+ "qstsa3q12": 969,
+ "qstsa3q21": 970,
+ "qstsa3q22": 971,
+ "qstsa3q23": 972,
+ "qstsa3q24": 973,
+ "qstsa3q25": 974,
+ "qstsa3q26": 975,
+ "qstsa3q21a": 976,
+ "qstsa3q31": 977,
+ "qstsa3q32": 978,
+ "qstsa3q33": 979,
+ "qstsa3q34": 980,
+ "qstsa3q35": 981,
+ "qstsa3q41": 982,
+ "qstsa3q42": 983,
+ "qstsa3q43": 984,
+ "qstsa3q44": 985,
+ "qstsa3q45": 986,
+ "qstsa3q51": 987,
+ "qstsa3q52": 988,
+ "qstsa3q53": 989,
+ "qstsa3q61": 990,
+ "qstsa3q62": 991,
+ "qstsa3q63": 992,
+ "qstsa3q31a": 993,
+ "qstsa3q51a": 994,
+ "qstsa3q61a": 995,
+ "qstsa4q11": 996,
+ "qstsa4q12": 997,
+ "qstsa4q13a": 998,
+ "qstsa4q13": 999,
+ "qstsa4q31": 1000,
+ "qstsa4q32": 1001,
+ "qstsa4q33": 1002,
+ "qstsa4q34": 1003,
+ "qstsa4q21": 1004,
+ "qstsa4q22": 1005,
+ "qstsa4q23": 1006,
+ "qstsa4q24": 1007,
+ "asheara": 1008,
+ "hratli": 1009,
+ "alkor": 1010,
+ "ormus": 1011,
+ "nikita": 1012,
+ "tyrael": 1013,
+ "Izual": 1014,
+ "izual": 1015,
+ "Jamella": 1016,
+ "halbu": 1017,
+ "Malachai": 1018,
+ "merca201": 1019,
+ "merca202": 1020,
+ "merca203": 1021,
+ "merca204": 1022,
+ "merca205": 1023,
+ "merca206": 1024,
+ "merca207": 1025,
+ "merca208": 1026,
+ "merca209": 1027,
+ "merca210": 1028,
+ "merca211": 1029,
+ "merca212": 1030,
+ "merca213": 1031,
+ "merca214": 1032,
+ "merca215": 1033,
+ "merca216": 1034,
+ "merca217": 1035,
+ "merca218": 1036,
+ "merca219": 1037,
+ "merca220": 1038,
+ "merca221": 1039,
+ "merca222": 1040,
+ "merca223": 1041,
+ "merca224": 1042,
+ "merca225": 1043,
+ "merca226": 1044,
+ "merca227": 1045,
+ "merca228": 1046,
+ "merca229": 1047,
+ "merca230": 1048,
+ "merca231": 1049,
+ "merca232": 1050,
+ "merca233": 1051,
+ "merca234": 1052,
+ "merca235": 1053,
+ "merca236": 1054,
+ "merca237": 1055,
+ "merca238": 1056,
+ "merca239": 1057,
+ "merca240": 1058,
+ "merca241": 1059,
+ "qf1": 1060,
+ "qf2": 1061,
+ "KhalimFlail": 1062,
+ "SuperKhalimFlail": 1063,
+ "qey": 1064,
+ "qbr": 1065,
+ "qhr": 1066,
+ "The Feature Creep": 1067,
+ "Hell Bovine": 1068,
+ "Playersubtitles00": 1069,
+ "Playersubtitles01": 1070,
+ "Playersubtitles02": 1071,
+ "Playersubtitles03": 1072,
+ "Playersubtitles04": 1073,
+ "Playersubtitles05": 1074,
+ "Playersubtitles06": 1075,
+ "Playersubtitles07": 1076,
+ "Playersubtitles09": 1077,
+ "Playersubtitles10": 1078,
+ "Playersubtitles11": 1079,
+ "Playersubtitles12": 1080,
+ "Playersubtitles13": 1081,
+ "Playersubtitles14": 1082,
+ "Playersubtitles15": 1083,
+ "Playersubtitles16": 1084,
+ "Playersubtitles17": 1085,
+ "Playersubtitles18": 1086,
+ "Playersubtitles21": 1087,
+ "Playersubtitles22": 1088,
+ "Playersubtitles23": 1089,
+ "Playersubtitles24": 1090,
+ "Playersubtitles25": 1091,
+ "Playersubtitles26": 1092,
+ "Playersubtitles27": 1093,
+ "Playersubtitles28": 1094,
+ "LeaveCampAma": 1095,
+ "LeaveCampBar": 1096,
+ "LeaveCampPal": 1097,
+ "LeaveCampSor": 1098,
+ "LeaveCampNec": 1099,
+ "EnterDOEAma": 1100,
+ "EnterDOEBar": 1101,
+ "EnterDOEPal": 1102,
+ "EnterDOESor": 1103,
+ "EnterDOENec": 1104,
+ "EnterBurialAma": 1105,
+ "EnterBurialBar": 1106,
+ "EnterBurialPal": 1107,
+ "EnterBurialSor": 1108,
+ "EnterBurialNec": 1109,
+ "EnterMonasteryAma": 1110,
+ "EnterMonasteryBar": 1111,
+ "EnterMonasteryPal": 1112,
+ "EnterMonasterySor": 1113,
+ "EnterMonasteryNec": 1114,
+ "EnterForgottenTAma": 1115,
+ "EnterForgottenTBar": 1116,
+ "EnterForgottenTPal": 1117,
+ "EnterForgottenTSor": 1118,
+ "EnterForgottenTNec": 1119,
+ "EnterJailAma": 1120,
+ "EnterJailBar": 1121,
+ "EnterJailPal": 1122,
+ "EnterJailSor": 1123,
+ "EnterJailNec": 1124,
+ "Barracksremoved": 1129,
+ "EnterCatacombsAma": 1130,
+ "EnterCatacombsBar": 1131,
+ "EnterCatacombsPal": 1132,
+ "EnterCatacombsSor": 1133,
+ "EnterCatacombsNec": 1134,
+ "CompletingDOEAma": 1135,
+ "CompletingDOEBar": 1136,
+ "CompletingDOEPal": 1137,
+ "CompletingDOESor": 1138,
+ "CompletingDOENec": 1139,
+ "CompletingBurialAma": 1140,
+ "CompletingBurialBar": 1141,
+ "CompletingBurialPal": 1142,
+ "CompletingBurialSor": 1143,
+ "CompletingBurialNec": 1144,
+ "FindingInifusAma": 1145,
+ "FindingInifusBar": 1146,
+ "FindingInifusPal": 1147,
+ "FindingInifusSor": 1148,
+ "FindingInifusNec": 1149,
+ "FindingCairnAma": 1150,
+ "FindingCairnBar": 1151,
+ "FindingCairnPal": 1152,
+ "FindingCairnSor": 1153,
+ "FindingCairnNec": 1154,
+ "FindingTristramAma": 1155,
+ "FindingTristramBar": 1156,
+ "FindingTristramPal": 1157,
+ "FindingTristramSor": 1158,
+ "FindingTristramNec": 1159,
+ "RescueCainAma": 1160,
+ "RescueCainBar": 1161,
+ "RescueCainPal": 1162,
+ "RescueCainSor": 1163,
+ "RescueCainNec": 1164,
+ "HoradricMalusAma": 1165,
+ "HoradricMalusBar": 1166,
+ "HoradricMalusPal": 1167,
+ "HoradricMalusSor": 1168,
+ "HoradricMalusNec": 1169,
+ "CompletingForgottenTAma": 1170,
+ "CompletingForgottenTBar": 1171,
+ "CompletingForgottenTPal": 21924,
+ "CompletingForgottenTSor": 1173,
+ "CompletingForgottenTNec": 1174,
+ "CompletingAndarielAma": 1175,
+ "CompletingAndarielBar": 1176,
+ "CompletingAndarielPal": 1177,
+ "CompletingAndarielSor": 1178,
+ "CompletingAndarielNec": 1179,
+ "EnteringRadamentAma": 1180,
+ "EnteringRadamentBar": 1181,
+ "EnteringRadamentPal": 1182,
+ "EnteringRadamentSor": 1183,
+ "EnteringRadamentNec": 1184,
+ "CompletingRadamentAma": 1185,
+ "CompletingRadamentBar": 1186,
+ "CompletingRadamentPal": 1187,
+ "CompletingRadamentSor": 1188,
+ "CompletingRadamentNec": 1189,
+ "BeginTaintedSunAma": 1190,
+ "BeginTaintedSunBar": 1191,
+ "BeginTaintedSunPal": 1192,
+ "BeginTaintedSunSor": 1193,
+ "BeginTaintedSunNec": 1194,
+ "EnteringClawViperAma": 1195,
+ "EnteringClawViperBar": 1196,
+ "EnteringClawViperPal": 1197,
+ "EnteringClawViperSor": 1198,
+ "EnteringClawViperNec": 1199,
+ "CompletingTaintedSunAma": 1200,
+ "CompletingTaintedSunBar": 1201,
+ "CompletingTaintedSunPal": 1202,
+ "CompletingTaintedSunSor": 1203,
+ "CompletingTaintedSunNec": 1204,
+ "EnteringArcaneAma": 1205,
+ "EnteringArcaneBar": 1206,
+ "EnteringArcanePal": 1207,
+ "EnteringArcaneSor": 1208,
+ "EnteringArcaneNec": 1209,
+ "FindingSummonerAma": 1210,
+ "FindingSummonerBar": 1211,
+ "FindingSummonerPal": 1212,
+ "FindingSummonerSor": 1213,
+ "FindingSummonerNec": 1214,
+ "CompletingSummonerAma": 1215,
+ "CompletingSummonerBar": 1216,
+ "CompletingSummonerPal": 1217,
+ "CompletingSummonerSor": 1218,
+ "CompletingSummonerNec": 1219,
+ "FindingdecoyTombAma": 1220,
+ "FindingdecoyTombBar": 1221,
+ "FindingdecoyTombPal": 1222,
+ "FindingdecoyTombSor": 1223,
+ "FindingdecoyTombNec": 1224,
+ "FindingTrueTombAma": 1225,
+ "FindingTrueTombBar": 1226,
+ "FindingTrueTombPal": 1227,
+ "FindingTrueTombSor": 1228,
+ "FindingTrueTombNec": 1229,
+ "CompletingTombAma": 1230,
+ "CompletingTombBar": 1231,
+ "CompletingTombPal": 1232,
+ "CompletingTombSor": 1233,
+ "CompletingTombNec": 1234,
+ "nodarkwanderer": 1235,
+ "FindingLamEsenAma": 1236,
+ "FindingLamEsenBar": 1237,
+ "FindingLamEsenPal": 1238,
+ "FindingLamEsenSor": 1239,
+ "FindingLamEsenNec": 1240,
+ "CompletingLamEsenAma": 1241,
+ "CompletingLamEsenBar": 1242,
+ "CompletingLamEsenPal": 1243,
+ "CompletingLamEsenSor": 1244,
+ "CompletingLamEsenNec": 1245,
+ "FindingBeneathCityAma": 1246,
+ "FindingBeneathCityBar": 1247,
+ "FindingBeneathCityPal": 1248,
+ "FindingBeneathCitySor": 1249,
+ "FindingBeneathCityNec": 1250,
+ "FindingDrainLeverAma": 1251,
+ "FindingDrainLeverBar": 1252,
+ "FindingDrainLeverPal": 1253,
+ "FindingDrainLeverSor": 1254,
+ "FindingDrainLeverNec": 1255,
+ "CompletingBeneathCityAma": 1256,
+ "CompletingBeneathCityBar": 1257,
+ "CompletingBeneathCityPal": 1258,
+ "CompletingBeneathCitySor": 1259,
+ "CompletingBeneathCityNec": 1260,
+ "CompletingBladeAma": 1261,
+ "CompletingBladeBar": 1262,
+ "CompletingBladePal": 1263,
+ "CompletingBladeSor": 1264,
+ "CompletingBladeNec": 1265,
+ "FindingJadeFigAma": 1270,
+ "FindingTempleAma": 1271,
+ "FindingTempleBar": 1272,
+ "FindingTemplePal": 1273,
+ "FindingTempleSor": 1274,
+ "FindingTempleNec": 1275,
+ "CompletingTempleAma": 1276,
+ "CompletingTempleBar": 1277,
+ "CompletingTemplePal": 1278,
+ "CompletingTempleSor": 1279,
+ "CompletingTempleNec": 1280,
+ "FindingGuardianTowerAma": 1281,
+ "FindingGuardianTowerBar": 1282,
+ "FindingGuardianTowerPal": 1283,
+ "FindingGuardianTowerSor": 1284,
+ "FindingGuardianTowerNec": 1285,
+ "CompletingGuardianTowerAma": 1286,
+ "CompletingGuardianTowerBar": 1287,
+ "CompletingGuardianTowerPal": 1288,
+ "CompletingGuardianTowerSor": 1289,
+ "CompletingGuardianTowerNec": 1290,
+ "FreezingIzualAma": 21972,
+ "FreezingIzualBar": 1292,
+ "FreezingIzualPal": 1293,
+ "FreezingIzualSor": 1294,
+ "FreezingIzualNec": 1295,
+ "Eskillname0": 1296,
+ "Eskillsd0": 1297,
+ "Eskillld0": 1298,
+ "Eskillan0": 1299,
+ "EskillnameExp1": 1300,
+ "EskillsExpd1": 1301,
+ "EskilllExpd1": 1302,
+ "EskillExpan1": 1303,
+ "Eskillname2": 1304,
+ "Eskillsd2": 1305,
+ "Eskillld2": 1306,
+ "Eskillan2": 1307,
+ "Eskillname3": 1308,
+ "Eskillsd3": 1309,
+ "Eskillld3": 1310,
+ "Eskillan3": 1311,
+ "Eskillname4": 1312,
+ "Eskillsd4": 1313,
+ "Eskillld4": 1314,
+ "Eskillan4": 1315,
+ "Eskillname5": 1316,
+ "Eskillsd5": 1317,
+ "Eskillld5": 1318,
+ "Eskillan5": 1319,
+ "Eskillname6": 1320,
+ "Eskillsd6": 1321,
+ "Eskillld6": 1322,
+ "Eskillan6": 1323,
+ "Eskillname7": 1324,
+ "Eskillsd7": 1325,
+ "Eskillld7": 1326,
+ "Eskillan7": 1327,
+ "Eskillname8": 1328,
+ "Eskillsd8": 1329,
+ "Eskillld8": 1330,
+ "Eskillan8": 1331,
+ "Eskillname9": 1332,
+ "Eskillsd9": 1333,
+ "Eskillld9": 1334,
+ "Eskillan9": 1335,
+ "Eskillname10": 1336,
+ "Eskillsd10": 1337,
+ "Eskillld10": 1338,
+ "Eskillan10": 1339,
+ "Eskillname11": 1340,
+ "Eskillsd11": 1341,
+ "Eskillld11": 1342,
+ "Eskillan11": 1343,
+ "Eskillname12": 1344,
+ "Eskillsd12": 1345,
+ "Eskillld12": 1346,
+ "Eskillan12": 1347,
+ "Eskillname13": 1348,
+ "Eskillsd13": 1349,
+ "Eskillld13": 1350,
+ "Eskillan13": 1351,
+ "Eskillname14": 1352,
+ "Eskillsd14": 1353,
+ "Eskillld14": 1354,
+ "Eskillan14": 1355,
+ "Eskillname15": 1356,
+ "Eskillsd15": 1357,
+ "Eskillld15": 1358,
+ "Eskillan15": 1359,
+ "Eskillname16": 1360,
+ "Eskillsd16": 1361,
+ "Eskillld16": 1362,
+ "Eskillan16": 1363,
+ "Eskillname17": 1364,
+ "Eskillsd17": 1365,
+ "Eskillld17": 1366,
+ "Eskillan17": 1367,
+ "Eskillname18": 1368,
+ "Eskillsd18": 1369,
+ "Eskillld18": 1370,
+ "Eskillan18": 1371,
+ "Eskillname19": 1372,
+ "Eskillsd19": 1373,
+ "Eskillld19": 1374,
+ "Eskillan19": 1375,
+ "Eskillname20": 1376,
+ "Eskillsd20": 1377,
+ "Eskillld20": 1378,
+ "Eskillan20": 1379,
+ "Eskillname21": 1380,
+ "Eskillsd21": 1381,
+ "Eskillld21": 1382,
+ "Eskillan21": 1383,
+ "Eskillname22": 1384,
+ "Eskillsd22": 1385,
+ "Eskillld22": 1386,
+ "Eskillan22": 1387,
+ "Eskillname23": 1388,
+ "Eskillsd23": 1389,
+ "Eskillld23": 1390,
+ "Eskillan23": 1391,
+ "Eskillname24": 1392,
+ "Eskillsd24": 1393,
+ "Eskillld24": 1394,
+ "Eskillan24": 1395,
+ "Eskillname25": 1396,
+ "Eskillsd25": 1397,
+ "Eskillld25": 1398,
+ "Eskillan25": 1399,
+ "Eskillname26": 1400,
+ "Eskillsd26": 1401,
+ "Eskillld26": 1402,
+ "Eskillan26": 1403,
+ "Eskillname27": 1404,
+ "Eskillsd27": 1405,
+ "Eskillld27": 1406,
+ "Eskillan27": 1407,
+ "Eskillname28": 1408,
+ "Eskillsd28": 1409,
+ "Eskillld28": 1410,
+ "Eskillan28": 1411,
+ "Eskillname29": 1412,
+ "Eskillsd29": 1413,
+ "Eskillld29": 1414,
+ "Eskillan29": 1415,
+ "Eskillname30": 1416,
+ "Eskillsd30": 1417,
+ "Eskillld30": 1418,
+ "Eskillan30": 1419,
+ "Eskillname31": 1420,
+ "Eskillsd31": 1421,
+ "Eskillld31": 1422,
+ "Eskillan31": 1423,
+ "Eskillname32": 1424,
+ "Eskillsd32": 1425,
+ "Eskillld32": 1426,
+ "Eskillan32": 1427,
+ "Eskillname33": 1428,
+ "Eskillsd33": 1429,
+ "Eskillld33": 1430,
+ "Eskillan33": 1431,
+ "Eskillname34": 1432,
+ "Eskillsd34": 1433,
+ "Eskillld34": 1434,
+ "Eskillan34": 1435,
+ "Eskillname35": 1436,
+ "Eskillsd35": 1437,
+ "Eskillld35": 1438,
+ "Eskillan35": 1439,
+ "Eskillname36": 1440,
+ "Eskillsd36": 1441,
+ "Eskillld36": 1442,
+ "Eskillan36": 1443,
+ "Eskillname37": 1444,
+ "Eskillsd37": 1445,
+ "Eskillld37": 1446,
+ "Eskillan37": 1447,
+ "Eskillname38": 1448,
+ "Eskillsd38": 1449,
+ "Eskillld38": 1450,
+ "Eskillan38": 1451,
+ "Eskillname39": 1452,
+ "Eskillsd39": 1453,
+ "Eskillld39": 1454,
+ "Eskillan39": 1455,
+ "Eskillname40": 1456,
+ "Eskillsd40": 1457,
+ "Eskillld40": 1458,
+ "Eskillan40": 1459,
+ "Eskillname41": 1460,
+ "Eskillsd41": 1461,
+ "Eskillld41": 1462,
+ "Eskillan41": 1463,
+ "Eskillname42": 1464,
+ "Eskillsd42": 1465,
+ "Eskillld42": 1466,
+ "Eskillan42": 1467,
+ "Eskillname43": 1468,
+ "Eskillsd43": 1469,
+ "Eskillld43": 1470,
+ "Eskillan43": 1471,
+ "Eskillname44": 1472,
+ "Eskillsd44": 1473,
+ "Eskillld44": 1474,
+ "Eskillan44": 1475,
+ "Eskillname45": 1476,
+ "Eskillsd45": 1477,
+ "Eskillld45": 1478,
+ "Eskillan45": 1479,
+ "Eskillname46": 1480,
+ "Eskillsd46": 1481,
+ "Eskillld46": 1482,
+ "Eskillan46": 1483,
+ "Eskillname47": 1484,
+ "Eskillsd47": 1485,
+ "Eskillld47": 1486,
+ "Eskillan47": 1487,
+ "Eskillname48": 1488,
+ "Eskillsd48": 1489,
+ "Eskillld48": 1490,
+ "Eskillan48": 1491,
+ "Eskillname49": 1492,
+ "Eskillsd49": 1493,
+ "Eskillld49": 1494,
+ "Eskillan49": 1495,
+ "Eskillname50": 1496,
+ "Eskillsd50": 1497,
+ "Eskillld50": 1498,
+ "Eskillan50": 1499,
+ "Eskillname51": 1500,
+ "Eskillsd51": 1501,
+ "Eskillld51": 1502,
+ "Eskillan51": 1503,
+ "Eskillname52": 1504,
+ "Eskillsd52": 1505,
+ "Eskillld52": 1506,
+ "Eskillan52": 1507,
+ "Eskillname53": 1508,
+ "Eskillsd53": 1509,
+ "Eskillld53": 1510,
+ "Eskillan53": 1511,
+ "Eskillname54": 1512,
+ "Eskillsd54": 1513,
+ "Eskillld54": 1514,
+ "Eskillan54": 1515,
+ "Eskillname55": 1516,
+ "Eskillsd55": 1517,
+ "Eskillld55": 1518,
+ "Eskillan55": 1519,
+ "Eskillname56": 1520,
+ "Eskillsd56": 1521,
+ "Eskillld56": 1522,
+ "Eskillan56": 1523,
+ "Eskillname57": 1524,
+ "Eskillsd57": 1525,
+ "Eskillld57": 1526,
+ "Eskillan57": 1527,
+ "Eskillname58": 1528,
+ "Eskillsd58": 1529,
+ "Eskillld58": 1530,
+ "Eskillan58": 1531,
+ "Eskillname59": 1532,
+ "Eskillsd59": 1533,
+ "Eskillld59": 1534,
+ "Eskillan59": 1535,
+ "ESkillHawk": 22278,
+ "ESkillSpikes": 22279,
+ "ESkillStars": 22280,
+ "ESkillWolf": 22281,
+ "ESkillWolves": 22282,
+ "ESkillShoots": 22283,
+ "ESkillTimes": 22284,
+ "ESkillSpikes2": 22285,
+ "ob1": 20281,
+ "ob2": 20282,
+ "ob3": 20283,
+ "ob4": 20284,
+ "ob5": 21778,
+ "ne1": 20332,
+ "ne2": 20333,
+ "ne3": 20334,
+ "ne4": 20335,
+ "ne5": 20336,
+ "dr1": 20320,
+ "dr2": 20318,
+ "dr3": 20319,
+ "dr4": 20317,
+ "dr5": 20321,
+ "as1": 20285,
+ "as2": 20286,
+ "as3": 20287,
+ "as4": 20288,
+ "as5": 20289,
+ "as6": 20290,
+ "as7": 20291,
+ "AmaOnly": 20426,
+ "SorOnly": 20427,
+ "NecOnly": 20428,
+ "PalOnly": 20429,
+ "BarOnly": 20430,
+ "DruOnly": 20431,
+ "AssOnly": 20432,
+ "WeaponDescH2H": 21258,
+ "Seige Tower": 22352,
+ "RotWalker": 22353,
+ "ReanimatedHorde": 22354,
+ "ProwlingDead": 22355,
+ "UnholyCorpse": 22356,
+ "DefiledWarrior": 22357,
+ "Seige Beast": 1580,
+ "CrushBiest": 22359,
+ "BloodBringer": 22360,
+ "GoreBearer": 22361,
+ "DeamonSteed": 22362,
+ "WailingSpirit": 22363,
+ "LifeSeeker": 22364,
+ "LifeStealer": 22365,
+ "DeathlyVisage": 22366,
+ "BoundSpirit": 22367,
+ "BanishedSoul": 22368,
+ "Deathexp": 22369,
+ "Minionexp": 22370,
+ "Slayerexp": 22371,
+ "IceBoar": 22372,
+ "FireBoar": 22373,
+ "HellSpawn": 22374,
+ "IceSpawn": 22375,
+ "GreaterHellSpawn": 22376,
+ "GreaterIceSpawn": 22377,
+ "FanaticMinion": 22378,
+ "BerserkSlayer": 22379,
+ "ConsumedFireBoar": 22380,
+ "ConsumedIceBoar": 22381,
+ "FrenziedHellSpawn": 22382,
+ "FrenziedIceSpawn": 22383,
+ "InsaneHellSpawn": 22384,
+ "InsaneIceSpawn": 22385,
+ "Succubusexp": 22386,
+ "VileTemptress": 22387,
+ "StygianHarlot": 22388,
+ "BlightWing": 1611,
+ "BloodWitch": 1612,
+ "Dominus": 22391,
+ "VileWitch": 22392,
+ "StygianFury": 22393,
+ "MageWing": 1616,
+ "HellWitch": 1617,
+ "OverSeer": 22396,
+ "Lasher": 22397,
+ "OverLord": 22398,
+ "BloodBoss": 22399,
+ "HellWhip": 22400,
+ "MinionSpawner": 22401,
+ "MinionSlayerSpawner": 22402,
+ "MinionIce/fireBoarSpawner": 22403,
+ "Minionice/hellSpawnSpawner": 22404,
+ "MinionGreaterIce/hellSpawnSpawner": 22405,
+ "Imp1": 22406,
+ "Imp2": 22407,
+ "Imp3": 22408,
+ "Imp4": 22409,
+ "Imp5": 22410,
+ "CapsJoinMenu4": 1633,
+ "CapsJoinMenu5": 1634,
+ "Guild 1": 1635,
+ "Guild 2": 1636,
+ "Guild 3": 1637,
+ "Guild 4": 1638,
+ "Guild 5": 1639,
+ "To Guild 5": 1640,
+ "To Guild 4": 1641,
+ "To Guild 3": 1642,
+ "To Guild 2": 1643,
+ "To Guild 1": 1644,
+ "CapsBnet9": 1645,
+ "CapsBnet10": 1646,
+ "CapsBnet11": 1647,
+ "CapsBnet12": 1648,
+ "CapsBnet13": 1649,
+ "CapsBnet14": 1650,
+ "CapsBnet15": 1651,
+ "CapsGuildName": 1652,
+ "CapsGuildTag": 1653,
+ "GuildText1": 1654,
+ "GuildText2": 1655,
+ "Ladder3": 1656,
+ "Ladder7": 1657,
+ "gmGuildTitle": 1658,
+ "gmGuildName": 1659,
+ "gmGuildTag": 1660,
+ "gmWWW": 1661,
+ "gmGuildCharter": 1662,
+ "gmGuildCurrentGolds": 1663,
+ "gmGuildNextLevel": 1664,
+ "gmGuildMaster": 1665,
+ "gmOfficer": 1666,
+ "gmName": 1667,
+ "gmClass": 1668,
+ "gmLevel": 1669,
+ "gmDonate": 1670,
+ "gmRemove": 1671,
+ "gmPal": 1672,
+ "gmSor": 1673,
+ "gmAma": 1674,
+ "gmNec": 1675,
+ "gmBar": 1676,
+ "gmChangeSym": 1677,
+ "gmChangeCharter": 1678,
+ "gmChangeWebLink": 1679,
+ "Guild Portal": 1680,
+ "createdguildsuccess": 1681,
+ "createdguildfailure": 1682,
+ "inviteguildsuccess": 1683,
+ "inviteguildfailure": 1684,
+ "inviteguildins": 1685,
+ "joinedguildsuccess": 1686,
+ "joinedguildfailure": 1687,
+ "quitguildsuccess": 1688,
+ "quitguildfailure": 1689,
+ "guildentererror": 1690,
+ "strGuildMasterKicked": 1691,
+ "strGuildPerk1": 1692,
+ "strGuildPerk2": 1693,
+ "strGuildPerk3": 1694,
+ "strGuildPerk4": 1695,
+ "strGuildPerk5": 1696,
+ "strGuildPerk6": 1697,
+ "strGuildGoldDonated": 1698,
+ "strGuildDonateGold": 1699,
+ "gmGuildCurrentGoldPopup": 1700,
+ "gmGuildNextLevelPopup": 1701,
+ "gmGuildDonateGoldPopup": 1702,
+ "Message Board": 1703,
+ "Trophy Case": 1704,
+ "Guild Vault": 1705,
+ "Steeg Stone": 1706,
+ "guildaccepticon": 1707,
+ "guildmsgtext": 1708,
+ "ScrollFormat": 1709,
+ "BookFormat": 1710,
+ "HiqualityFormat": 1711,
+ "LowqualityFormat": 1712,
+ "HerbFormat": 1713,
+ "MagicFormat": 1714,
+ "GemmedNormalName": 1715,
+ "BodyPartsFormat": 1716,
+ "PlayerBodyPartFormat": 1717,
+ "RareFormat": 1718,
+ "SetItemFormat": 1719,
+ "ChampionFormat": 1720,
+ "Monster1Format": 1721,
+ "Monster2Format": 1722,
+ "Low Quality": 1723,
+ "Damaged": 1724,
+ "Cracked": 1725,
+ "Crude": 20910,
+ "Hiquality": 1727,
+ "Gemmed": 1728,
+ "Resiliant": 1729,
+ "Sturdy": 1730,
+ "Strong": 1731,
+ "Glorious": 1732,
+ "Blessed": 1733,
+ "Saintly": 1734,
+ "Holy": 1735,
+ "Devious": 1736,
+ "Fortified": 1737,
+ "Urgent": 1738,
+ "Fleet": 1739,
+ "Muscular": 1740,
+ "Jagged": 1741,
+ "Deadly": 1742,
+ "Vicious": 1743,
+ "Brutal": 1744,
+ "Massive": 1745,
+ "Savage": 1746,
+ "Merciless": 1747,
+ "Vulpine": 1748,
+ "Swift": 1749,
+ "Artful": 1750,
+ "Skillful": 1751,
+ "Adroit": 1752,
+ "Tireless": 1753,
+ "Rugged": 1754,
+ "Bronze": 1755,
+ "Iron": 1756,
+ "Steel": 1757,
+ "Silver": 1758,
+ "Gold": 1759,
+ "Platinum": 1760,
+ "Meteoric": 1761,
+ "Sharp": 1762,
+ "Fine": 1763,
+ "Warrior's": 1764,
+ "Soldier's": 1765,
+ "Knight's": 1766,
+ "Lord's": 1767,
+ "King's": 1768,
+ "Howling": 1769,
+ "Fortuitous": 1770,
+ "Brilliant": 1771,
+ "Omniscient": 1772,
+ "Sage": 1773,
+ "Shrewd": 1774,
+ "Vivid": 1775,
+ "Glimmering": 1776,
+ "Glowing": 1777,
+ "Bright": 1778,
+ "Solar": 1779,
+ "Lizard's": 1780,
+ "Forceful": 1781,
+ "Snake's": 1782,
+ "Serpent's": 1783,
+ "Drake's": 1784,
+ "Dragon's": 1785,
+ "Wyrm's": 1786,
+ "Dazzling": 1787,
+ "Facinating": 1788,
+ "Prismatic": 1789,
+ "Azure": 1790,
+ "Lapis": 1791,
+ "Cobalt": 1792,
+ "Indigo": 1793,
+ "Sapphire": 1794,
+ "Cerulean": 1795,
+ "Red": 1796,
+ "Crimson": 1797,
+ "Burgundy": 1798,
+ "Garnet": 1799,
+ "Russet": 1800,
+ "Ruby": 1801,
+ "Vermilion": 1802,
+ "Orange": 1803,
+ "Ocher": 1804,
+ "Tangerine": 1805,
+ "Coral": 1806,
+ "Crackling": 1807,
+ "Amber": 1808,
+ "Forked": 1809,
+ "Green": 20905,
+ "Beryl": 1811,
+ "Jade": 1812,
+ "Viridian": 1813,
+ "Vital": 1814,
+ "Emerald": 1815,
+ "Enduring": 1816,
+ "Fletcher's": 1817,
+ "Archer's": 1818,
+ "Monk's": 1819,
+ "Priest's": 1820,
+ "Summoner's": 1821,
+ "Necromancer's": 1822,
+ "Angel's": 1823,
+ "Arch-Angel's": 1824,
+ "Slayer's": 1825,
+ "Berserker's": 2507,
+ "Kicking": 1827,
+ "Triumphant": 1828,
+ "Mighty": 1829,
+ "Energizing": 1830,
+ "Strengthening": 1831,
+ "Empowering": 1832,
+ "Brisk": 1833,
+ "Tough": 1834,
+ "Hardy": 1835,
+ "Robust": 1836,
+ "of Health": 1837,
+ "of Protection": 1838,
+ "of Absorption": 1839,
+ "of Warding": 1840,
+ "of the Sentinel": 1841,
+ "of Guarding": 1842,
+ "of Negation": 1843,
+ "of Piercing": 1844,
+ "of Bashing": 1845,
+ "of Puncturing": 1846,
+ "of Thorns": 1847,
+ "of Spikes": 1848,
+ "of Readiness": 1849,
+ "of Alacrity": 1850,
+ "of Swiftness": 1851,
+ "of Quickness": 1852,
+ "of Blocking": 1853,
+ "of Deflecting": 1854,
+ "of the Apprentice": 1855,
+ "of the Magus": 1856,
+ "of Frost": 1857,
+ "of the Glacier": 1858,
+ "of Warmth": 1859,
+ "of Flame": 1860,
+ "of Fire": 1861,
+ "of Burning": 1862,
+ "of Shock": 1863,
+ "of Lightning": 1864,
+ "of Thunder": 1865,
+ "of Craftsmanship": 1866,
+ "of Quality": 1867,
+ "of Maiming": 1868,
+ "of Slaying": 1869,
+ "of Gore": 1870,
+ "of Carnage": 1871,
+ "of Slaughter": 1872,
+ "of Worth": 1873,
+ "of Measure": 1874,
+ "of Excellence": 1875,
+ "of Performance": 1876,
+ "of Blight": 1877,
+ "of Venom": 1878,
+ "of Pestilence": 1879,
+ "of Dexterity": 1880,
+ "of Skill": 1881,
+ "of Accuracy": 1882,
+ "of Precision": 1883,
+ "of Perfection": 1884,
+ "of Balance": 1885,
+ "of Stability": 1886,
+ "of the Horse": 1887,
+ "of Regeneration": 1888,
+ "of Regrowth": 1889,
+ "of Vileness": 1890,
+ "of Greed": 1891,
+ "of Wealth": 1892,
+ "of Chance": 1893,
+ "of Fortune": 1894,
+ "of Energy": 1895,
+ "of the Mind": 1896,
+ "of Brilliance": 1897,
+ "of Sorcery": 1898,
+ "of Wizardry": 1899,
+ "of the Bear": 1900,
+ "of Light": 1901,
+ "of Radiance": 1902,
+ "of the Sun": 1903,
+ "of Life": 1904,
+ "of the Jackal": 1905,
+ "of the Fox": 1906,
+ "of the Wolf": 1907,
+ "of the Tiger": 1908,
+ "of the Mammoth": 1909,
+ "of the Colosuss": 1910,
+ "of the Leech": 1911,
+ "of the Locust": 1912,
+ "of the Bat": 1913,
+ "of the Vampire": 1914,
+ "of Defiance": 1915,
+ "of Remedy": 1916,
+ "of Amelioration": 1917,
+ "of Ice": 1918,
+ "of Simplicity": 1919,
+ "of Ease": 1920,
+ "of the Mule": 1921,
+ "of Strength": 1922,
+ "of Might": 1923,
+ "of the Ox": 1924,
+ "of the Giant": 1925,
+ "of the Titan": 1926,
+ "of Pacing": 1927,
+ "of Haste": 1928,
+ "of Speed": 1929,
+ "cap": 1930,
+ "skp": 1931,
+ "hlm": 1932,
+ "fhl": 1933,
+ "ghm": 1934,
+ "crn": 1935,
+ "msk": 1936,
+ "qui": 1937,
+ "lea": 1938,
+ "hla": 1939,
+ "stu": 1940,
+ "rng": 1941,
+ "scl": 1942,
+ "chn": 1943,
+ "brs": 1944,
+ "spl": 1945,
+ "plt": 1946,
+ "fld": 1947,
+ "gth": 1948,
+ "ful": 1949,
+ "aar": 1950,
+ "ltp": 1951,
+ "buc": 1952,
+ "sml": 1953,
+ "lrg": 1954,
+ "kit": 1955,
+ "tow": 1956,
+ "gts": 1957,
+ "lgl": 1958,
+ "vgl": 1959,
+ "mgl": 1960,
+ "tgl": 1961,
+ "hgl": 1962,
+ "lbt": 1963,
+ "vbt": 1964,
+ "mbt": 1965,
+ "tbt": 1966,
+ "hbt": 1967,
+ "lbl": 1968,
+ "vbl": 1969,
+ "mbl": 1970,
+ "tbl": 1971,
+ "hbl": 1972,
+ "bhm": 1973,
+ "bsh": 1974,
+ "spk": 1975,
+ "hax": 1976,
+ "axe": 1977,
+ "2ax": 1978,
+ "mpi": 1979,
+ "wax": 1980,
+ "lax": 1981,
+ "bax": 1982,
+ "btx": 1983,
+ "gax": 1984,
+ "gix": 1985,
+ "wnd": 1986,
+ "ywn": 1987,
+ "bwn": 1988,
+ "gwn": 1989,
+ "clb": 1990,
+ "scp": 1991,
+ "gsc": 1992,
+ "wsp": 1993,
+ "spc": 1994,
+ "mac": 1995,
+ "mst": 1996,
+ "fla": 1997,
+ "whm": 1998,
+ "mau": 1999,
+ "gma": 2000,
+ "ssd": 2001,
+ "scm": 2002,
+ "sbr": 2003,
+ "flc": 2004,
+ "crs": 2005,
+ "bsd": 2006,
+ "lsd": 2007,
+ "wsd": 2008,
+ "2hs": 2009,
+ "clm": 2010,
+ "gis": 2011,
+ "bsw": 2012,
+ "flb": 2013,
+ "gsd": 2014,
+ "dgr": 2015,
+ "dir": 2016,
+ "kri": 2017,
+ "bld": 2018,
+ "tkf": 2019,
+ "tax": 2020,
+ "bkf": 2021,
+ "bal": 2022,
+ "jav": 2023,
+ "pil": 2024,
+ "ssp": 2025,
+ "glv": 2026,
+ "tsp": 2027,
+ "spr": 2028,
+ "tri": 2029,
+ "brn": 2030,
+ "spt": 2031,
+ "pik": 2032,
+ "bar": 2033,
+ "vou": 2034,
+ "scy": 2035,
+ "pax": 2036,
+ "hal": 2037,
+ "wsc": 2038,
+ "sst": 2039,
+ "lst": 2040,
+ "cst": 2041,
+ "bst": 2042,
+ "wst": 2043,
+ "sbw": 2044,
+ "hbw": 2045,
+ "lbw": 2046,
+ "cbw": 2047,
+ "sbb": 2048,
+ "lbb": 2049,
+ "swb": 2050,
+ "lwb": 2051,
+ "lxb": 2052,
+ "mxb": 2053,
+ "hxb": 2054,
+ "rxb": 2055,
+ "xpk": 2056,
+ "xsh": 2057,
+ "xh9": 2058,
+ "zhb": 2059,
+ "ztb": 2060,
+ "zmb": 2061,
+ "zvb": 2062,
+ "zlb": 2063,
+ "xhb": 2064,
+ "xtb": 2065,
+ "xmb": 2066,
+ "xvb": 2067,
+ "xlb": 2068,
+ "xhg": 2069,
+ "xtg": 2070,
+ "xmg": 2071,
+ "xvg": 2072,
+ "xlg": 2073,
+ "xts": 2074,
+ "xow": 2075,
+ "xit": 2076,
+ "xrg": 2077,
+ "xml": 2078,
+ "xuc": 2079,
+ "xtp": 2080,
+ "xar": 2081,
+ "xul": 2082,
+ "xth": 2083,
+ "xld": 2084,
+ "xlt": 2085,
+ "xpl": 2086,
+ "xrs": 2087,
+ "xhn": 2088,
+ "xcl": 2089,
+ "xng": 2090,
+ "xtu": 2091,
+ "xla": 2092,
+ "xea": 2093,
+ "xui": 2094,
+ "xsk": 2095,
+ "xrn": 2096,
+ "xhm": 2097,
+ "xhl": 2098,
+ "xlm": 2099,
+ "xkp": 2100,
+ "xap": 2101,
+ "8rx": 2102,
+ "8hx": 2103,
+ "8mx": 2104,
+ "8lx": 2105,
+ "8lw": 2106,
+ "8sw": 2107,
+ "8l8": 2108,
+ "8s8": 2109,
+ "8cb": 2110,
+ "8lb": 2111,
+ "8hb": 2112,
+ "8sb": 2113,
+ "8ws": 2114,
+ "8bs": 2115,
+ "8cs": 2116,
+ "8ls": 2117,
+ "8ss": 2118,
+ "9wc": 2119,
+ "9h9": 2120,
+ "9pa": 2121,
+ "9s8": 2122,
+ "9vo": 2123,
+ "9b7": 2124,
+ "9p9": 2125,
+ "9st": 2126,
+ "9br": 2127,
+ "9tr": 2128,
+ "9sr": 2129,
+ "9ts": 2130,
+ "9gl": 2131,
+ "9s9": 2132,
+ "9pi": 2133,
+ "9ja": 2134,
+ "9b8": 2135,
+ "9bk": 2136,
+ "9ta": 2137,
+ "9tk": 2138,
+ "9bl": 2139,
+ "9kr": 2140,
+ "9di": 2141,
+ "9dg": 2142,
+ "9gd": 2143,
+ "9fb": 2144,
+ "9gs": 2145,
+ "9cm": 2146,
+ "92h": 2147,
+ "9wd": 2148,
+ "9ls": 2149,
+ "9bs": 2150,
+ "9cr": 2151,
+ "9fc": 2152,
+ "9sb": 2153,
+ "9sm": 2154,
+ "9ss": 2155,
+ "9gm": 2156,
+ "9m9": 2157,
+ "9wh": 2158,
+ "9fl": 2159,
+ "9mt": 2160,
+ "9ma": 2161,
+ "9sp": 2162,
+ "9ws": 2163,
+ "9qs": 2164,
+ "9sc": 2165,
+ "9cl": 2166,
+ "9gw": 2167,
+ "9bw": 2168,
+ "9yw": 2169,
+ "9wn": 2170,
+ "9gi": 2171,
+ "9ga": 2172,
+ "9bt": 2173,
+ "9ba": 2174,
+ "9la": 2175,
+ "9wa": 2176,
+ "9mp": 2177,
+ "92a": 2178,
+ "9ax": 2179,
+ "9ha": 2180,
+ "9b9": 2181,
+ "gpl": 2182,
+ "opl": 2183,
+ "gpm": 2184,
+ "opm": 2185,
+ "gps": 2186,
+ "ops": 2187,
+ "gidbinn": 2188,
+ "g33": 2189,
+ "d33": 2190,
+ "leg": 2191,
+ "Malus": 2192,
+ "hdm": 2193,
+ "hfh": 2194,
+ "hst": 2195,
+ "msf": 2196,
+ "orifice": 2197,
+ "elx": 2198,
+ "tbk": 2199,
+ "tsc": 2200,
+ "ibk": 2201,
+ "isc": 2202,
+ "RightClicktoUse": 2203,
+ "RightClicktoOpen": 2204,
+ "RightClicktoRead": 2205,
+ "InsertScrolls": 2206,
+ "vps": 2207,
+ "yps": 2208,
+ "rvs": 2209,
+ "rvl": 2210,
+ "wms": 2211,
+ "amu": 2212,
+ "vip": 2213,
+ "rin": 2214,
+ "gld": 2215,
+ "bks": 2216,
+ "bkd": 2217,
+ "aqv": 2218,
+ "tch": 2219,
+ "cqv": 2220,
+ "Key": 2221,
+ "key": 2222,
+ "luv": 2223,
+ "xyz": 2224,
+ "shrine": 2225,
+ "teleport pad": 2226,
+ "j34": 2227,
+ "g34": 2228,
+ "bbb": 2229,
+ "LamTome": 2230,
+ "box": 2231,
+ "tr1": 2232,
+ "mss": 2233,
+ "ass": 2234,
+ "ear": 2235,
+ "gcv": 2236,
+ "gfv": 2237,
+ "gsv": 2238,
+ "gzv": 2239,
+ "gpv": 2240,
+ "gcy": 2241,
+ "gfy": 2242,
+ "gsy": 2243,
+ "gly": 2244,
+ "gpy": 2245,
+ "gcb": 2246,
+ "gfb": 2247,
+ "gsb": 2248,
+ "glb": 2249,
+ "gpb": 2250,
+ "gcg": 2251,
+ "gfg": 2252,
+ "glg": 2253,
+ "gsg": 2254,
+ "gpg": 2255,
+ "gcr": 2256,
+ "gfr": 2257,
+ "gsr": 2258,
+ "glr": 2259,
+ "gpr": 2260,
+ "gcw": 2261,
+ "gfw": 2262,
+ "gsw": 2263,
+ "glw": 2264,
+ "gpw": 2265,
+ "hp1": 2266,
+ "hp2": 2267,
+ "hp3": 2268,
+ "hp4": 2269,
+ "hp5": 2270,
+ "mp1": 2271,
+ "mp2": 2272,
+ "mp3": 2273,
+ "mp4": 2274,
+ "mp5": 2275,
+ "hrb": 20434,
+ "skc": 2277,
+ "skf": 2278,
+ "sku": 2279,
+ "skl": 2280,
+ "skz": 2281,
+ "Beast": 2282,
+ "Eagle": 2283,
+ "Raven": 2284,
+ "Viper": 2285,
+ "GhoulRI": 2286,
+ "Skull": 2287,
+ "Blood": 2288,
+ "Dread": 2289,
+ "Doom": 2290,
+ "Grim": 2291,
+ "Bone": 2292,
+ "Death": 2293,
+ "Shadow": 2294,
+ "Storm": 2295,
+ "Rune": 2296,
+ "PlagueRI": 2297,
+ "Stone": 2298,
+ "Wraith": 2989,
+ "Spirit": 2300,
+ "Demon": 2301,
+ "Cruel": 2302,
+ "Empyrion": 2303,
+ "Bramble": 2304,
+ "Pain": 2305,
+ "Loath": 2306,
+ "Glyph": 2307,
+ "Imp": 2308,
+ "Fiend": 2309,
+ "Hailstone": 2310,
+ "Gale": 2311,
+ "Dire": 2312,
+ "Soul": 2313,
+ "Brimstone": 2314,
+ "Corpse": 2315,
+ "Carrion": 2316,
+ "Holocaust": 2317,
+ "Havoc": 2318,
+ "Bitter": 2319,
+ "Entropy": 2320,
+ "Chaos": 2321,
+ "Order": 2322,
+ "Rift": 2323,
+ "Corruption": 2324,
+ "bite": 2325,
+ "scratch": 2326,
+ "scalpel": 2327,
+ "fang": 2328,
+ "gutter": 2329,
+ "thirst": 2330,
+ "razor": 2331,
+ "scythe": 2332,
+ "edge": 2333,
+ "saw": 2334,
+ "splitter": 2335,
+ "cleaver": 2336,
+ "sever": 2337,
+ "sunder": 2338,
+ "rend": 2339,
+ "mangler": 2340,
+ "slayer": 2341,
+ "reaver": 2342,
+ "Spawn": 2343,
+ "gnash": 2344,
+ "star": 2345,
+ "blow": 2346,
+ "smasher": 2347,
+ "Bane": 2348,
+ "crusher": 2349,
+ "breaker": 2350,
+ "grinder": 2351,
+ "crack": 2352,
+ "mallet": 2353,
+ "knell": 2354,
+ "lance": 2355,
+ "spike": 2356,
+ "impaler": 2357,
+ "skewer": 2358,
+ "prod": 2359,
+ "scourge": 2360,
+ "wand": 2361,
+ "wrack": 2362,
+ "barb": 2363,
+ "needle": 2364,
+ "dart": 2365,
+ "bolt": 2366,
+ "quarrel": 2367,
+ "fletch": 2368,
+ "flight": 2369,
+ "nock": 2370,
+ "horn": 2371,
+ "stinger": 2372,
+ "quill": 2373,
+ "goad": 2374,
+ "branch": 2375,
+ "spire": 2376,
+ "song": 2377,
+ "call": 2378,
+ "cry": 2379,
+ "spell": 2380,
+ "chant": 2381,
+ "weaver": 2382,
+ "gnarl": 2383,
+ "visage": 2384,
+ "crest": 2385,
+ "circlet": 2386,
+ "veil": 2387,
+ "hood": 2388,
+ "mask": 2389,
+ "brow": 2390,
+ "casque": 2391,
+ "visor": 2392,
+ "cowl": 2393,
+ "hide": 2394,
+ "Pelt": 2395,
+ "carapace": 2396,
+ "coat": 2397,
+ "wrap": 2398,
+ "suit": 2399,
+ "cloak": 2400,
+ "shroud": 2401,
+ "jack": 2402,
+ "mantle": 2403,
+ "guard": 2404,
+ "badge": 2405,
+ "rock": 2406,
+ "aegis": 2407,
+ "ward": 2408,
+ "tower": 2409,
+ "shield": 2410,
+ "wing": 2411,
+ "mark": 2412,
+ "emblem": 2413,
+ "hand": 2414,
+ "fist": 2415,
+ "claw": 2416,
+ "clutches": 2417,
+ "grip": 2418,
+ "grasp": 2419,
+ "hold": 2420,
+ "touch": 2421,
+ "finger": 2422,
+ "knuckle": 2423,
+ "shank": 2424,
+ "spur": 2425,
+ "tread": 2426,
+ "stalker": 2427,
+ "greave": 2428,
+ "blazer": 2429,
+ "nails": 2430,
+ "trample": 2431,
+ "Brogues": 2432,
+ "track": 2433,
+ "slippers": 2434,
+ "clasp": 2435,
+ "buckle": 2436,
+ "harness": 2437,
+ "lock": 2438,
+ "fringe": 2439,
+ "winding": 2440,
+ "chain": 2441,
+ "strap": 2442,
+ "lash": 2443,
+ "cord": 2444,
+ "knot": 2445,
+ "circle": 2446,
+ "loop": 2447,
+ "eye": 2448,
+ "turn": 2449,
+ "spiral": 2450,
+ "coil": 2451,
+ "gyre": 2452,
+ "band": 2453,
+ "whorl": 2454,
+ "talisman": 2455,
+ "heart": 2456,
+ "noose": 2457,
+ "necklace": 2458,
+ "collar": 2459,
+ "beads": 2460,
+ "torc": 2461,
+ "gorget": 2462,
+ "scarab": 2463,
+ "wood": 2464,
+ "brand": 2465,
+ "bludgeon": 2466,
+ "cudgel": 2467,
+ "loom": 2468,
+ "harp": 2469,
+ "master": 2470,
+ "barRI": 2471,
+ "hew": 2472,
+ "crook": 2473,
+ "mar": 2474,
+ "shell": 2475,
+ "stake": 2476,
+ "picket": 2477,
+ "pale": 2478,
+ "flange": 2479,
+ "Civerb's Vestments": 2480,
+ "Hsarus' Trim": 2481,
+ "Cleglaw's Brace": 2482,
+ "Iratha's Finery": 2483,
+ "Isenhart's Armory": 2484,
+ "Vidala's Rig": 2485,
+ "Milabrega's Regalia": 2486,
+ "Cathan's Traps": 2487,
+ "Tancred's Battlegear": 2488,
+ "Sigon's Complete Steel": 2489,
+ "Infernal Tools": 2490,
+ "Berserker's Garb": 2491,
+ "Death's Disguise": 2492,
+ "Angelical Raiment": 2493,
+ "Arctic Gear": 2494,
+ "Arcanna's Tricks": 2495,
+ "Civerb's": 2496,
+ "Hsarus'\tHsaru's": 2497,
+ "Cleglaw's": 2498,
+ "Iratha's": 2499,
+ "Isenhart's": 2500,
+ "Vidala's": 2501,
+ "Milabrega's": 2502,
+ "Cathan's": 2503,
+ "Tancred's": 2504,
+ "Sigon's": 2505,
+ "Infernal": 2506,
+ "Death's": 2508,
+ "Angelical": 2509,
+ "Arctic": 2510,
+ "Arcanna's": 2511,
+ "Ward": 2512,
+ "Iron Heel": 2513,
+ "Tooth": 2514,
+ "Collar": 2515,
+ "Lightbrand": 2516,
+ "Barb": 2517,
+ "Orb": 2518,
+ "Rule": 2519,
+ "Crowbill": 2520,
+ "Visor": 2521,
+ "Cranium": 2522,
+ "Headgear": 2523,
+ "Hand": 2524,
+ "Sickle": 2525,
+ "Horn": 2526,
+ "Sign": 2527,
+ "Icon": 2528,
+ "Iron Fist": 2529,
+ "Claw": 2530,
+ "Cuff": 2531,
+ "Parry": 2532,
+ "Fetlock": 2533,
+ "Rod": 2534,
+ "Mesh": 2535,
+ "Spine": 2536,
+ "Shelter": 2537,
+ "Torch": 2538,
+ "Hauberk": 2539,
+ "Guard": 2540,
+ "Mantle": 2541,
+ "Furs": 2542,
+ "Deathwand": 2543,
+ "CudgelSI3S": 2544,
+ "Iron Stay": 2545,
+ "Pincers": 2546,
+ "Coil": 2547,
+ "Case": 2548,
+ "Ambush": 2549,
+ "Diadem": 2550,
+ "Visage": 2551,
+ "Hobnails": 2552,
+ "Gage": 2553,
+ "SignSI3S": 2554,
+ "Hatchet": 2555,
+ "Touch": 2556,
+ "Halo": 2557,
+ "Binding": 2558,
+ "Head": 2559,
+ "Horns": 2560,
+ "Snare": 2561,
+ "Robe": 2562,
+ "Sigil": 2563,
+ "Weird": 2564,
+ "Sabot": 2565,
+ "Wings": 2566,
+ "Mitts": 2567,
+ "Flesh": 2568,
+ "Cord": 2569,
+ "Seal": 2570,
+ "SkullSI5S": 2571,
+ "Wrap": 2572,
+ "GuardSI6S": 2573,
+ "The Gnasher": 2574,
+ "Deathspade": 2575,
+ "Bladebone": 2576,
+ "Mindrend": 2577,
+ "Rakescar": 2578,
+ "Fechmars Axe": 2579,
+ "Goreshovel": 2580,
+ "The Chieftan": 2581,
+ "Brainhew": 2582,
+ "The Humongous": 2583,
+ "Iros Torch": 2584,
+ "Maelstromwrath": 2585,
+ "Gravenspine": 2586,
+ "Umes Lament": 2587,
+ "Felloak": 2588,
+ "Knell Striker": 2589,
+ "Rusthandle": 2590,
+ "Stormeye": 2591,
+ "Stoutnail": 2592,
+ "Crushflange": 2593,
+ "Bloodrise": 2594,
+ "The Generals Tan Do Li Ga": 2595,
+ "Ironstone": 2596,
+ "Bonesob": 2597,
+ "Steeldriver": 2598,
+ "Rixots Keen": 2599,
+ "Blood Crescent": 2600,
+ "Krintizs Skewer": 2601,
+ "Gleamscythe": 2602,
+ "Azurewrath": 2603,
+ "Griswolds Edge": 2604,
+ "Hellplague": 2605,
+ "Culwens Point": 2606,
+ "Shadowfang": 2607,
+ "Soulflay": 2608,
+ "Kinemils Awl": 2609,
+ "Blacktongue": 2610,
+ "Ripsaw": 2611,
+ "The Patriarch": 2612,
+ "Gull": 2613,
+ "The Diggler": 2614,
+ "The Jade Tan Do": 2615,
+ "Irices Shard": 2616,
+ "The Dragon Chang": 2617,
+ "Razortine": 2618,
+ "Bloodthief": 2619,
+ "Lance of Yaggai": 2620,
+ "The Tannr Gorerod": 2621,
+ "Dimoaks Hew": 2622,
+ "Steelgoad": 2623,
+ "Soul Harvest": 2624,
+ "The Battlebranch": 2625,
+ "Woestave": 2626,
+ "The Grim Reaper": 2627,
+ "Bane Ash": 2628,
+ "Serpent Lord": 2629,
+ "Lazarus Spire": 2630,
+ "The Salamander": 2631,
+ "The Iron Jang Bong": 2632,
+ "Pluckeye": 2633,
+ "Witherstring": 2634,
+ "Rimeraven": 2635,
+ "Piercerib": 2636,
+ "Pullspite": 2637,
+ "Wizendraw": 2638,
+ "Hellclap": 2639,
+ "Blastbark": 2640,
+ "Leadcrow": 2641,
+ "Ichorsting": 2642,
+ "Hellcast": 2643,
+ "Doomspittle": 2644,
+ "War Bonnet": 2645,
+ "Tarnhelm": 2646,
+ "Coif of Glory": 2647,
+ "Duskdeep": 2648,
+ "Wormskull": 2649,
+ "Howltusk": 2650,
+ "Undead Crown": 2651,
+ "The Face of Horror": 2652,
+ "Greyform": 2653,
+ "Blinkbats Form": 2654,
+ "The Centurion": 2655,
+ "Twitchthroe": 2656,
+ "Darkglow": 2657,
+ "Hawkmail": 2658,
+ "Sparking Mail": 2659,
+ "Venomsward": 2660,
+ "Iceblink": 2661,
+ "Boneflesh": 2662,
+ "Rockfleece": 2663,
+ "Rattlecage": 2664,
+ "Goldskin": 2665,
+ "Victors Silk": 2666,
+ "Heavenly Garb": 2667,
+ "Pelta Lunata": 2668,
+ "Umbral Disk": 2669,
+ "Stormguild": 2670,
+ "Wall of the Eyeless": 2671,
+ "Swordback Hold": 2672,
+ "Steelclash": 2673,
+ "Bverrit Keep": 2674,
+ "The Ward": 2675,
+ "The Hand of Broc": 2676,
+ "Bloodfist": 2677,
+ "Chance Guards": 2678,
+ "Magefist": 2679,
+ "Frostburn": 2680,
+ "Hotspur": 2681,
+ "Gorefoot": 2682,
+ "Treads of Cthon": 2683,
+ "Goblin Toe": 2684,
+ "Tearhaunch": 2685,
+ "Lenyms Cord": 2686,
+ "Snakecord": 2687,
+ "Nightsmoke": 2688,
+ "Goldwrap": 2689,
+ "Bladebuckle": 2690,
+ "Nokozan Relic": 2691,
+ "The Eye of Etlich": 2692,
+ "The Mahim-Oak Curio": 2693,
+ "Nagelring": 2694,
+ "Manald Heal": 2695,
+ "Gorgethroat": 2696,
+ "Amulet of the Viper": 2697,
+ "Staff of Kings": 2698,
+ "Horadric Staff": 2699,
+ "Hell Forge Hammer": 2700,
+ "The Stone of Jordan": 2701,
+ "GloomUM": 2702,
+ "Gray": 2703,
+ "DireUM": 2704,
+ "Black": 2705,
+ "ShadowUM": 2706,
+ "Haze": 2707,
+ "Wind": 2708,
+ "StormUM": 2709,
+ "Warp": 2710,
+ "Night": 2711,
+ "Moon": 2712,
+ "Star": 2713,
+ "Pit": 2714,
+ "Fire": 2715,
+ "Cold": 2716,
+ "Seethe": 2717,
+ "SharpUM": 2718,
+ "AshUM": 2719,
+ "Blade": 2720,
+ "SteelUM": 2721,
+ "StoneUM": 2722,
+ "Rust": 2723,
+ "Mold": 2724,
+ "Blight": 2725,
+ "Plague": 2726,
+ "Rot": 2727,
+ "Ooze": 2728,
+ "Puke": 2729,
+ "Snot": 2730,
+ "Bile": 2731,
+ "BloodUM": 2732,
+ "Pulse": 2733,
+ "Gut": 2734,
+ "Gore": 2735,
+ "FleshUM": 2736,
+ "BoneUM": 2737,
+ "SpineUM": 2738,
+ "Mind": 2739,
+ "SpiritUM": 2740,
+ "SoulUM": 2741,
+ "Wrath": 2742,
+ "GriefUM": 2743,
+ "Foul": 2744,
+ "Vile": 2745,
+ "Sin": 2746,
+ "ChaosUM": 2747,
+ "DreadUM": 2748,
+ "DoomUM": 2749,
+ "BaneUM": 2750,
+ "DeathUM": 2751,
+ "ViperUM": 2752,
+ "Dragon": 2753,
+ "Devil": 2754,
+ "touchUM": 2755,
+ "spellUM": 2756,
+ "feast": 2757,
+ "wound": 2758,
+ "grin": 2759,
+ "maim": 2760,
+ "hack": 2761,
+ "biteUM": 2762,
+ "rendUM": 2763,
+ "burn": 2764,
+ "rip": 2765,
+ "kill": 2766,
+ "callUM": 2767,
+ "vex": 2768,
+ "jade": 2769,
+ "web": 2770,
+ "shieldUM": 2771,
+ "KillerUM": 2772,
+ "RazorUM": 2773,
+ "drinker": 2774,
+ "shifter": 2775,
+ "crawler": 2776,
+ "dancer": 2777,
+ "bender": 2778,
+ "weaverUM": 2779,
+ "eater": 2780,
+ "widow": 2781,
+ "maggot": 2782,
+ "spawn": 2783,
+ "wight": 2784,
+ "GrumbleUM": 2785,
+ "GrowlerUM": 2786,
+ "SnarlUM": 2787,
+ "wolf": 2788,
+ "crow": 2789,
+ "raven": 2790,
+ "hawk": 2791,
+ "cloud": 2792,
+ "BangUM": 2793,
+ "head": 2794,
+ "skullUM": 2795,
+ "browUM": 2796,
+ "eyeUM": 2797,
+ "maw": 2798,
+ "tongue": 2799,
+ "fangUM": 2800,
+ "hornUM": 2801,
+ "thorn": 2802,
+ "clawUM": 2803,
+ "fistUM": 2804,
+ "heartUM": 2805,
+ "shankUM": 2806,
+ "skinUM": 2807,
+ "wingUM": 2808,
+ "pox": 2809,
+ "fester": 2810,
+ "blister": 3291,
+ "pus": 2812,
+ "SlimeUM": 2813,
+ "drool": 2814,
+ "froth": 2815,
+ "sludge": 2816,
+ "venom": 2817,
+ "poison": 2818,
+ "break": 2819,
+ "shard": 2820,
+ "flame": 2821,
+ "maul": 2822,
+ "thirstUM": 2823,
+ "lust": 2824,
+ "the Hammer": 2825,
+ "the Axe": 2826,
+ "the Sharp": 2827,
+ "the Jagged": 2828,
+ "the Flayer": 2829,
+ "the Slasher": 2830,
+ "the Impaler": 2831,
+ "the Hunter": 2832,
+ "the Slayer": 2833,
+ "the Mauler": 2834,
+ "the Destroyer": 2835,
+ "theQuick": 2836,
+ "the Witch": 2837,
+ "the Mad": 2838,
+ "the Wraith": 2839,
+ "the Shade": 2840,
+ "the Dead": 2841,
+ "the Unholy": 2842,
+ "the Howler": 2843,
+ "the Grim": 2844,
+ "the Dark": 2845,
+ "the Tainted": 2846,
+ "the Unclean": 2847,
+ "the Hungry": 2848,
+ "the Cold": 2849,
+ "The Cow King": 2850,
+ "Grand Vizier of Chaos": 2851,
+ "Lord De Seis": 2852,
+ "Infector of Souls": 2853,
+ "Riftwraith the Cannibal": 2854,
+ "Taintbreeder": 2855,
+ "The Tormentor": 2856,
+ "Winged Death": 2857,
+ "Maffer Dragonhand": 2858,
+ "Wyand Voidfinger": 2859,
+ "Toorc Icefist": 2860,
+ "Bremm Sparkfist": 2861,
+ "Geleb Flamefinger": 2862,
+ "Ismail Vilehand": 2863,
+ "Icehawk Riftwing": 2864,
+ "Sarina the Battlemaid": 2865,
+ "Stormtree": 2866,
+ "Witch Doctor Endugu": 2867,
+ "Web Mage the Burning": 2868,
+ "Bishibosh": 2869,
+ "Bonebreak": 2870,
+ "Coldcrow": 2871,
+ "Rakanishu": 2872,
+ "Treehead WoodFist": 2873,
+ "Griswold": 2874,
+ "The Countess": 2875,
+ "Pitspawn Fouldog": 2876,
+ "Flamespike the Crawler": 2877,
+ "Boneash": 2878,
+ "Radament": 2879,
+ "Bloodwitch the Wild": 2880,
+ "Fangskin": 2881,
+ "Beetleburst": 2882,
+ "Leatherarm": 2883,
+ "Coldworm the Burrower": 2884,
+ "Fire Eye": 2885,
+ "Dark Elder": 2886,
+ "The Summoner": 2887,
+ "Ancient Kaa the Soulless": 2888,
+ "The Smith": 2889,
+ "DeckardCain": 2890,
+ "Gheed": 2891,
+ "Akara": 2892,
+ "Kashya": 2893,
+ "Charsi": 2894,
+ "Wariv": 2895,
+ "Warriv": 2896,
+ "Rogue": 2897,
+ "StygianDoll": 2898,
+ "SoulKiller": 2899,
+ "Flayer": 2900,
+ "Fetish": 2901,
+ "RatMan": 2902,
+ "Undead StygianDoll": 2903,
+ "Undead SoulKiller": 2904,
+ "Undead Flayer": 2905,
+ "Undead Fetish": 2906,
+ "Undead RatMan": 2907,
+ "DarkFamiliar": 2908,
+ "BloodDiver": 2909,
+ "Gloombat": 2910,
+ "DesertWing": 2911,
+ "Banished": 2912,
+ "BloodLord": 2913,
+ "DarkLord": 2914,
+ "NightLord": 2915,
+ "GhoulLord": 2916,
+ "Spikefist": 2917,
+ "Thrasher": 2918,
+ "BrambleHulk": 2919,
+ "ThornedHulk": 2920,
+ "SpiderMagus": 2921,
+ "FlameSpider": 2922,
+ "PoisonSpinner": 2923,
+ "SandFisher": 2924,
+ "Arach": 2925,
+ "BloodWing": 2926,
+ "BloodHook": 2927,
+ "Feeder": 2928,
+ "Sucker": 2929,
+ "WingedNightmare": 2930,
+ "HellBuzzard": 2931,
+ "UndeadScavenger": 2932,
+ "CarrionBird": 2933,
+ "Unraveler": 2934,
+ "Guardian": 2935,
+ "HollowOne": 2936,
+ "Horadrim Ancient": 2937,
+ "AlbinoRoach": 2938,
+ "SteelWeevil": 2939,
+ "Scarab": 2940,
+ "SandWarrior": 2941,
+ "DungSoldier": 2942,
+ "HellSwarm": 2943,
+ "PlagueBugs": 2944,
+ "BlackLocusts": 2945,
+ "Itchies": 2946,
+ "HellCat": 2947,
+ "NightTiger": 2948,
+ "SaberCat": 2949,
+ "Huntress": 2950,
+ "RazorPitDemon": 2951,
+ "TreeLurker": 2952,
+ "CaveLeaper": 2953,
+ "TombCreeper": 2954,
+ "SandLeaper": 2955,
+ "TombViper": 2956,
+ "PitViper": 2957,
+ "Salamander": 2958,
+ "ClawViper": 2959,
+ "SerpentMagus": 2960,
+ "WorldKiller": 2961,
+ "GiantLamprey": 2962,
+ "Devourer": 2963,
+ "RockWorm": 2964,
+ "SandMaggot": 2965,
+ "JungleUrchin": 2966,
+ "RazorSpine": 2967,
+ "ThornBeast": 2968,
+ "SpikeFiend": 2969,
+ "QuillRat": 2970,
+ "HellClan": 2971,
+ "MoonClan": 2972,
+ "NightClan": 2973,
+ "DeathClan": 2974,
+ "BloodClan": 2975,
+ "TempleGuard": 2976,
+ "DoomApe": 2977,
+ "JungleHunter": 2978,
+ "RockDweller": 2979,
+ "DuneBeast": 2980,
+ "FleshHunter": 2981,
+ "BlackRogue": 2982,
+ "DarkStalker": 2983,
+ "VileHunter": 2984,
+ "DarkHunter": 2985,
+ "DarkShape": 2986,
+ "Apparition": 2987,
+ "Specter": 2988,
+ "Ghost": 2990,
+ "Assailant": 2991,
+ "Infidel": 2992,
+ "Invader": 2993,
+ "Marauder": 2994,
+ "SandRaider": 2995,
+ "GargantuanBeast": 2996,
+ "WailingBeast": 2997,
+ "Yeti": 2998,
+ "Crusher": 2999,
+ "Brute": 3000,
+ "CloudStalker": 3001,
+ "BlackVulture": 3002,
+ "BlackRaptor": 3003,
+ "BloodHawk": 3004,
+ "FoulCrow": 3005,
+ "PlagueBearer": 3006,
+ "Ghoul": 3007,
+ "DrownedCarcass": 3008,
+ "HungryDead": 3009,
+ "Zombie": 3010,
+ "Skeleton": 3011,
+ "Horror": 3012,
+ "Returned": 3013,
+ "BurningDead": 3014,
+ "BoneWarrior": 3015,
+ "Damned": 3016,
+ "Disfigured": 3017,
+ "Misshapen": 3018,
+ "Tainted": 3019,
+ "Afflicted": 3020,
+ "Andariel": 3021,
+ "Natalya": 3022,
+ "Drognan": 3023,
+ "Atma": 3024,
+ "Fara": 3025,
+ "Lysander": 3026,
+ "Jerhyn": 3027,
+ "jerhyn": 3028,
+ "Geglash": 3029,
+ "Elzix": 3030,
+ "Greiz": 3031,
+ "Meshif": 3032,
+ "Camel": 3033,
+ "Cadaver": 3034,
+ "PreservedDead": 3035,
+ "Embalmed": 3036,
+ "DriedCorpse": 3037,
+ "Decayed": 3038,
+ "Urdar": 3039,
+ "Mauler": 3040,
+ "Gorbelly": 3041,
+ "Blunderbore": 3042,
+ "WorldKillerYoung": 3043,
+ "GiantLampreyYoung": 3044,
+ "DevourerYoung": 3045,
+ "RockWormYoung": 3046,
+ "SandMaggotYoung": 3047,
+ "WorldKillerEgg": 3048,
+ "GiantLampreyEgg": 3049,
+ "DevourerEgg": 3050,
+ "RockWormEgg": 3051,
+ "SandMaggotEgg": 3052,
+ "Maggot": 3053,
+ "Duriel": 3054,
+ "BloodHawkNest": 3055,
+ "FlyingScimitar": 3056,
+ "CloudStalkerNest": 3057,
+ "BlackVultureNest": 3058,
+ "FoulCrowNest": 3059,
+ "Diablo": 3060,
+ "Baal": 3061,
+ "Mephisto": 3062,
+ "Cantor": 3063,
+ "Heirophant": 3064,
+ "Sexton": 3065,
+ "Zealot": 3066,
+ "Faithful": 3067,
+ "Zakarumite": 3068,
+ "BlackSoul": 3069,
+ "BurningSoul": 3070,
+ "SwampGhost": 3071,
+ "Gloam": 3072,
+ "WarpedShaman": 3073,
+ "DarkShaman": 3074,
+ "DevilkinShaman": 3075,
+ "CarverShaman": 3076,
+ "FallenShaman": 3077,
+ "WarpedFallen": 3078,
+ "DarkOne": 3079,
+ "Devilkin": 3080,
+ "Carver": 3081,
+ "Fallen": 3082,
+ "ReturnedArcher": 3083,
+ "HorrorArcher": 3084,
+ "BurningDeadArcher": 3085,
+ "BoneArcher": 3086,
+ "CorpseArcher": 3087,
+ "SkeletonArcher": 3088,
+ "FleshLancer": 3089,
+ "BlackLancer": 3090,
+ "DarkLancer": 3091,
+ "VileLancer": 3092,
+ "DarkSpearwoman": 3093,
+ "FleshArcher": 3094,
+ "BlackArcher": 3095,
+ "DarkRanger": 3096,
+ "VileArcher": 3097,
+ "DarkArcher": 3098,
+ "Summoner": 3099,
+ "StygianDollShaman": 3100,
+ "SoulKillerShaman": 3101,
+ "FlayerShaman": 3102,
+ "FetishShaman": 3103,
+ "RatManShaman": 3104,
+ "HorrorMage": 3105,
+ "BurningDeadMage": 3106,
+ "BoneMage": 3107,
+ "CorpseMage": 3108,
+ "ReturnedMage": 3109,
+ "GargoyleTrap": 3110,
+ "Bloodraven": 3111,
+ "navi": 3112,
+ "Kaelan": 3113,
+ "meshif": 3114,
+ "StygianWatcherHead": 3115,
+ "RiverStalkerHead": 3116,
+ "WaterWatcherHead": 3117,
+ "StygianWatcherLimb": 3118,
+ "RiverStalkerLimb": 3119,
+ "WaterWatcherLimb": 3120,
+ "NightMarauder": 3121,
+ "FireGolem": 3122,
+ "IronGolem": 3123,
+ "BloodGolem": 3124,
+ "ClayGolem": 3125,
+ "WorldKillerQueen": 3126,
+ "GiantLampreyQueen": 3127,
+ "DevourerQueen": 3128,
+ "RockWormQueen": 3129,
+ "SandMaggotQueen": 3130,
+ "Slime Prince": 3131,
+ "Bog Creature": 3132,
+ "Swamp Dweller": 3133,
+ "GiantUrchin": 3134,
+ "RazorBeast": 3135,
+ "ThornBrute": 3136,
+ "SpikeGiant": 3137,
+ "QuillBear": 3138,
+ "Council Member": 3139,
+ "youngdiablo": 3140,
+ "darkwanderer": 3141,
+ "HellSlinger": 3142,
+ "NightSlinger": 3143,
+ "SpearCat": 3144,
+ "Slinger": 3145,
+ "FireTower": 3146,
+ "LightningSpire": 3147,
+ "PitLord": 3148,
+ "Balrog": 3149,
+ "VenomLord": 3150,
+ "Iron Wolf": 3151,
+ "InvisoSpawner": 3152,
+ "OblivionKnight": 3153,
+ "Mage": 3154,
+ "AbyssKnight": 3155,
+ "Fighter Mage": 3156,
+ "DoomKnight": 3157,
+ "Fighter": 3158,
+ "MawFiend": 3159,
+ "CorpseSpitter": 3160,
+ "Corpulent": 3161,
+ "StormCaster": 3162,
+ "Strangler": 3163,
+ "Groper": 3164,
+ "GrotesqueWyrm": 3165,
+ "StygianDog": 3166,
+ "FleshBeast": 3167,
+ "Grotesque": 3168,
+ "StygianHag": 3169,
+ "FleshSpawner": 3170,
+ "RogueScout": 3171,
+ "BloodWingNest": 3172,
+ "BloodHookNest": 3173,
+ "FeederNest": 3174,
+ "SuckerNest": 3175,
+ "NecroMage": 3176,
+ "NecroSkeleton": 3177,
+ "TrappedSoul": 3178,
+ "Valkyrie": 3179,
+ "Dopplezon": 3180,
+ "Raises Fetishes": 3181,
+ "Raises Undead": 3182,
+ "Lays Eggs": 3183,
+ "Raises Fallen": 3184,
+ "heals Zealots and Cantors": 3185,
+ "drains mana and stamina": 3186,
+ "drains mana": 3187,
+ "drains stamina": 3188,
+ "stun attack": 3189,
+ "eats and spits corspes": 3190,
+ "homing missiles": 3191,
+ "raises Stygian Dolls": 3192,
+ "raises Soul Killers": 3193,
+ "raises Flayers": 3194,
+ "raises Fetishes": 3195,
+ "raises Ratmen": 3196,
+ "steals life": 3197,
+ "raises undead": 3198,
+ "raises Dark Ones": 3199,
+ "raises Devilkin": 3200,
+ "raises Carvers": 3201,
+ "raises Fallen": 3202,
+ "raises Warped Fallen": 3203,
+ "shocking hit": 3204,
+ "uniquextrastrong": 3205,
+ "uniqueextrafast": 3206,
+ "uniquecursed": 3207,
+ "uniquemagicresistance": 3208,
+ "uniquefireenchanted": 3209,
+ "monsteruniqueprop1": 3210,
+ "monsteruniqueprop2": 3211,
+ "monsteruniqueprop3": 3212,
+ "monsteruniqueprop4": 3213,
+ "monsteruniqueprop5": 3214,
+ "monsteruniqueprop6": 3215,
+ "monsteruniqueprop7": 3216,
+ "monsteruniqueprop8": 3217,
+ "monsteruniqueprop9": 3218,
+ "This Cow Bites": 3219,
+ "Champion": 3220,
+ "minion": 3221,
+ "Barrel": 3222,
+ "Lever1": 3223,
+ "BarrelEx": 3224,
+ "Door": 3225,
+ "Portal": 3226,
+ "ODoor": 3227,
+ "BlockedDoor": 3228,
+ "LockedDoor": 3229,
+ "StoneAlpha": 3230,
+ "StoneBeta": 3231,
+ "StoneDelta": 3232,
+ "StoneGamma": 3233,
+ "StoneLambda": 3234,
+ "StoneTheta": 3235,
+ "Crate": 3236,
+ "Casket": 3237,
+ "Cabinet": 3238,
+ "Vase": 3239,
+ "Inifuss": 3240,
+ "corpse": 3241,
+ "RogueCorpse": 3242,
+ "CorpseOnStick": 3243,
+ "TowerTome": 3244,
+ "Gibbet": 3245,
+ "MummyGenerator": 3246,
+ "ArmorStand": 3247,
+ "WeaponRack": 3248,
+ "Sarcophagus": 3249,
+ "Trap Door": 3250,
+ "LargeUrn": 3251,
+ "CanopicJar": 3252,
+ "Obelisk": 3253,
+ "HoleAnim": 3254,
+ "Shrine": 3255,
+ "Urn": 3256,
+ "Waypoint": 22526,
+ "Well": 3258,
+ "bag": 3259,
+ "Chest": 3260,
+ "chest": 3261,
+ "lockedchest": 3262,
+ "HorazonsJournal": 3263,
+ "templeshrine": 3264,
+ "stair": 3265,
+ "coffin": 3266,
+ "bookshelf": 3267,
+ "loose boulder": 3268,
+ "loose rock": 3269,
+ "hollow log": 3270,
+ "hiding spot": 3271,
+ "fire": 3328,
+ "Chest3": 3273,
+ "hidden stash": 3274,
+ "GuardCorpse": 3275,
+ "bowl": 3276,
+ "jug": 3277,
+ "AmbientSound": 3278,
+ "ratnest": 3279,
+ "burning body": 3280,
+ "well": 22525,
+ "door": 3282,
+ "skeleton": 3283,
+ "skullpile": 3284,
+ "cocoon": 3285,
+ "gidbinn altar": 3286,
+ "cowa": 3287,
+ "manashrine": 3288,
+ "bed": 3289,
+ "ratchest": 3290,
+ "bank": 3292,
+ "goo pile": 3293,
+ "holyshrine": 3294,
+ "teleportation pad": 3295,
+ "ratchest-r": 3296,
+ "skull pile": 3297,
+ "body": 3298,
+ "hell bridge": 3299,
+ "compellingorb": 3300,
+ "basket": 3301,
+ "Basket": 3302,
+ "RockPIle": 3303,
+ "Tome": 3304,
+ "dead body": 3305,
+ "eunuch": 3306,
+ "dead guard": 3307,
+ "portal": 3308,
+ "sarcophagus": 3309,
+ "dead villager": 3310,
+ "sewer lever": 3311,
+ "sewer stairs": 3312,
+ "magic shrine": 3313,
+ "wirt's body": 3314,
+ "stash": 3315,
+ "guyq": 3316,
+ "taintedsunaltar": 3317,
+ "Hellforge": 3318,
+ "Corpsefire": 3319,
+ "fissure": 3320,
+ "BoneChest": 3321,
+ "casket": 3322,
+ "HungSkeleton": 3323,
+ "pillar": 3324,
+ "Hydra": 3325,
+ "Turret": 3326,
+ "a trap": 3327,
+ "cost": 3329,
+ "Repair": 3330,
+ "Sell": 3331,
+ "Identify": 3332,
+ "priceless": 3333,
+ "NPCMenuTradeRepair": 3334,
+ "NPCPurchaseItems": 3335,
+ "NPCSellItems": 3336,
+ "NPCHeal": 3337,
+ "NPCRepairItems": 3338,
+ "NPCNextPage": 3339,
+ "NPCPreviousPage": 3340,
+ "strUiMenu2": 4131,
+ "TransactionMenu1a": 3342,
+ "TransactionMenu1f": 3343,
+ "VerifyTransaction1": 3344,
+ "VerifyTransaction2": 3345,
+ "VerifyTransaction3": 3346,
+ "VerifyTransaction4": 3347,
+ "VerifyTransaction5": 3348,
+ "VerifyTransaction6": 3349,
+ "VerifyTransaction7": 3350,
+ "VerifyTransaction8": 3351,
+ "VerifyTransaction9": 3352,
+ "TransactionResults1": 3353,
+ "TransactionResults2": 3354,
+ "TransactionResults3": 3355,
+ "TransactionResults4": 3356,
+ "TransactionResults5": 3357,
+ "TransactionResults6": 3358,
+ "TransactionResults7": 3359,
+ "TransactionResults8": 3360,
+ "TransactionResults9": 3361,
+ "TransactionResults10": 3362,
+ "TransactionResults11": 3363,
+ "ItemDesc1s": 3364,
+ "ItemDesc1t": 3365,
+ "HP": 3366,
+ "AC": 3367,
+ "Level": 3368,
+ "Cost": 3369,
+ "Damage": 3370,
+ "strhirespecial1": 3371,
+ "strhirespecial2": 3372,
+ "strhirespecial3": 3373,
+ "strhirespecial4": 3374,
+ "strhirespecial5": 3375,
+ "strhirespecial6": 3376,
+ "strhirespecial7": 3377,
+ "strhirespecial8": 3378,
+ "strhirespecial9": 3379,
+ "strhirespecial10": 3380,
+ "TalkMenu": 3381,
+ "WarrivMenu1b": 3382,
+ "WarrivMenu1c": 3383,
+ "MeshifMenuEast": 3384,
+ "MeshifMenuWest": 3385,
+ "NPCMenuNews0": 3386,
+ "NPCMenuNews1": 3387,
+ "NPCMenuNews2": 3388,
+ "NPCMenuNews3": 3389,
+ "NPCMenuNews4": 3390,
+ "NPCMenuTalkMore": 3391,
+ "NPCTownMore0": 3392,
+ "NPCTownMore1": 3393,
+ "NPCMenuLeave": 3394,
+ "NPCGossipMenu": 3395,
+ "NPCMenuTrade": 3396,
+ "NPCMenuHire": 3397,
+ "gamble": 3398,
+ "Intro": 3399,
+ "Back": 3400,
+ "ok": 3401,
+ "cancel": 3402,
+ "Continue": 3403,
+ "strMenuMain15": 3404,
+ "strOptMusic": 3405,
+ "strOptSound": 3406,
+ "strOptGamma": 3407,
+ "strOptRender": 3408,
+ "strOptPrevious": 3409,
+ "cfgCtrl": 3410,
+ "merc01": 3411,
+ "merc02": 3412,
+ "merc03": 3413,
+ "merc04": 3414,
+ "merc05": 3415,
+ "merc06": 3416,
+ "merc07": 3417,
+ "merc08": 3418,
+ "merc09": 3419,
+ "merc10": 3420,
+ "merc11": 3421,
+ "merc12": 3422,
+ "merc13": 3423,
+ "merc14": 3424,
+ "merc15": 3425,
+ "merc16": 3426,
+ "merc17": 3427,
+ "merc18": 3428,
+ "merc19": 3429,
+ "merc20": 3430,
+ "merc21": 3431,
+ "merc22": 3432,
+ "merc23": 3433,
+ "merc24": 3434,
+ "merc25": 3435,
+ "merc26": 3436,
+ "merc27": 3437,
+ "merc28": 3438,
+ "merc29": 3439,
+ "merc30": 3440,
+ "merc31": 3441,
+ "merc32": 3442,
+ "merc33": 3443,
+ "merc34": 3444,
+ "merc35": 3445,
+ "merc36": 3446,
+ "merc37": 3447,
+ "merc38": 3448,
+ "merc39": 3449,
+ "merc40": 3450,
+ "merc41": 3451,
+ "merclevelup": 3452,
+ "Socketable": 3453,
+ "ItemStats1a": 3454,
+ "ItemStats1b": 3455,
+ "ItemStats1c": 3456,
+ "ItemStats1d": 3457,
+ "ItemStats1e": 3458,
+ "ItemStats1f": 3459,
+ "ItemStats1g": 3460,
+ "ItemStats1h": 3461,
+ "ItemStats1i": 3462,
+ "ItemStats1j": 3463,
+ "ItemStast1k": 3464,
+ "ItemStats1l": 3465,
+ "ItemStats1m": 3466,
+ "ItemStats1n": 3467,
+ "ItemStats1o": 3468,
+ "ItemStats1p": 3469,
+ "ItemStats1q": 3470,
+ "ItemStatsrejuv1": 3471,
+ "ItemStatsrejuv2": 3472,
+ "ModStr1a": 3473,
+ "ModStr1b": 3474,
+ "ModStr1c": 3475,
+ "ModStr1d": 3476,
+ "ModStr1e": 3477,
+ "ModStr1f": 3478,
+ "ModStr1g": 3479,
+ "ModStr1h": 3480,
+ "ModStr1i": 3481,
+ "ModStr1j": 3482,
+ "ModStr1k": 3483,
+ "ModStr1l": 3484,
+ "ModStr1m": 3485,
+ "ModStr1n": 3486,
+ "ModStr1o": 3487,
+ "ModStr1p": 3488,
+ "ModStr1q": 3489,
+ "ModStr1r": 3490,
+ "ModStr1s": 3491,
+ "ModStr1t": 3492,
+ "ModStr1u": 3493,
+ "ModStr1v": 3494,
+ "ModStr1w": 3495,
+ "ModStr1x": 3496,
+ "ModStr1y": 3497,
+ "ModStr1z": 3498,
+ "ModStr2a": 3499,
+ "ModStr2b": 3500,
+ "ModStr2c": 3501,
+ "ModStr2d": 3502,
+ "ModStr2e": 3503,
+ "ModStr2f": 3504,
+ "ModStr2g": 3505,
+ "ModStr2h": 3506,
+ "ModStr2i": 3507,
+ "ModStr2j": 3508,
+ "ModStr2k": 3509,
+ "ModStr2l": 3510,
+ "ModStr2m": 3511,
+ "ModStr2n": 3512,
+ "ModStr2o": 3513,
+ "ModStr2p": 3514,
+ "ModStr2q": 3515,
+ "ModStr2r": 3516,
+ "ModStr2s": 3517,
+ "ModStr2t": 3518,
+ "ModStr2u": 3519,
+ "Modstr2v": 3520,
+ "ModStr2w": 3521,
+ "ModStr2x": 3522,
+ "ModStr2y": 3523,
+ "ModStr2z": 3524,
+ "ModStr3a": 3525,
+ "ModStr3b": 3526,
+ "ModStr3c": 3527,
+ "ModStr3d": 3528,
+ "ModStr3e": 3529,
+ "ModStr3f": 3530,
+ "ModStr3g": 3531,
+ "ModStr3h": 3532,
+ "ModStr3i": 3533,
+ "ModStr3j": 3534,
+ "ModStr3k": 3535,
+ "ModStr3l": 3536,
+ "ModStr3m": 3537,
+ "ModStr3n": 3538,
+ "ModStr3o": 3539,
+ "ModStr3p": 3540,
+ "ModStr3q": 3541,
+ "ModStr3r": 3542,
+ "ModStr3u": 3543,
+ "ModStr3v": 3544,
+ "ModStr3w": 3545,
+ "ModStr3x": 3546,
+ "ModStr3y": 3547,
+ "ModStr3z": 3548,
+ "ModStr4a": 3549,
+ "ModStr4b": 3550,
+ "ModStr4c": 3551,
+ "ModStr4d": 3552,
+ "ModStr4e": 3553,
+ "ModStr4f": 3554,
+ "ModStr4g": 3555,
+ "ModStr4h": 3556,
+ "ModStr4i": 3557,
+ "ModStr4j": 3558,
+ "ModStr4k": 3559,
+ "ModStr4l": 3560,
+ "ModStr4m": 3561,
+ "ModStr4n": 3562,
+ "ModStr4o": 3563,
+ "ModStr4p": 3564,
+ "ModStr4q": 3565,
+ "ModStr4r": 3566,
+ "ModStr4s": 3567,
+ "ModStr4t": 3568,
+ "ModStr4u": 3569,
+ "ModStr4v": 3570,
+ "ModStr4w": 3571,
+ "ModStr4x": 3572,
+ "ModStr4y": 3573,
+ "ModStr4z": 3574,
+ "ModStr5a": 3575,
+ "ModStr5b": 3576,
+ "ModStr5c": 3577,
+ "ModStr5d": 3578,
+ "ModStr5e": 3579,
+ "ModStr5f": 3580,
+ "ModStr5g": 3581,
+ "ModStr5h": 3582,
+ "ModStr5i": 3583,
+ "ModStr5j": 3584,
+ "ModStr5k": 3585,
+ "ModStr5l": 3586,
+ "ModStr5m": 3587,
+ "ModStr5n": 3588,
+ "ModStr5o": 3589,
+ "ModStr5p": 3590,
+ "ModStr5q": 3591,
+ "ModStr5r": 3592,
+ "ModStr5s": 3593,
+ "ModStr5t": 3594,
+ "ModStr5u": 3595,
+ "ModStr5v": 3596,
+ "ModStr5w": 3597,
+ "ModStr5x": 3598,
+ "ModStr5y": 3599,
+ "ModStr5z": 3600,
+ "ModStr6a": 3601,
+ "ModStr6b": 3602,
+ "ModStr6c": 3603,
+ "ModStr6d": 3604,
+ "ModStr6e": 3605,
+ "ModStr6f": 3606,
+ "ModStr6g": 3607,
+ "ModStr6h": 3608,
+ "ModStr6i": 3609,
+ "strModAllResistances": 3610,
+ "strModAllSkillLevels": 3611,
+ "strModFireDamage": 3612,
+ "strModFireDamageRange": 3613,
+ "strModColdDamage": 3614,
+ "strModColdDamageRange": 3615,
+ "strModLightningDamage": 3616,
+ "strModLightningDamageRange": 3617,
+ "strModMagicDamage": 3618,
+ "strModMagicDamageRange": 3619,
+ "strModPoisonDamage": 3620,
+ "strModPoisonDamageRange": 3621,
+ "strModMinDamage": 3622,
+ "strModMinDamageRange": 3623,
+ "strModEnhancedDamage": 3624,
+ "improved damage": 3625,
+ "improved to hit": 3626,
+ "improved armor class": 3627,
+ "improved durability": 3628,
+ "Quick Strike": 3629,
+ "strGemPlace1": 3630,
+ "strGemPlace2": 3631,
+ "gemeffect1": 3632,
+ "gemeffect2": 3633,
+ "gemeffect3": 3634,
+ "gemeffect4": 3635,
+ "gemeffect5": 3636,
+ "gemeffect6": 3637,
+ "gemeffect7": 3638,
+ "sysmsg1": 3639,
+ "sysmsg2": 3640,
+ "sysmsg3": 3641,
+ "sysmsg4": 3642,
+ "sysmsg3a": 3643,
+ "sysmsg4a": 3644,
+ "sysmsg5": 3645,
+ "sysmsg6": 3646,
+ "sysmsg7": 3647,
+ "sysmsg8": 3648,
+ "sysmsg9": 3649,
+ "sysmsg10": 3650,
+ "sysmsg11": 3651,
+ "sysmsg12": 3652,
+ "sysmsgPlayer": 3653,
+ "chatmsg1": 3654,
+ "chatmsg2": 3655,
+ "chatmsg3": 3657,
+ "strwhisperworked": 3658,
+ "syswork": 3659,
+ "ShrId0": 3660,
+ "ShrId1": 3661,
+ "ShrId2": 3662,
+ "ShrId3": 3663,
+ "ShrId4": 3664,
+ "ShrId5": 3665,
+ "ShrId6": 3666,
+ "ShrId7": 3667,
+ "ShrId8": 3668,
+ "ShrId9": 3669,
+ "ShrId10": 3670,
+ "ShrId11": 3671,
+ "ShrId12": 3672,
+ "ShrId13": 3673,
+ "ShrId14": 3674,
+ "ShrId15": 3675,
+ "ShrId16": 3676,
+ "ShrId17": 3677,
+ "ShrId18": 3678,
+ "ShrId19": 3679,
+ "ShrId20": 3680,
+ "ShrId21": 3681,
+ "ShrId22": 3682,
+ "ShrMsg0": 3683,
+ "ShrMsg1": 3684,
+ "ShrMsg2": 3685,
+ "ShrMsg3": 3686,
+ "ShrMsg4": 3687,
+ "ShrMsg5": 3688,
+ "ShrMsg6": 3689,
+ "ShrMsg7": 3690,
+ "ShrMsg8": 3691,
+ "ShrMsg9": 3692,
+ "ShrMsg10": 3693,
+ "ShrMsg11": 3694,
+ "ShrMsg12": 3695,
+ "ShrMsg13": 3696,
+ "ShrMsg14": 3697,
+ "ShrMsg15": 3698,
+ "ShrMsg16": 3699,
+ "ShrMsg17": 3700,
+ "ShrMsg18": 3701,
+ "ShrMsg19": 3702,
+ "ShrMsg20": 3703,
+ "ShrMsg21": 3704,
+ "ShrMsg22": 3705,
+ "strqi1": 3706,
+ "strqi2": 3707,
+ "stsa1q3alert": 3708,
+ "stsa1q4alert": 3709,
+ "stsa3q1alert": 3710,
+ "qstsa1qt": 3711,
+ "qstsa1qt0": 3712,
+ "qstsa1q0": 3713,
+ "qstsa1q1": 3714,
+ "qstsa1q2": 3715,
+ "qstsa1q3": 3716,
+ "qstsa1q4": 3717,
+ "qstsa1q5": 3718,
+ "qstsa1q6": 3719,
+ "strplaylast": 3720,
+ "newquestlog": 3721,
+ "qsts": 3722,
+ "noactivequest": 3723,
+ "qstsxxx": 3724,
+ "qstsnull": 3725,
+ "qstsComplete": 3726,
+ "qstsother": 3727,
+ "qstsprevious": 3728,
+ "qstsThankYouComeAgain": 3729,
+ "qstsThankYouComeAgainMulti": 3730,
+ "qstsThankYouComeAgainSingle": 3731,
+ "Qstsyouarenot8": 3732,
+ "qstsa1q3x": 3733,
+ "qstsa1q4x": 3734,
+ "qstsa1q11": 3735,
+ "qstsa1q12": 3736,
+ "qstsa1q13": 3737,
+ "qstsa1q14": 3738,
+ "qstsa1q140": 3739,
+ "qstsa1q15": 3740,
+ "qstsa1q21": 3741,
+ "qstsa1q22": 3742,
+ "qstsa1q23": 3743,
+ "qstsa1q41": 3744,
+ "qstsa1q42": 3745,
+ "qstsa1q43": 3746,
+ "qstsa1q44": 3747,
+ "qstsa1q45": 3748,
+ "qstsa1q46": 3749,
+ "qstsa1q46b": 3750,
+ "qstsa1q51": 3751,
+ "qstsa1q51a": 3752,
+ "qstsa1q51b": 3753,
+ "qstsa1q52": 3754,
+ "qstsa1q31": 3755,
+ "qstsa1q32": 3756,
+ "qstsa1q32b": 3757,
+ "qstsa1q61": 3758,
+ "qstsa1q62": 3759,
+ "qstsa1q62b": 3760,
+ "qstsa1q63": 3761,
+ "KeyNone": 3762,
+ "KeyLButton": 3763,
+ "KeyRButton": 3764,
+ "KeyCancel": 3765,
+ "KeyMButton": 3766,
+ "Key4Button": 3767,
+ "Key5Button": 3768,
+ "KeyWheelUp": 3769,
+ "KeyWheelDown": 3770,
+ "KeyKana": 3771,
+ "KeyJunja": 3772,
+ "KeyFinal": 3773,
+ "KeyKanji": 3774,
+ "KeyEscape": 3775,
+ "KeyConvert": 3776,
+ "KeyNonConvert": 3777,
+ "KeyAccept": 3778,
+ "KeyModeChange": 3779,
+ "KeyLeft": 3780,
+ "KeyUp": 3781,
+ "KeyRight": 3782,
+ "KeyDown": 3783,
+ "KeySelect": 3784,
+ "KeyExecute": 3785,
+ "KeyLWin": 3786,
+ "KeyRWin": 3787,
+ "KeyApps": 3788,
+ "KeyNumLock": 3789,
+ "KeyBack": 3790,
+ "KeyTab": 3791,
+ "KeyClear": 3792,
+ "KeyReturn": 3793,
+ "KeyShift": 3794,
+ "KeyControl": 3795,
+ "KeyMenu": 3796,
+ "KeyPause": 3797,
+ "KeyCapital": 3798,
+ "KeySpace": 3799,
+ "KeyPrior": 3800,
+ "KeyNext": 3801,
+ "KeyEnd": 3802,
+ "KeyHome": 3803,
+ "KeyPrint": 3804,
+ "KeySnapshot": 3805,
+ "KeyInsert": 3806,
+ "KeyDelete": 3807,
+ "KeyHelp": 3808,
+ "KeyNumPad0": 3809,
+ "KeyNumPad1": 3810,
+ "KeyNumPad2": 3811,
+ "KeyNumPad3": 3812,
+ "KeyNumPad4": 3813,
+ "KeyNumPad5": 3814,
+ "KeyNumPad6": 3815,
+ "KeyNumPad7": 3816,
+ "KeyNumPad8": 3817,
+ "KeyNumPad9": 3818,
+ "KeyMultiply": 3819,
+ "KeyAdd": 3820,
+ "KeySeparator": 3821,
+ "KeySubtract": 3822,
+ "KeyDecimal": 3823,
+ "KeyDivide": 3824,
+ "KeyF1": 3825,
+ "KeyF2": 3826,
+ "KeyF3": 3827,
+ "KeyF4": 3828,
+ "KeyF5": 3829,
+ "KeyF6": 3830,
+ "KeyF7": 3831,
+ "KeyF8": 3832,
+ "KeyF9": 3833,
+ "KeyF10": 3834,
+ "KeyF11": 3835,
+ "KeyF12": 3836,
+ "KeyF13": 3837,
+ "KeyF14": 3838,
+ "KeyF15": 3839,
+ "KeyF16": 3840,
+ "KeyF17": 3841,
+ "KeyF18": 3842,
+ "KeyF19": 3843,
+ "KeyF20": 3844,
+ "KeyF21": 3845,
+ "KeyF22": 3846,
+ "KeyF23": 3847,
+ "KeyF24": 3848,
+ "KeyScroll": 3849,
+ "KeySemicolon": 3850,
+ "KeyEqual": 3851,
+ "KeyComma": 3852,
+ "KeyMinus": 3853,
+ "KeyPeriod": 3854,
+ "KeySlash": 3855,
+ "KeyTilde": 3856,
+ "KeyLBracket": 3857,
+ "KeyBackslash": 3858,
+ "KeyRBracket": 3859,
+ "KeyApostrophe": 3860,
+ "ShorthandKeyMButton": 3861,
+ "ShorthandKey4Button": 3862,
+ "ShorthandKey5Button": 3863,
+ "ShorthandKeyWheelUp": 3864,
+ "ShorthandKeyWheelDown": 3865,
+ "ShorthandKeyKana": 3866,
+ "ShorthandKeyJunja": 3867,
+ "ShorthandKeyFinal": 3868,
+ "ShorthandKeyKanji": 3869,
+ "ShorthandKeyEscape": 3870,
+ "ShorthandKeyConvert": 3871,
+ "ShorthandKeyNonConvert": 3872,
+ "ShorthandKeyAccept": 3873,
+ "ShorthandKeyModeChange": 3874,
+ "ShorthandKeyLeft": 3875,
+ "ShorthandKeyRight": 3876,
+ "ShorthandKeyDown": 3877,
+ "ShorthandKeySelect": 3878,
+ "ShorthandKeyExecute": 3879,
+ "ShorthandKeyLeftWindows": 3880,
+ "ShorthandKeyRightWindows": 3881,
+ "ShorthandKeyApps": 3882,
+ "ShorthandKeyNumLock": 3883,
+ "ShorthandKeyBackspace": 3884,
+ "ShorthandKeyClear": 3885,
+ "ShorthandKeyEnter": 3886,
+ "ShorthandKeyShift": 3887,
+ "ShorthandKeyControl": 3888,
+ "ShorthandKeyPause": 3889,
+ "ShorthandKeyCapsLock": 3890,
+ "ShorthandKeySpace": 3891,
+ "ShorthandKeyPageUp": 3892,
+ "ShorthandKeyPageDown": 3893,
+ "ShorthandKeyHome": 3894,
+ "ShorthandKeyPrintScreen": 3895,
+ "ShorthandKeyInsert": 3896,
+ "ShorthandKeyDelete": 3897,
+ "ShorthandKeyHelp": 3898,
+ "ShorthandKeyNumPad0": 3899,
+ "ShorthandKeyNumPad1": 3900,
+ "ShorthandKeyNumPad2": 3901,
+ "ShorthandKeyNumPad3": 3902,
+ "ShorthandKeyNumPad4": 3903,
+ "ShorthandKeyNumPad5": 3904,
+ "ShorthandKeyNumPad6": 3905,
+ "ShorthandKeyNumPad7": 3906,
+ "ShorthandKeyNumPad8": 3907,
+ "ShorthandKeyNumPad9": 3908,
+ "ShorthandKeyNumPad*\tnp*": 3909,
+ "ShorthandKeyNumPad+\tnp+": 3910,
+ "ShorthandKeyNumPad-\tnp-": 3911,
+ "ShorthandKeyNumPad.\tnp.": 3912,
+ "ShorthandKeyNumPad/\tnp/": 3913,
+ "ShorthandKeyScroll": 3914,
+ "KeyMacOption": 3915,
+ "KeyMacCommand": 3916,
+ "KeyMacNumPad=\tNum Pad =": 3917,
+ "ShorthandKeyMacOption": 3918,
+ "ShorthandKeyMacCommand": 3919,
+ "ShorthandKeyMacNumPad=\tNP=": 3920,
+ "CfgFunction": 3921,
+ "CfgPrimaryKey": 3922,
+ "CfgSecondaryKey": 3923,
+ "CfgCharacter": 3924,
+ "CfgInventory": 3925,
+ "CfgParty": 3926,
+ "CfgMessageLog": 3927,
+ "CfgQuestLog": 3928,
+ "CfgChat": 3929,
+ "CfgAutoMap": 3930,
+ "CfgAutoMapCenter": 3931,
+ "CfgMiniMap": 3932,
+ "CfgHelp": 3933,
+ "CfgSkillTree": 3934,
+ "CfgSkillPick": 3935,
+ "CfgSkill1": 3936,
+ "CfgSkill2": 3937,
+ "CfgSkill3": 3938,
+ "CfgSkill4": 3939,
+ "CfgSkill5": 3940,
+ "CfgSkill6": 3941,
+ "CfgSkill7": 3942,
+ "CfgSkill8": 3943,
+ "Cfgskillup": 3944,
+ "Cfgskilldown": 3945,
+ "CfgBeltShow": 3946,
+ "CfgBelt1": 3947,
+ "CfgBelt2": 3948,
+ "CfgBelt3": 3949,
+ "CfgBelt4": 3950,
+ "CfgBelt5": 3951,
+ "CfgBelt6": 3952,
+ "CfgBelt7": 3953,
+ "CfgBelt8": 3954,
+ "CfgBelt9": 3955,
+ "CfgBelt10": 3956,
+ "CfgBelt11": 3957,
+ "CfgBelt12": 3958,
+ "CfgSay0": 3959,
+ "CfgSay1": 3960,
+ "CfgSay2": 3961,
+ "CfgSay3": 3962,
+ "CfgSay4": 3963,
+ "CfgSay5": 3964,
+ "CfgSay6": 3965,
+ "CfgRun": 3966,
+ "CfgRunLock": 3967,
+ "CfgStandStill": 3968,
+ "CfgShowItems": 3969,
+ "CfgClearScreen": 3970,
+ "CfgSnapshot": 3971,
+ "CfgDefault": 3972,
+ "CfgAccept": 3973,
+ "CfgCancel": 3974,
+ "strNoKeysAssigned": 3975,
+ "KeysAssigned": 3976,
+ "CantAssignMB": 3977,
+ "CantAssignMW": 3978,
+ "CantAssignKey": 3979,
+ "CfgClearKey": 3980,
+ "Cfgcleartextmsg": 3981,
+ "CfgTogglePortraits": 3982,
+ "CfgAutoMapFade": 3983,
+ "CfgAutoMapNames": 3984,
+ "CfgAutoMapParty": 3985,
+ "strlvlup": 3986,
+ "strnewskl": 3987,
+ "warpsheader": 3988,
+ "nowarps": 3989,
+ "waypointsheader": 3990,
+ "nowaypoints": 3991,
+ "max": 3992,
+ "MAX": 3993,
+ "colorcode": 3994,
+ "space": 3995,
+ "dash": 3996,
+ "colon": 3997,
+ "newline": 3998,
+ "pipe": 3999,
+ "slash": 4000,
+ "percent": 4001,
+ "plus": 4002,
+ "to": 4003,
+ "srostertitle": 4004,
+ "dwell": 4005,
+ "larva": 4006,
+ "Barbarian": 4007,
+ "Paladin": 4008,
+ "Necromancer": 4009,
+ "Sorceress": 4010,
+ "Amazon": 4011,
+ "druidstr \tDruid": 4012,
+ "assassinstr": 4013,
+ "Nest": 4014,
+ "NoParty": 4015,
+ "ItsMyParty": 4016,
+ "Upgrade": 4017,
+ "upgraderestrict": 4018,
+ "Use": 4019,
+ "NPCIdentify1": 4020,
+ "NPCIdentify2": 4021,
+ "strCannotDoThisToUnknown": 4022,
+ "Body Looted": 4023,
+ "Party1": 4024,
+ "Party2": 4025,
+ "Party3": 4026,
+ "Party4": 4027,
+ "Party5": 4028,
+ "Party6": 4029,
+ "Party7": 4030,
+ "Party8": 4031,
+ "Party9": 4032,
+ "strDropGoldHowMuch": 4033,
+ "strDropGoldInfo": 4034,
+ "strMsgLog": 4035,
+ "strBSArmor": 4036,
+ "strBSWeapons": 4037,
+ "strBSMagic": 4038,
+ "strBSMisc": 4039,
+ "strTrade": 4040,
+ "strTradeAccept": 4041,
+ "strTradeAgreeTo": 4042,
+ "strWaitingForOtherPlayer": 4043,
+ "strTradeBusy": 4044,
+ "strTradeTooFull": 4045,
+ "strTradeGoldHowMuch": 4046,
+ "strTradeTimeout": 4047,
+ "SysmsgPlayer1": 4048,
+ "strBankGoldDeposit": 4049,
+ "strBankGoldWithdraw": 4050,
+ "GoldMax": 4051,
+ "StrUI0": 4052,
+ "StrUI1": 4053,
+ "StrUI2": 4054,
+ "StrUI3": 4055,
+ "StrUI4": 4056,
+ "strchrlvl": 4057,
+ "strchrexp": 4058,
+ "strchrnxtlvl": 4059,
+ "strchrstr": 4060,
+ "strchrskm": 4061,
+ "strchrdex": 4062,
+ "strchratr": 4063,
+ "strchrdef": 4064,
+ "strchrrat": 4065,
+ "strchrvit": 4066,
+ "strchrstm": 4067,
+ "strchrlif": 4068,
+ "strchreng": 4069,
+ "strchrman": 4070,
+ "strchrfir": 4071,
+ "strchrcol": 4072,
+ "strchrlit": 4073,
+ "strchrpos": 4074,
+ "strchrstat": 4075,
+ "strchrrema": 4076,
+ "WeaponDescMace": 4077,
+ "WeaponDescAxe": 4078,
+ "WeaponDescSword": 4079,
+ "WeaponDescDagger": 4080,
+ "WeaponDescThrownPotion": 4081,
+ "WeaponDescJavelin": 4082,
+ "WeaponDescSpear": 4083,
+ "WeaponDescBow": 4084,
+ "WeaponDescStaff": 4085,
+ "WeaponDescPoleArm": 4086,
+ "WeaponDescCrossBow": 4087,
+ "WeaponAttackFastest": 4088,
+ "WeaponAttackVeryFast": 4089,
+ "WeaponAttackFast": 4090,
+ "WeaponAttackNormal": 4091,
+ "WeaponAttackSlow": 4092,
+ "WeaponAttackVerySlow": 4093,
+ "WeaponAttackSlowest": 4094,
+ "strNecromanerOnly": 4095,
+ "strPaladinOnly": 4096,
+ "strSorceressOnly": 4097,
+ "strMaceSpecialDamage": 4098,
+ "strGoldLabel": 4099,
+ "strParty1": 4100,
+ "strParty2": 4101,
+ "strParty3": 4102,
+ "strParty4": 4103,
+ "strParty5": 4104,
+ "strParty6": 4105,
+ "strParty7": 4106,
+ "strParty8": 4107,
+ "strParty9": 4108,
+ "strParty10": 4109,
+ "strParty11": 4110,
+ "strParty12": 4111,
+ "strParty13": 4112,
+ "strParty14": 4113,
+ "strParty15": 4114,
+ "strParty16": 4115,
+ "strParty17": 4116,
+ "strParty18": 4117,
+ "strParty19": 4118,
+ "strParty22": 4119,
+ "strParty24": 4120,
+ "strParty25": 4121,
+ "StrParty26": 4122,
+ "StrParty27": 4123,
+ "strGoldWithdraw": 4124,
+ "strGoldDrop": 4125,
+ "strGoldDeposit": 4126,
+ "strGoldTrade": 4127,
+ "strGoldInStash": 4128,
+ "strGoldTradepup": 4129,
+ "strUiMenu1": 4130,
+ "strUiBank": 4132,
+ "strUnknownTomb": 4133,
+ "strTradeOtherBox": 4134,
+ "strTradeBox": 4135,
+ "strFree": 4136,
+ "act1": 4137,
+ "act2": 4138,
+ "act3": 4139,
+ "act4": 4140,
+ "level": 4141,
+ "lowercasecancel": 4142,
+ "close": 4143,
+ "strClose": 4144,
+ "Lightning Spell": 4145,
+ "Fire Spell": 4146,
+ "Cold Spell": 4147,
+ "Yourparty": 4148,
+ "Inparty": 4149,
+ "Invite": 4150,
+ "Accept": 4151,
+ "Leave": 4152,
+ "Partyclose": 4153,
+ "partycharama": 4154,
+ "partycharsor": 4155,
+ "partycharbar": 4156,
+ "partycharnec": 4157,
+ "partycharpal": 4158,
+ "charavghit": 4159,
+ "charmonster": 4160,
+ "charmontohit1": 4161,
+ "charmontohit2": 4162,
+ "panelexp": 4163,
+ "panelstamina": 4164,
+ "panelhealth": 4165,
+ "panelmana": 4166,
+ "panelmini": 4167,
+ "panelcmini": 4168,
+ "minipanelchar": 4169,
+ "minipanelinv": 4170,
+ "minipaneltree": 4171,
+ "minipanelparty": 4172,
+ "minipanelautomap": 4173,
+ "minipanelmessage": 4174,
+ "minipanelquest": 4175,
+ "minipanelmenubtn": 4176,
+ "minipanelHelp": 4177,
+ "minipanelspecial": 4178,
+ "RunOn": 4179,
+ "RunOff": 4180,
+ "automapgame": 4181,
+ "automappw": 4182,
+ "automapdif": 4183,
+ "scrollbooktext": 4184,
+ "skilldesc1": 4185,
+ "skilldesc2": 4186,
+ "skilldesc3": 4187,
+ "skilldesc4": 4188,
+ "strpanel1": 4189,
+ "strpanel2": 4190,
+ "strpanel3": 4191,
+ "strpanel4": 4192,
+ "strpanel5": 4193,
+ "strpanel6": 4194,
+ "strpanel7": 4195,
+ "strpanel8": 4196,
+ "stashfull": 4197,
+ "Strhelp1": 4198,
+ "StrHelp2": 4199,
+ "StrHelp3": 4200,
+ "StrHelp4": 4201,
+ "StrHelp5": 4202,
+ "StrHelp6": 4203,
+ "StrHelp7": 4204,
+ "StrHelp8": 4205,
+ "StrHelp8a": 4206,
+ "StrHelp9": 4207,
+ "StrHelp10": 4208,
+ "StrHelp11": 4209,
+ "StrHelp12": 4210,
+ "StrHelp13": 4211,
+ "StrHelp14": 4212,
+ "StrHelp14a": 4213,
+ "StrHelp15": 4214,
+ "StrHelp16": 4215,
+ "StrHelp16a": 4216,
+ "StrHelp17": 4217,
+ "StrHelp18": 4218,
+ "StrHelp19": 4219,
+ "StrHelp20": 4220,
+ "StrHelp21": 4221,
+ "StrHelp22": 4222,
+ "strSklTree": 4223,
+ "StrSklTreea": 4224,
+ "StrSklTreeb": 4225,
+ "StrSklTreec": 4226,
+ "StrSklTree1": 4227,
+ "StrSklTree2": 4228,
+ "StrSklTree3": 4229,
+ "StrSklTree4": 4230,
+ "StrSklTree5": 4231,
+ "StrSklTree6": 4232,
+ "StrSklTree7": 4233,
+ "StrSklTree8": 4234,
+ "StrSklTree9": 4235,
+ "StrSklTree10": 4236,
+ "StrSklTree11": 4237,
+ "StrSklTree12": 4238,
+ "StrSklTree13": 4239,
+ "StrSklTree14": 4240,
+ "StrSklTree15": 4241,
+ "StrSklTree16": 4242,
+ "StrSklTree17": 4243,
+ "StrSklTree18": 4244,
+ "StrSklTree19": 4245,
+ "StrSklTree20": 4246,
+ "StrSklTree21": 4247,
+ "StrSklTree22": 4248,
+ "StrSklTree23": 4249,
+ "StrSklTree24": 4250,
+ "StrSklTree25": 4251,
+ "StrSkill0": 4252,
+ "StrSkill1": 4253,
+ "StrSkill2": 4254,
+ "StrSkill3": 4255,
+ "StrSkill4": 4256,
+ "StrSkill5": 4257,
+ "StrSkill6": 4258,
+ "StrSkill7": 4259,
+ "StrSkill8": 4260,
+ "StrSkill9": 4261,
+ "StrSkill10": 4262,
+ "StrSkill11": 4263,
+ "StrSkill12": 4264,
+ "StrSkill13": 4265,
+ "StrSkill14": 4266,
+ "StrSkill15": 4267,
+ "StrSkill16": 4268,
+ "StrSkill17": 4269,
+ "StrSkill18": 4270,
+ "StrSkill19": 4271,
+ "StrSkill20": 4272,
+ "StrSkill21": 4273,
+ "StrSkill22": 4274,
+ "StrSkill23": 4275,
+ "StrSkill24": 4276,
+ "StrSkill25": 4277,
+ "StrSkill26": 4278,
+ "StrSkill27": 4279,
+ "StrSkill28": 4280,
+ "StrSkill29": 4281,
+ "StrSkill30": 4282,
+ "StrSkill31": 4283,
+ "StrSkill32": 4284,
+ "StrSkill33": 4285,
+ "StrSkill34": 4286,
+ "StrSkill35": 4287,
+ "StrSkill36": 4288,
+ "StrSkill37": 4289,
+ "StrSkill38": 4290,
+ "StrSkill39": 4291,
+ "StrSkill40": 4292,
+ "StrSkill41": 4293,
+ "StrSkill42": 4294,
+ "StrSkill43": 4297,
+ "StrSkill44": 4298,
+ "StrSkill45": 4299,
+ "StrSkill46": 4300,
+ "StrSkill47": 4301,
+ "StrSkill48": 4302,
+ "StrSkill49": 4303,
+ "StrSkill50": 4304,
+ "StrSkill51": 4305,
+ "StrSkill52": 4306,
+ "StrSkill53": 4307,
+ "StrSkill54": 4308,
+ "StrSkill55": 4309,
+ "StrSkill56": 4310,
+ "StrSkill57": 4311,
+ "StrSkill58": 4312,
+ "StrSkill59": 4313,
+ "StrSkill60": 4314,
+ "StrSkill61": 4315,
+ "StrSkill62": 4316,
+ "StrSkill63": 4317,
+ "StrSkill64": 4318,
+ "StrSkill65": 4319,
+ "StrSkill66": 4320,
+ "StrSkill67": 4321,
+ "StrSkill68": 4322,
+ "StrSkill69": 4323,
+ "StrSkill70": 4324,
+ "StrSkill71": 4325,
+ "StrSkill72": 4326,
+ "StrSkill73": 4327,
+ "StrSkill74": 4328,
+ "StrSkill75": 4329,
+ "StrSkill76": 4330,
+ "StrSkill77": 4331,
+ "StrSkill78": 4332,
+ "StrSkill79": 4333,
+ "StrSkill80": 4334,
+ "StrSkill81": 4335,
+ "StrSkill82": 4336,
+ "StrSkill83": 4337,
+ "StrSkill84": 4338,
+ "StrSkill85": 4339,
+ "StrSkill86": 4340,
+ "StrSkill87": 4341,
+ "StrSkill88": 4342,
+ "StrSkill89": 4343,
+ "StrSkill90": 4344,
+ "StrSkill91": 4345,
+ "StrSkill92": 4346,
+ "StrSkill94": 4347,
+ "StrSkill95": 4348,
+ "StrSkill96": 4349,
+ "StrSkill97": 4350,
+ "StrSkill98": 4351,
+ "StrSkill99": 4352,
+ "StrSkill100": 4353,
+ "StrSkill101": 4354,
+ "StrSkill102": 4355,
+ "StrSkill103": 4356,
+ "StrSkill104": 4357,
+ "StrSkill105": 4358,
+ "StrSkill106": 4359,
+ "StrSkill107": 4360,
+ "StrSkill108": 4361,
+ "StrSkill109": 4362,
+ "StrSkill110": 4363,
+ "StrSkill111": 4364,
+ "StrSkill112": 4365,
+ "StrSkill113": 4366,
+ "StrSkill114": 4367,
+ "StrSkill115": 4368,
+ "StrSkill116": 4369,
+ "StrSkill117": 4370,
+ "StrSkill118": 4371,
+ "StrSkill119": 4372,
+ "skillname0": 4373,
+ "skillsd0": 4374,
+ "skillld0": 4375,
+ "skillan0": 4376,
+ "skillname1": 4377,
+ "skillsd1": 4378,
+ "skillld1": 4379,
+ "skillan1": 4380,
+ "skillname2": 4381,
+ "skillsd2": 4382,
+ "skillld2": 4383,
+ "skillan2": 4384,
+ "skillname3": 4385,
+ "skillsd3": 4386,
+ "skillld3": 4387,
+ "skillan3": 4388,
+ "skillname4": 4389,
+ "skillsd4": 4390,
+ "skillld4": 4391,
+ "skillan4": 4392,
+ "skillname5": 4393,
+ "skillsd5": 4394,
+ "skillld5": 4395,
+ "skillan5": 4396,
+ "skillname6": 4397,
+ "skillsd6": 4398,
+ "skillld6": 4399,
+ "skillan6": 4400,
+ "skillname7": 4401,
+ "skillsd7": 4402,
+ "skillld7": 4403,
+ "skillan7": 4404,
+ "skillname8": 4405,
+ "skillsd8": 4406,
+ "skillld8": 4407,
+ "skillan8": 4408,
+ "skillname9": 4409,
+ "skillsd9": 4410,
+ "skillld9": 4411,
+ "skillan9": 4412,
+ "skillname10": 4413,
+ "skillsd10": 4414,
+ "skillld10": 4415,
+ "skillan10": 4416,
+ "skillname11": 4417,
+ "skillsd11": 4418,
+ "skillld11": 4419,
+ "skillan11": 4420,
+ "skillname12": 4421,
+ "skillsd12": 4422,
+ "skillld12": 4423,
+ "skillan12": 4424,
+ "skillname13": 4425,
+ "skillsd13": 4426,
+ "skillld13": 4427,
+ "skillan13": 4428,
+ "skillname14": 4429,
+ "skillsd14": 4430,
+ "skillld14": 4431,
+ "skillan14": 4432,
+ "skillname15": 4433,
+ "skillsd15": 4434,
+ "skillld15": 4435,
+ "skillan15": 4436,
+ "skillname16": 4437,
+ "skillsd16": 4438,
+ "skillld16": 4439,
+ "skillan16": 4440,
+ "skillname17": 4441,
+ "skillsd17": 4442,
+ "skillld17": 4443,
+ "skillan17": 4444,
+ "skillname18": 4445,
+ "skillsd18": 4446,
+ "skillld18": 4447,
+ "skillan18": 4448,
+ "skillname19": 4449,
+ "skillsd19": 4450,
+ "skillld19": 4451,
+ "skillan19": 4452,
+ "skillname20": 4453,
+ "skillsd20": 4454,
+ "skillld20": 4455,
+ "skillan20": 4456,
+ "skillname21": 4457,
+ "skillsd21": 4458,
+ "skillld21": 4459,
+ "skillan21": 4460,
+ "skillname22": 4461,
+ "skillsd22": 4462,
+ "skillld22": 4463,
+ "skillan22": 4464,
+ "skillname23": 4465,
+ "skillsd23": 4466,
+ "skillld23": 4467,
+ "skillan23": 4468,
+ "skillname24": 4469,
+ "skillsd24": 4470,
+ "skillld24": 4471,
+ "skillan24": 4472,
+ "skillname25": 4473,
+ "skillsd25": 4474,
+ "skillld25": 4475,
+ "skillan25": 4476,
+ "skillname26": 4477,
+ "skillsd26": 4478,
+ "skillld26": 4479,
+ "skillan26": 4480,
+ "skillname27": 4481,
+ "skillsd27": 4482,
+ "skillld27": 4483,
+ "skillan27": 4484,
+ "skillname28": 4485,
+ "skillsd28": 4486,
+ "skillld28": 4487,
+ "skillan28": 4488,
+ "skillname29": 4489,
+ "skillsd29": 4490,
+ "skillld29": 4491,
+ "skillan29": 4492,
+ "skillname30": 4493,
+ "skillsd30": 4494,
+ "skillld30": 4495,
+ "skillan30": 4496,
+ "skillname31": 4497,
+ "skillsd31": 4498,
+ "skillld31": 4499,
+ "skillan31": 4500,
+ "skillname32": 4501,
+ "skillsd32": 4502,
+ "skillld32": 4503,
+ "skillan32": 4504,
+ "skillname33": 4505,
+ "skillsd33": 4506,
+ "skillld33": 4507,
+ "skillan33": 4508,
+ "skillname34": 4509,
+ "skillsd34": 4510,
+ "skillld34": 4511,
+ "skillan34": 4512,
+ "skillname35": 4513,
+ "skillsd35": 4514,
+ "skillld35": 4515,
+ "skillan35": 4516,
+ "skillname36": 4517,
+ "skillsd36": 4518,
+ "skillld36": 4519,
+ "skillan36": 4520,
+ "skillname37": 4521,
+ "skillsd37": 4522,
+ "skillld37": 4523,
+ "skillan37": 4524,
+ "skillname38": 4525,
+ "skillsd38": 4526,
+ "skillld38": 4527,
+ "skillan38": 4528,
+ "skillname39": 4529,
+ "skillsd39": 4530,
+ "skillld39": 4531,
+ "skillan39": 4532,
+ "skillname40": 4533,
+ "skillsd40": 4534,
+ "skillld40": 4535,
+ "skillan40": 4536,
+ "skillname41": 4537,
+ "skillsd41": 4538,
+ "skillld41": 4539,
+ "skillan41": 4540,
+ "skillname42": 4541,
+ "skillsd42": 4542,
+ "skillld42": 4543,
+ "skillan42": 4544,
+ "skillname43": 4545,
+ "skillsd43": 4546,
+ "skillld43": 4547,
+ "skillan43": 4548,
+ "skillname44": 4549,
+ "skillsd44": 4550,
+ "skillld44": 4551,
+ "skillan44": 4552,
+ "skillname45": 4553,
+ "skillsd45": 4554,
+ "skillld45": 4555,
+ "skillan45": 4556,
+ "skillname46": 4557,
+ "skillsd46": 4558,
+ "skillld46": 4559,
+ "skillan46": 4560,
+ "skillname47": 4561,
+ "skillsd47": 4562,
+ "skillld47": 4563,
+ "skillan47": 4564,
+ "skillname48": 4565,
+ "skillsd48": 4566,
+ "skillld48": 4567,
+ "skillan48": 4568,
+ "skillname49": 4569,
+ "skillsd49": 4570,
+ "skillld49": 4571,
+ "skillan49": 4572,
+ "skillname50": 4573,
+ "skillsd50": 4574,
+ "skillld50": 4575,
+ "skillan50": 4576,
+ "skillname51": 4577,
+ "skillsd51": 4578,
+ "skillld51": 4579,
+ "skillan51": 4580,
+ "skillname52": 4581,
+ "skillsd52": 4582,
+ "skillld52": 4583,
+ "skillan52": 4584,
+ "skillname53": 4585,
+ "skillsd53": 4586,
+ "skillld53": 4587,
+ "skillan53": 4588,
+ "skillname54": 4589,
+ "skillsd54": 4590,
+ "skillld54": 4591,
+ "skillan54": 4592,
+ "skillname55": 4593,
+ "skillsd55": 4594,
+ "skillld55": 4595,
+ "skillan55": 4596,
+ "skillname56": 4597,
+ "skillsd56": 4598,
+ "skillld56": 4599,
+ "skillan56": 4600,
+ "skillname57": 4601,
+ "skillsd57": 4602,
+ "skillld57": 4603,
+ "skillan57": 4604,
+ "skillname58": 4605,
+ "skillsd58": 4606,
+ "skillld58": 4607,
+ "skillan58": 4608,
+ "skillname59": 4609,
+ "skillsd59": 4610,
+ "skillld59": 4611,
+ "skillan59": 4612,
+ "skillname60": 4613,
+ "skillsd60": 4614,
+ "skillld60": 4615,
+ "skillan60": 4616,
+ "skillsname61": 4617,
+ "skillsd61": 4618,
+ "skillld61": 4619,
+ "skillan61": 4620,
+ "skillname62": 4621,
+ "skillsd62": 4622,
+ "skillld62": 4623,
+ "skillan62": 4624,
+ "skillname63": 4625,
+ "skillsd63": 4626,
+ "skillld63": 4627,
+ "skillan63": 4628,
+ "skillname64": 4629,
+ "skillsd64": 4630,
+ "skillld64": 4631,
+ "skillan64": 4632,
+ "skillname65": 4633,
+ "skillsd65": 4634,
+ "skillld65": 4635,
+ "skillan65": 4636,
+ "skillname66": 4637,
+ "skillsd66": 4638,
+ "skillld66": 4639,
+ "skillan66": 4640,
+ "skillname67": 4641,
+ "skillsd67": 4642,
+ "skillld67": 4643,
+ "skillan67": 4644,
+ "skillname68": 4645,
+ "skillsd68": 4646,
+ "skillld68": 4647,
+ "skillan68": 4648,
+ "skillname69": 4649,
+ "skillsd69": 4650,
+ "skillld69": 4651,
+ "skillan69": 4652,
+ "skillname70": 4653,
+ "skillsd70": 4654,
+ "skillld70": 4655,
+ "skillan70": 4656,
+ "skillname71": 4657,
+ "skillsd71": 4658,
+ "skillld71": 4659,
+ "skillan71": 4660,
+ "skillname72": 4661,
+ "skillsd72": 4662,
+ "skillld72": 4663,
+ "skillan72": 4664,
+ "skillname73": 4665,
+ "skillsd73": 4666,
+ "skillld73": 4667,
+ "skillan73": 4668,
+ "skillname74": 4669,
+ "skillsd74": 4670,
+ "skillld74": 4671,
+ "skillan74": 4672,
+ "skillname75": 4673,
+ "skillsd75": 4674,
+ "skillld75": 4675,
+ "skillan75": 4676,
+ "skillname76": 4677,
+ "skillsd76": 4678,
+ "skillld76": 4679,
+ "skillan76": 4680,
+ "skillname77": 4681,
+ "skillsd77": 4682,
+ "skillld77": 4683,
+ "skillan77": 4684,
+ "skillname78": 4685,
+ "skillsd78": 4686,
+ "skillld78": 4687,
+ "skillan78": 4688,
+ "skillname79": 4689,
+ "skillsd79": 4690,
+ "skillld79": 4691,
+ "skillan79": 4692,
+ "skillname80": 4693,
+ "skillsd80": 4694,
+ "skillld80": 4695,
+ "skillan80": 4696,
+ "skillname81": 4697,
+ "skillsd81": 4698,
+ "skillld81": 4699,
+ "skillan81": 4700,
+ "skillname82": 4701,
+ "skillsd82": 4702,
+ "skillld82": 4703,
+ "skillan82": 4704,
+ "skillname83": 4705,
+ "skillsd83": 4706,
+ "skillld83": 4707,
+ "skillan83": 4708,
+ "skillname84": 4709,
+ "skillsd84": 4710,
+ "skillld84": 4711,
+ "skillan84": 4712,
+ "skillname85": 4713,
+ "skillsd85": 4714,
+ "skillld85": 4715,
+ "skillan85": 4716,
+ "skillname86": 4717,
+ "skillsd86": 4718,
+ "skillld86": 4719,
+ "skillan86": 4720,
+ "skillname87": 4721,
+ "skillsd87": 4722,
+ "skillld87": 4723,
+ "skillan87": 4724,
+ "skillname88": 4725,
+ "skillsd88": 4726,
+ "skillld88": 4727,
+ "skillan88": 4728,
+ "skillname89": 4729,
+ "skillsd89": 4730,
+ "skillld89": 4731,
+ "skillan89": 4732,
+ "skillname90": 4733,
+ "skillsd90": 4734,
+ "skillld90": 4735,
+ "skillan90": 4736,
+ "skillname91": 4737,
+ "skillsd91": 4738,
+ "skillld91": 4739,
+ "skillan91": 4740,
+ "skillname92": 4741,
+ "skillsd92": 4742,
+ "skillld92": 4743,
+ "skillan92": 4744,
+ "skillname93": 4745,
+ "skillsd93": 4746,
+ "skillld93": 4747,
+ "skillan93": 4748,
+ "skillname94": 4749,
+ "skillsd94": 4750,
+ "skillld94": 4751,
+ "skillan94": 4752,
+ "skillname95": 4753,
+ "skillsd95": 4754,
+ "skillld95": 4755,
+ "skillan95": 4756,
+ "skillname96": 4757,
+ "skillsd96": 4758,
+ "skillld96": 4759,
+ "skillan96": 4760,
+ "skillname97": 4761,
+ "skillsd97": 4762,
+ "skillld97": 4763,
+ "skillan97": 4764,
+ "skillname98": 4765,
+ "skillsd98": 4766,
+ "skillld98": 4767,
+ "skillan98": 4768,
+ "skillname99": 4769,
+ "skillsd99": 4770,
+ "skillld99": 4771,
+ "skillan99": 4772,
+ "skillname100": 4773,
+ "skillsd100": 4774,
+ "skillld100": 4775,
+ "skillan100": 4776,
+ "skillname101": 4777,
+ "skillsd101": 4778,
+ "skillld101": 4779,
+ "skillan101": 4780,
+ "skillname102": 4781,
+ "skillsd102": 4782,
+ "skillld102": 4783,
+ "skillan102": 4784,
+ "skillname103": 4785,
+ "skillsd103": 4786,
+ "skillld103": 4787,
+ "skillan103": 4788,
+ "skillname104": 4789,
+ "skillsd104": 4790,
+ "skillld104": 4791,
+ "skillan104": 4792,
+ "skillname105": 4793,
+ "skillsd105": 4794,
+ "skillld105": 4795,
+ "skillan105": 4796,
+ "skillname106": 4797,
+ "skillsd106": 4798,
+ "skillld106": 4799,
+ "skillan106": 4800,
+ "skillname107": 4801,
+ "skillsd107": 4802,
+ "skillld107": 4803,
+ "skillan107": 4804,
+ "skillname108": 4805,
+ "skillsd108": 4806,
+ "skillld108": 4807,
+ "skillan108": 4808,
+ "skillname109": 4809,
+ "skillsd109": 4810,
+ "skillld109": 4811,
+ "skillan109": 4812,
+ "skillname110": 4813,
+ "skillsd110": 4814,
+ "skillld110": 4815,
+ "skillan110": 4816,
+ "skillname111": 4817,
+ "skillsd111": 4818,
+ "skillld111": 4819,
+ "skillan111": 4820,
+ "skillname112": 4821,
+ "skillsd112": 4822,
+ "skillld112": 4823,
+ "skillan112": 4824,
+ "skillname113": 4825,
+ "skillsd113": 4826,
+ "skillld113": 4827,
+ "skillan113": 4828,
+ "skillname114": 4829,
+ "skillsd114": 4830,
+ "skillld114": 4831,
+ "skillan114": 4832,
+ "skillname115": 4833,
+ "skillsd115": 4834,
+ "skillld115": 4835,
+ "skillan115": 4836,
+ "skillname116": 4837,
+ "skillsd116": 4838,
+ "skillld116": 4839,
+ "skillan116": 4840,
+ "skillname117": 4841,
+ "skillsd117": 4842,
+ "skillld117": 4843,
+ "skillan117": 4844,
+ "skillname118": 4845,
+ "skillsd118": 4846,
+ "skillld118": 4847,
+ "skillan118": 4848,
+ "skillname119": 4849,
+ "skillsd119": 4850,
+ "skillld119": 4851,
+ "skillan119": 4852,
+ "skillname120": 4853,
+ "skillsd120": 4854,
+ "skillld120": 4855,
+ "skillan120": 4856,
+ "skillname121": 4857,
+ "skillsd121": 4858,
+ "skillld121": 4859,
+ "skillan121": 4860,
+ "skillname122": 4861,
+ "skillsd122": 4862,
+ "skillld122": 4863,
+ "skillan122": 4864,
+ "skillname123": 4865,
+ "skillsd123": 4866,
+ "skillld123": 4867,
+ "skillan123": 4868,
+ "skillname124": 4869,
+ "skillsd124": 4870,
+ "skillld124": 4871,
+ "skillan124": 4872,
+ "skillname125": 4873,
+ "skillsd125": 4874,
+ "skillld125": 4875,
+ "skillan125": 4876,
+ "skillname126": 4877,
+ "skillsd126": 4878,
+ "skillld126": 4879,
+ "skillan126": 4880,
+ "skillname127": 4881,
+ "skillsd127": 4882,
+ "skillld127": 4883,
+ "skillan127": 4884,
+ "skillname128": 4885,
+ "skillsd128": 4886,
+ "skillld128": 4887,
+ "skillan128": 4888,
+ "skillname129": 4889,
+ "skillsd129": 4890,
+ "skillld129": 4891,
+ "skillan129": 4892,
+ "skillname130": 4893,
+ "skillsd130": 4894,
+ "skillld130": 4895,
+ "skillan130": 4896,
+ "skillname131": 4897,
+ "skillsd131": 4898,
+ "skillld131": 4899,
+ "skillan131": 4900,
+ "skillname132": 4901,
+ "skillsd132": 4902,
+ "skillld132": 4903,
+ "skillan132": 4904,
+ "skillname133": 4905,
+ "skillsd133": 4906,
+ "skillld133": 4907,
+ "skillan133": 4908,
+ "skillname134": 4909,
+ "skillsd134": 4910,
+ "skillld134": 4911,
+ "skillan134": 4912,
+ "skillname135": 4913,
+ "skillsd135": 4914,
+ "skillld135": 4915,
+ "skillan135": 4916,
+ "skillname136": 4917,
+ "skillsd136": 4918,
+ "skillld136": 4919,
+ "skillan136": 4920,
+ "skillname137": 4921,
+ "skillsd137": 4922,
+ "skillld137": 4923,
+ "skillan137": 4924,
+ "skillname138": 4925,
+ "skillsd138": 4926,
+ "skillld138": 4927,
+ "skillan138": 4928,
+ "skillname139": 4929,
+ "skillsd139": 4930,
+ "skillld139": 4931,
+ "skillan139": 4932,
+ "skillname140": 4933,
+ "skillsd140": 4934,
+ "skillld140": 4935,
+ "skillan140": 4936,
+ "skillname141": 4937,
+ "skillsd141": 4938,
+ "skillld141": 4939,
+ "skillan141": 4940,
+ "skillname142": 4941,
+ "skillsd142": 4942,
+ "skillld142": 4943,
+ "skillan142": 4944,
+ "skillname143": 4945,
+ "skillsd143": 4946,
+ "skillld143": 4947,
+ "skillan143": 4948,
+ "skillname144": 4949,
+ "skillsd144": 4950,
+ "skillld144": 4951,
+ "skillan144": 4952,
+ "skillname145": 4953,
+ "skillsd145": 4954,
+ "skillld145": 4955,
+ "skillan145": 4956,
+ "skillname146": 4957,
+ "skillsd146": 4958,
+ "skillld146": 4959,
+ "skillan146": 4960,
+ "skillname147": 4961,
+ "skillsd147": 4962,
+ "skillld147": 4963,
+ "skillan147": 4964,
+ "skillname148": 4965,
+ "skillsd148": 4966,
+ "skillld148": 4967,
+ "skillan148": 4968,
+ "skillname149": 4969,
+ "skillsd149": 4970,
+ "skillld149": 4971,
+ "skillan149": 4972,
+ "skillname150": 4973,
+ "skillsd150": 4974,
+ "skillld150": 4975,
+ "skillan150": 4976,
+ "skillname151": 4977,
+ "skillsd151": 4978,
+ "skillld151": 4979,
+ "skillan151": 4980,
+ "skillname152": 4981,
+ "skillsd152": 4982,
+ "skillld152": 4983,
+ "skillan152": 4984,
+ "skillname153": 4985,
+ "skillsd153": 4986,
+ "skillld153": 4987,
+ "skillan153": 4988,
+ "skillname154": 4989,
+ "skillsd154": 4990,
+ "skillld154": 4991,
+ "skillan154": 4992,
+ "skillname155": 4993,
+ "skillsd155": 4994,
+ "skillld155": 4995,
+ "skillan155": 4996,
+ "skillname217": 4997,
+ "skillsd217": 4998,
+ "skillld217": 4999,
+ "skillan217": 5000,
+ "skillname218": 5001,
+ "skillsd218": 5002,
+ "skillld218": 5003,
+ "skillan218": 5004,
+ "skillname219": 5005,
+ "skillsd219": 5006,
+ "skillld219": 5007,
+ "skillan219": 5008,
+ "skillname220": 5009,
+ "skillsd220": 5010,
+ "skillld220": 5011,
+ "skillan220": 5012,
+ "strMephistoDoorLocked": 5013,
+ "strTitleFeminine": 5014,
+ "strTitleMasculine": 5015,
+ "strChatHardcore": 5016,
+ "strChatLevel": 5017,
+ "Tristram": 5018,
+ "Catacombs Level 4": 5019,
+ "Catacombs Level 3": 5020,
+ "Catacombs Level 2": 5021,
+ "Catacombs Level 1": 5022,
+ "Cathedral": 5023,
+ "Inner Cloister": 5024,
+ "Jail Level 3": 5025,
+ "Jail Level 2": 5026,
+ "Jail Level 1": 5027,
+ "Barracks": 5028,
+ "Outer Cloister": 5029,
+ "Monastery Gate": 5030,
+ "Tower Cellar Level 5": 5031,
+ "Tower Cellar Level 4": 5032,
+ "Tower Cellar Level 3": 5033,
+ "Tower Cellar Level 2": 5034,
+ "Tower Cellar Level 1": 5035,
+ "Forgotten Tower": 5036,
+ "Mausoleum": 5037,
+ "Crypt": 5038,
+ "Burial Grounds": 5039,
+ "Pit Level 2": 5040,
+ "Hole Level 2": 5041,
+ "Underground Passage Level 2": 5042,
+ "Cave Level 2": 5043,
+ "Pit Level 1": 5044,
+ "Hole Level 1": 5045,
+ "Underground Passage Level 1": 5046,
+ "Cave Level 1": 5047,
+ "Den of Evil": 5048,
+ "Tamoe Highland": 5049,
+ "Black Marsh": 5050,
+ "Dark Wood": 5051,
+ "Stony Field": 5052,
+ "Cold Plains": 5053,
+ "Blood Moor": 5054,
+ "Rogue Encampment": 5055,
+ "To Tristram": 5056,
+ "To The Catacombs Level 4": 5057,
+ "To The Catacombs Level 3": 5058,
+ "To The Catacombs Level 2": 5059,
+ "To The Catacombs Level 1": 5060,
+ "To The Cathedral": 5061,
+ "To The Inner Cloister": 5062,
+ "To The Jail Level 3": 5063,
+ "To The Jail Level 2": 5064,
+ "To The Jail Level 1": 5065,
+ "To The Barracks": 5066,
+ "To The Outer Cloister": 5067,
+ "To The Monastery Gate": 5068,
+ "To The Tower Cellar Level 5": 5069,
+ "To The Tower Cellar Level 4": 5070,
+ "To The Tower Cellar Level 3": 5071,
+ "To The Tower Cellar Level 2": 5072,
+ "To The Tower Cellar Level 1": 5073,
+ "To The Forgotten Tower": 5074,
+ "To The Mausoleum": 5075,
+ "To The Crypt": 5076,
+ "To The Burial Grounds": 5077,
+ "To The Pit Level 2": 5078,
+ "To The Hole Level 2": 5079,
+ "To Underground Passage Level 2": 5080,
+ "To The Cave Level 2": 5081,
+ "To The Pit Level 1": 5082,
+ "To The Hole Level 1": 5083,
+ "To Underground Passage Level 1": 5084,
+ "To The Cave Level 1": 5085,
+ "To The Den of Evil": 5086,
+ "To The Tamoe Highland": 5087,
+ "To The Black Marsh": 5088,
+ "To The Dark Wood": 5089,
+ "To The Stony Field": 5090,
+ "To The Cold Plains": 5091,
+ "To The Blood Moor": 5092,
+ "To The Rogue Encampment": 5093,
+ "Deathmessage": 5094,
+ "Deathmessnight": 5095,
+ "Harddeathmessage": 5096,
+ "LordofTerrordied": 5097,
+ "Killdiablo1": 5098,
+ "KillDiablo2": 5099,
+ "KillDiablo3": 5100,
+ "x": 22741,
+ "X": 22746,
+ "Gem Activated": 5334,
+ "Gem Deactivated": 5335,
+ "Perfect Gem Activated": 5336,
+ "dummy": 5382,
+ "Dummy": 5383,
+ "not used": 5384,
+ "unused": 5385,
+ "Not used": 5386,
+ "convertsto": 5387,
+ "strNotInBeta": 5388,
+ "strLevelLoadFailed": 5389,
+ "Endthispuppy": 5390,
+ "A4Q2ExpansionSuccessTyrael": 20000,
+ "A4Q2ExpansionSuccessCain": 20001,
+ "AncientsAct5IntroGossip1": 20002,
+ "CainAct5IntroGossip1": 20003,
+ "CainAct5Gossip1": 20004,
+ "CainAct5Gossip2": 20005,
+ "CainAct5Gossip3": 20006,
+ "CainAct5Gossip4": 20007,
+ "CainAct5Gossip5": 20008,
+ "CainAct5Gossip6": 20009,
+ "CainAct5Gossip7": 20010,
+ "CainAct5Gossip8": 20011,
+ "CainAct5Gossip9": 20012,
+ "CainAct5Gossip10": 20013,
+ "AnyaAct5IntroGossip1": 20014,
+ "AnyaGossip1": 20015,
+ "AnyaGossip2": 20016,
+ "AnyaGossip3": 20017,
+ "AnyaGossip4": 20018,
+ "AnyaGossip5": 20019,
+ "AnyaGossip6": 20020,
+ "AnyaGossip7": 20021,
+ "AnyaGossip8": 20022,
+ "AnyaGossip9": 20023,
+ "AnyaGossip10": 20024,
+ "LarzukAct5IntroGossip1": 20025,
+ "LarzukAct5IntroAmaGossip1": 20026,
+ "LarzukGossip1": 20027,
+ "LarzukGossip2": 20028,
+ "LarzukGossip3": 20029,
+ "LarzukGossip4": 20030,
+ "LarzukGossip5": 20031,
+ "LarzukGossip6": 20032,
+ "LarzukGossip7": 20033,
+ "LarzukGossip8": 20034,
+ "LarzukGossip9": 20035,
+ "LarzukGossip10": 20036,
+ "MalahAct5IntroGossip1": 20037,
+ "MalahAct5IntroSorGossip1": 20038,
+ "MalahAct5IntroBarGossip1": 20039,
+ "MalahGossip1": 20040,
+ "MalahGossip2": 20041,
+ "MalahGossip3": 20042,
+ "MalahGossip4": 20043,
+ "MalahGossip5": 20044,
+ "MalahGossip6": 20045,
+ "MalahGossip7": 20046,
+ "MalahGossip8": 20047,
+ "MalahGossip9": 20048,
+ "MalahGossip10": 20049,
+ "MalahGossip11": 20050,
+ "MalahGossip12": 20051,
+ "MalahGossip13": 20052,
+ "NihlathakAct5IntroGossip1": 20053,
+ "NihlathakAct5IntroAssGossip1": 20054,
+ "NihlathakAct5IntroNecGossip1": 20055,
+ "NihlathakGossip1": 20056,
+ "NihlathakGossip2": 20057,
+ "NihlathakGossip3": 20058,
+ "NihlathakGossip4": 20059,
+ "NihlathakGossip5": 20060,
+ "NihlathakGossip6": 20061,
+ "NihlathakGossip7": 20062,
+ "NihlathakGossip8": 20063,
+ "NihlathakGossip9": 20064,
+ "QualKehkAct5IntroGossip1": 20065,
+ "QualKehkAct5IntroPalGossip1": 20066,
+ "QualKehkAct5IntroDruGossip1": 20067,
+ "QualKehkGossip1": 20068,
+ "QualKehkGossip2": 20069,
+ "QualKehkGossip3": 20070,
+ "QualKehkGossip4": 20071,
+ "QualKehkGossip5": 20072,
+ "QualKehkGossip6": 20073,
+ "QualKehkGossip7": 20074,
+ "QualKehkGossip8": 20075,
+ "QualKehkGossip9": 20076,
+ "A5Q1InitLarzuk": 20077,
+ "A5Q1AfterInitLarzuk": 20078,
+ "A5Q1AfterInitCain": 20079,
+ "A5Q1AfterInitAnya": 20080,
+ "A5Q1AfterInitMalah": 20081,
+ "A5Q1AfterInitNihlathak": 20082,
+ "A5Q1AfterInitQualKehk": 20083,
+ "A5Q1EarlyReturnLarzuk": 20084,
+ "A5Q1EarlyReturnCain": 20085,
+ "A5Q1EarlyReturnAnya": 20086,
+ "A5Q1EarlyReturnMalah": 20087,
+ "A5Q1EarlyReturnNihlathak": 20088,
+ "A5Q1EarlyReturnQualKehk": 20089,
+ "A5Q1SuccessfulLarzuk": 20090,
+ "A5Q1SuccessfulCain": 20091,
+ "A5Q1SuccessfulAnya": 20092,
+ "A5Q1SuccessfulMalah": 20093,
+ "A5Q1SuccessfulNihlathak": 20094,
+ "A5Q1SuccessfulQualKehk": 20095,
+ "A5Q2InitQualKehk": 20096,
+ "A5Q2AfterInitQualKehk": 20097,
+ "A5Q2AfterInitCain": 20098,
+ "A5Q2AfterInitAnya": 20099,
+ "A5Q2AfterInitLarzuk": 20100,
+ "A5Q2AfterInitMalah": 20101,
+ "A5Q2AfterInitNihlathak": 20102,
+ "A5Q2EarlyReturnQualKehk": 20103,
+ "A5Q2EarlyReturnQualKehkMan": 20104,
+ "A5Q2EarlyReturnCain": 20105,
+ "A5Q2EarlyReturnAnya": 20106,
+ "A5Q2EarlyReturnLarzuk": 20107,
+ "A5Q2EarlyReturnMalah": 20108,
+ "A5Q2EarlyReturnNihlathak": 20109,
+ "A5Q2SuccessfulQualKehk": 20110,
+ "A5Q2SuccessfulCain": 20111,
+ "A5Q2SuccessfulAnya": 20112,
+ "A5Q2SuccessfulLarzuk": 20113,
+ "A5Q2SuccessfulMalah": 20114,
+ "A5Q2SuccessfulNihlathak": 20115,
+ "A5Q3InitMalah": 20116,
+ "A5Q3AfterInitMalah": 20117,
+ "A5Q3AfterInitCain": 20118,
+ "A5Q3AfterInitLarzuk": 20119,
+ "A5Q3AfterInitNihlathak": 20120,
+ "A5Q3AfterInitQualKehk": 20121,
+ "A5Q3EarlyReturnMalah": 20122,
+ "A5Q3EarlyReturnCain": 20123,
+ "A5Q3EarlyReturnLarzuk": 20124,
+ "A5Q3EarlyReturnNihlathak": 20125,
+ "A5Q3EarlyReturnQualKehk": 20126,
+ "A5Q3FoundAnyaMalah": 20127,
+ "A5Q3FoundAnyaCain": 20128,
+ "A5Q3FoundAnyaLarzuk": 20129,
+ "A5Q3FoundAnyaQualKehk": 20130,
+ "A5Q3FoundAnyaAnya": 20131,
+ "A5Q3SuccessfulMalah": 20132,
+ "A5Q3SuccessfulCain": 20133,
+ "A5Q3SuccessfulLarzuk": 20134,
+ "A5Q3SuccessfulQualKehk": 20135,
+ "A5Q3SuccessfulAnya": 20136,
+ "A5Q4InitAnya": 20137,
+ "A5Q4AfterInitAnya": 20138,
+ "A5Q4AfterInitCain": 20139,
+ "A5Q4AfterInitMalah": 20140,
+ "A5Q4AfterInitLarzuk": 20141,
+ "A5Q4AfterInitQualKehk": 20142,
+ "A5Q4EarlyReturnAnya": 20143,
+ "A5Q4EarlyReturnCain": 20144,
+ "A5Q4EarlyReturnLarzuk": 20145,
+ "A5Q4EarlyReturnMalah": 20146,
+ "A5Q4EarlyReturnQualKehk": 20147,
+ "A5Q4SuccessfulAnya": 20148,
+ "A5Q4SuccessfulCain": 20149,
+ "A5Q4SuccessfulLarzuk": 20150,
+ "A5Q4SuccessfulMalah": 20151,
+ "A5Q4SuccessfulQualKehk": 20152,
+ "A5Q5InitQualKehk": 20153,
+ "A5Q5AfterInitQualKehk": 20154,
+ "A5Q5AfterInitCain": 20155,
+ "A5Q5AfterInitAnya": 20156,
+ "A5Q5AfterInitLarzuk": 20157,
+ "A5Q5AfterInitMalah": 20158,
+ "A5Q5EarlyReturnQualKehk": 20159,
+ "A5Q5EarlyReturnCain": 20160,
+ "A5Q5EarlyReturnAnya": 20161,
+ "A5Q5EarlyReturnLarzuk": 20162,
+ "A5Q5EarlyReturnMalah": 20163,
+ "A5Q5SuccessfulQualKehk": 20164,
+ "A5Q5SuccessfulCain": 20165,
+ "A5Q5SuccessfulAnya": 20166,
+ "A5Q5SuccessfulLarzuk": 20167,
+ "A5Q5SuccessfulMalah": 20168,
+ "A5Q6InitAncients": 20169,
+ "A5Q6EarlyReturnCain": 20170,
+ "A5Q6EarlyReturnLarzuk": 20171,
+ "A5Q6EarlyReturnMalah": 20172,
+ "A5Q6EarlyReturnAnya": 20173,
+ "A5Q6EarlyReturnQualKehk": 20174,
+ "A5Q6SuccessfulTyrael": 20175,
+ "A5Q6SuccessfulAnya": 20176,
+ "A5Q6SuccessfulCain": 20177,
+ "A5Q6SuccessfulLarzuk": 20178,
+ "A5Q6SuccessfulMalah": 20179,
+ "A5Q6SuccessfulQualKehk": 20180,
+ "ktr": 20181,
+ "wrb": 20182,
+ "ces": 20183,
+ "clw": 20184,
+ "btl": 20185,
+ "skr": 20186,
+ "9ar": 20187,
+ "9wb": 20188,
+ "9xf": 20189,
+ "9cs": 20190,
+ "9lw": 20191,
+ "9tw": 20192,
+ "9qr": 20193,
+ "7ar": 20194,
+ "7wb": 20195,
+ "7xf": 20196,
+ "7cs": 20197,
+ "7lw": 20198,
+ "7tw": 20199,
+ "7qr": 20200,
+ "7ha": 20201,
+ "7ax": 20202,
+ "72a": 20203,
+ "7mp": 20204,
+ "7wa": 20205,
+ "7la": 20206,
+ "7ba": 20207,
+ "7bt": 20208,
+ "7ga": 20209,
+ "7gi": 20210,
+ "7wn": 20211,
+ "7yw": 20212,
+ "7bw": 20213,
+ "7gw": 20214,
+ "7cl": 20215,
+ "7sc": 20216,
+ "7qs": 20217,
+ "7ws": 20218,
+ "7sp": 20219,
+ "7ma": 20220,
+ "7mt": 20221,
+ "7fl": 20222,
+ "7wh": 20223,
+ "7m7": 20224,
+ "7gm": 20225,
+ "7ss": 20226,
+ "7sm": 20227,
+ "7sb": 20228,
+ "7fc": 20229,
+ "7cr": 20230,
+ "7bs": 20231,
+ "7ls": 20232,
+ "7wd": 20233,
+ "72h": 20234,
+ "7cm": 20235,
+ "7gs": 20236,
+ "7b7": 20237,
+ "7fb": 20238,
+ "7gd": 20239,
+ "7dg": 20240,
+ "7di": 20241,
+ "7kr": 20242,
+ "7bl": 20243,
+ "7tk": 20244,
+ "7ta": 20245,
+ "7bk": 20246,
+ "7b8": 20247,
+ "7ja": 20248,
+ "7pi": 20249,
+ "7s7": 20250,
+ "7gl": 20251,
+ "7ts": 20252,
+ "7sr": 20253,
+ "7tr": 20254,
+ "7br": 20255,
+ "7st": 20256,
+ "7p7": 20257,
+ "7o7": 20258,
+ "7vo": 20259,
+ "7s8": 20260,
+ "7pa": 20261,
+ "7h7": 20262,
+ "7wc": 20263,
+ "6ss": 20264,
+ "6ls": 20265,
+ "6cs": 20266,
+ "6bs": 20267,
+ "6ws": 20268,
+ "6sb": 20269,
+ "6hb": 20270,
+ "6lb": 20271,
+ "6cb": 20272,
+ "6s7": 20273,
+ "6l7": 20274,
+ "6sw": 20275,
+ "6lw": 20276,
+ "6lx": 20277,
+ "6mx": 20278,
+ "6hx": 20279,
+ "6rx": 20280,
+ "am1": 20292,
+ "am2": 20293,
+ "am3": 20294,
+ "am4": 20295,
+ "am5": 20296,
+ "ob6": 20297,
+ "ob7": 20298,
+ "ob8": 20299,
+ "ob9": 20300,
+ "oba": 20301,
+ "am6": 20302,
+ "am7": 20303,
+ "am8": 20304,
+ "am9": 20305,
+ "ama": 20306,
+ "obb": 20307,
+ "obc": 20308,
+ "obd": 20309,
+ "obe": 20310,
+ "obf": 20311,
+ "amb": 20312,
+ "amc": 20313,
+ "amd": 20314,
+ "ame": 20315,
+ "amf": 20316,
+ "ba1": 20322,
+ "ba2": 20323,
+ "ba3": 20324,
+ "ba4": 20325,
+ "ba5": 20326,
+ "pa1": 20327,
+ "pa2": 20328,
+ "pa3": 20329,
+ "pa4": 20330,
+ "pa5": 20331,
+ "ci0": 20337,
+ "ci1": 20338,
+ "ci2": 20339,
+ "ci3": 20340,
+ "uap": 20341,
+ "ukp": 20342,
+ "ulm": 20343,
+ "uhl": 20344,
+ "uhm": 20345,
+ "urn": 20346,
+ "usk": 20347,
+ "uui": 20348,
+ "uea": 20349,
+ "ula": 20350,
+ "utu": 20351,
+ "ung": 20352,
+ "ucl": 20353,
+ "uhn": 20354,
+ "urs": 20355,
+ "upl": 20356,
+ "ult": 20357,
+ "uld": 20358,
+ "uth": 20359,
+ "uul": 20360,
+ "uar": 20361,
+ "utp": 20362,
+ "uuc": 20363,
+ "uml": 20364,
+ "urg": 20365,
+ "uit": 20366,
+ "uow": 20367,
+ "uts": 20368,
+ "ulg": 20369,
+ "uvg": 20370,
+ "umg": 20371,
+ "utg": 20372,
+ "uhg": 20373,
+ "ulb": 20374,
+ "uvb": 20375,
+ "umb": 20376,
+ "utb": 20377,
+ "uhb": 20378,
+ "ulc": 20379,
+ "uvc": 20380,
+ "umc": 20381,
+ "utc": 20382,
+ "uhc": 20383,
+ "uh9": 20384,
+ "ush": 20385,
+ "upk": 20386,
+ "dr9": 20387,
+ "dr7": 20388,
+ "dr8": 20389,
+ "dr6": 20390,
+ "dra": 20391,
+ "ba6": 20392,
+ "ba7": 20393,
+ "ba8": 20394,
+ "ba9": 20395,
+ "baa": 20396,
+ "pa6": 20397,
+ "pa7": 20398,
+ "pa8": 20399,
+ "pa9": 20400,
+ "paa": 20401,
+ "ne6": 20402,
+ "ne7": 20403,
+ "ne8": 20404,
+ "ne9": 20405,
+ "nea": 20406,
+ "dre": 20407,
+ "drc": 20408,
+ "drd": 20409,
+ "drb": 20410,
+ "drf": 20411,
+ "bab": 20412,
+ "bac": 20413,
+ "bad": 20414,
+ "bae": 20415,
+ "baf": 20416,
+ "pab": 20417,
+ "pac": 20418,
+ "pae": 20419,
+ "paf": 20420,
+ "neb": 20421,
+ "nec": 20422,
+ "ned": 20423,
+ "nee": 20424,
+ "nef": 20425,
+ "jew": 20433,
+ "cm1": 20435,
+ "cm2": 20436,
+ "cm3": 20437,
+ "Charmdes": 20438,
+ "ice": 20439,
+ "r33": 20440,
+ "r32": 20441,
+ "r31": 20442,
+ "r30": 20443,
+ "r29": 20444,
+ "r28": 20445,
+ "r27": 20446,
+ "r26": 20447,
+ "r25": 20448,
+ "r24": 20449,
+ "r23": 20450,
+ "r22": 20451,
+ "r21": 20452,
+ "r20": 20453,
+ "r19": 20454,
+ "r18": 20455,
+ "r17": 20456,
+ "r16": 20457,
+ "r15": 20458,
+ "r14": 20459,
+ "r13": 20460,
+ "r12": 20461,
+ "r11": 20462,
+ "r10": 20463,
+ "r09": 20464,
+ "r08": 20465,
+ "r07": 20466,
+ "r06": 20467,
+ "r05": 20468,
+ "r04": 20469,
+ "r03": 20470,
+ "r02": 20471,
+ "r01": 20472,
+ "r33L": 20473,
+ "r32L": 20474,
+ "r31L": 20475,
+ "r30L": 20476,
+ "r29L": 20477,
+ "r28L": 20478,
+ "r27L": 20479,
+ "r26L": 20480,
+ "r25L": 20481,
+ "r24L": 20482,
+ "r23L": 20483,
+ "r22L": 20484,
+ "r21L": 20485,
+ "r20L": 20486,
+ "r19L": 20487,
+ "r18L": 20488,
+ "r17L": 20489,
+ "r16L": 20490,
+ "r15L": 20491,
+ "r14L": 20492,
+ "r13L": 20493,
+ "r12L": 20494,
+ "r11L": 20495,
+ "r10L": 20496,
+ "r09L": 20497,
+ "r08L": 20498,
+ "r07L": 20499,
+ "r06L": 20500,
+ "r05L": 20501,
+ "r04L": 20502,
+ "r03L": 20503,
+ "r02L": 20504,
+ "r01L": 20505,
+ "RuneQuote": 20506,
+ "Runeword1": 20507,
+ "Runeword2": 20508,
+ "Runeword3": 20509,
+ "Runeword4": 20510,
+ "Runeword5": 20511,
+ "Runeword6": 20512,
+ "Runeword7": 20513,
+ "Runeword8": 20514,
+ "Runeword9": 20515,
+ "Runeword10": 20516,
+ "Runeword11": 20517,
+ "Runeword12": 20518,
+ "Runeword13": 20519,
+ "Runeword14": 20520,
+ "Runeword15": 20521,
+ "Runeword16": 20522,
+ "Runeword17": 20523,
+ "Runeword18": 20524,
+ "Runeword19": 20525,
+ "Runeword20": 20526,
+ "Runeword21": 20527,
+ "Runeword22": 20528,
+ "Runeword23": 20529,
+ "Runeword24": 20530,
+ "Runeword25": 20531,
+ "Runeword26": 20532,
+ "Runeword27": 20533,
+ "Runeword28": 20534,
+ "Runeword29": 20535,
+ "Runeword30": 20536,
+ "Runeword31": 20537,
+ "Runeword32": 20538,
+ "Runeword33": 20539,
+ "Runeword34": 20540,
+ "Runeword35": 20541,
+ "Runeword36": 20542,
+ "Runeword37": 20543,
+ "Runeword38": 20544,
+ "Runeword39": 20545,
+ "Runeword40": 20546,
+ "Runeword41": 20547,
+ "Runeword42": 20548,
+ "Runeword43": 20549,
+ "Runeword44": 20550,
+ "Runeword45": 20551,
+ "Runeword46": 20552,
+ "Runeword47": 20553,
+ "Runeword48": 20554,
+ "Runeword49": 20555,
+ "Runeword50": 20556,
+ "Runeword51": 20557,
+ "Runeword52": 20558,
+ "Runeword53": 20559,
+ "Runeword54": 20560,
+ "Runeword55": 20561,
+ "Runeword56": 20562,
+ "Runeword57": 20563,
+ "Runeword58": 20564,
+ "Runeword59": 20565,
+ "Runeword60": 20566,
+ "Runeword61": 20567,
+ "Runeword62": 20568,
+ "Runeword63": 20569,
+ "Runeword64": 20570,
+ "Runeword65": 20571,
+ "Runeword66": 20572,
+ "Runeword67": 20573,
+ "Runeword68": 20574,
+ "Runeword69": 20575,
+ "Runeword70": 20576,
+ "Runeword71": 20577,
+ "Runeword72": 20578,
+ "Runeword73": 20579,
+ "Runeword74": 20580,
+ "Runeword75": 20581,
+ "Runeword76": 20582,
+ "Runeword77": 20583,
+ "Runeword78": 20584,
+ "Runeword79": 20585,
+ "Runeword81": 20586,
+ "Runeword82": 20587,
+ "Runeword83": 20588,
+ "Runeword84": 20589,
+ "Runeword85": 20590,
+ "Runeword86": 20591,
+ "Runeword87": 20592,
+ "Runeword88": 20593,
+ "Runeword89": 20594,
+ "Runeword90": 20595,
+ "Runeword91": 20596,
+ "Runeword92": 20597,
+ "Runeword93": 20598,
+ "Runeword94": 20599,
+ "Runeword95": 20600,
+ "Runeword96": 20601,
+ "Runeword97": 20602,
+ "Runeword98": 20603,
+ "Runeword99": 20604,
+ "Runeword100": 20605,
+ "Runeword101": 20606,
+ "Runeword102": 20607,
+ "Runeword103": 20608,
+ "Runeword104": 20609,
+ "Runeword105": 20610,
+ "Runeword106": 20611,
+ "Runeword107": 20612,
+ "Runeword108": 20613,
+ "Runeword109": 20614,
+ "Runeword110": 20615,
+ "Runeword111": 20616,
+ "Runeword112": 20617,
+ "Runeword113": 20618,
+ "Runeword114": 20619,
+ "Runeword115": 20620,
+ "Runeword116": 20621,
+ "Runeword117": 20622,
+ "Runeword118": 20623,
+ "Runeword119": 20624,
+ "Runeword120": 20625,
+ "Runeword121": 20626,
+ "Runeword122": 20627,
+ "Runeword123": 20628,
+ "Runeword124": 20629,
+ "Runeword125": 20630,
+ "Runeword126": 20631,
+ "Runeword127": 20632,
+ "Runeword128": 20633,
+ "Runeword129": 20634,
+ "Runeword130": 20635,
+ "Runeword131": 20636,
+ "Runeword132": 20637,
+ "Runeword133": 20638,
+ "Runeword134": 20639,
+ "Runeword135": 20640,
+ "Runeword136": 20641,
+ "Runeword137": 20642,
+ "Runeword138": 20643,
+ "Runeword139": 20644,
+ "Runeword140": 20645,
+ "Runeword141": 20646,
+ "Runeword142": 20647,
+ "Runeword143": 20648,
+ "Runeword144": 20649,
+ "Runeword145": 20650,
+ "Runeword146": 20651,
+ "Runeword147": 20652,
+ "Runeword148": 20653,
+ "Runeword149": 20654,
+ "Runeword150": 20655,
+ "Runeword151": 20656,
+ "Runeword152": 20657,
+ "Runeword153": 20658,
+ "Runeword154": 20659,
+ "Runeword155": 20660,
+ "Runeword156": 20661,
+ "Runeword157": 20662,
+ "Runeword158": 20663,
+ "Runeword159": 20664,
+ "Runeword160": 20665,
+ "Runeword161": 20666,
+ "Runeword162": 20667,
+ "Runeword163": 20668,
+ "Runeword164": 20669,
+ "Runeword165": 20670,
+ "Runeword166": 20671,
+ "Runeword167": 20672,
+ "Runeword168": 20673,
+ "Runeword169": 20674,
+ "Runeword170": 20675,
+ "spe": 20676,
+ "scz": 20677,
+ "sol": 20678,
+ "qll": 20679,
+ "fng": 20680,
+ "flg": 20681,
+ "tal": 20682,
+ "hrn": 20683,
+ "eyz": 20684,
+ "jaw": 20685,
+ "brz": 20686,
+ "hrt": 20687,
+ "Stout": 20688,
+ "Antimagic": 20689,
+ "Null": 20690,
+ "Godly": 20691,
+ "Ivory": 20692,
+ "Eburin": 20693,
+ "Blanched": 20694,
+ "Stalwart": 20695,
+ "Burly": 20696,
+ "Dense": 20697,
+ "Thin": 20698,
+ "Compact": 20699,
+ "Witch-hunter's": 20700,
+ "Magekiller's": 20701,
+ "Hierophant's": 20702,
+ "Shaman's": 20703,
+ "Pestilent": 20704,
+ "Toxic": 20705,
+ "Corosive": 20706,
+ "Envenomed": 20707,
+ "Septic": 20708,
+ "Shocking": 20709,
+ "Arcing": 20710,
+ "Buzzing": 20711,
+ "Static": 20712,
+ "Scorching": 20713,
+ "Flaming": 20714,
+ "Smoking": 20715,
+ "Smoldering": 20716,
+ "Ember": 20717,
+ "Hibernal": 20718,
+ "Boreal": 20719,
+ "Shivering": 20720,
+ "Snowflake": 20721,
+ "Mnemonic": 20722,
+ "Visionary": 20723,
+ "Eagleeye": 20724,
+ "Hawkeye": 20725,
+ "Falconeye": 20726,
+ "Sparroweye": 20727,
+ "Robineye": 20728,
+ "Paradox": 20729,
+ "Shouting": 20730,
+ "Yelling": 20731,
+ "Calling": 20732,
+ "Loud": 20733,
+ "Trump": 20734,
+ "Joker's": 20735,
+ "Jester's": 20736,
+ "Jack's": 20737,
+ "Knave's": 20738,
+ "Paleocene": 20739,
+ "Eocene": 20740,
+ "Oligocene": 20741,
+ "Miocene": 20742,
+ "Kenshi's": 20743,
+ "Sensei's": 20744,
+ "Shogukusha's": 20745,
+ "Psychic": 20746,
+ "Mentalist's": 20747,
+ "Cunning": 20748,
+ "Trickster's": 20749,
+ "Entrapping": 20750,
+ "Gaea's": 20751,
+ "Terra's": 20752,
+ "Nature's": 20753,
+ "Communal": 20754,
+ "Feral": 20755,
+ "Spiritual": 20756,
+ "Keeper's": 20757,
+ "Caretaker's": 20758,
+ "Trainer's": 20759,
+ "Veteran's": 20760,
+ "Expert's": 20761,
+ "Furious": 20762,
+ "Raging": 20763,
+ "Echoing": 20764,
+ "Resonant": 20765,
+ "Sounding": 20766,
+ "Guardian's": 20767,
+ "Warder's": 20768,
+ "Preserver's": 20769,
+ "Marshal's": 20770,
+ "Commander's": 20771,
+ "Captain's": 20772,
+ "Rose Branded": 20773,
+ "Hawk Branded": 20774,
+ "Lion Branded": 20775,
+ "Golemlord's": 20776,
+ "Vodoun": 20777,
+ "Graverobber's": 20778,
+ "Venomous": 20779,
+ "Noxious": 20780,
+ "Fungal": 20781,
+ "Accursed": 20782,
+ "Blighting": 20783,
+ "Hexing": 20784,
+ "Glacial": 20785,
+ "Freezing": 20786,
+ "Chilling": 20787,
+ "Powered": 20788,
+ "Charged": 20789,
+ "Sparking": 20790,
+ "Volcanic": 20791,
+ "Blazing": 20792,
+ "Burning": 20793,
+ "Lancer's": 20794,
+ "Spearmaiden's": 20795,
+ "Harpoonist's": 20796,
+ "Athlete's": 20797,
+ "Gymnast's": 20798,
+ "Acrobat's": 20799,
+ "Bowyer's": 20800,
+ "Diamond": 20801,
+ "Celestial": 20802,
+ "Elysian": 20803,
+ "Astral": 20804,
+ "Unearthly": 20805,
+ "Arcadian": 20806,
+ "Jeweler's": 20807,
+ "Artificer's": 20808,
+ "Mechanist's": 20809,
+ "Aureolin": 20810,
+ "Victorious": 20811,
+ "Ambergris": 20812,
+ "Camphor": 20813,
+ "Lapis Lazuli": 20814,
+ "Chromatic": 20815,
+ "Scintillating": 20816,
+ "Turquoise": 20817,
+ "Jacinth": 20818,
+ "Zircon": 20819,
+ "Bahamut's": 20820,
+ "Great Wyrm's": 20821,
+ "Felicitous": 20822,
+ "Lucky": 20823,
+ "Wailing": 20824,
+ "Screaming": 20825,
+ "Grandmaster's": 20826,
+ "Master's": 20827,
+ "Argent": 20828,
+ "Tin": 20829,
+ "Nickel": 20830,
+ "Maroon": 20831,
+ "Chestnut": 20832,
+ "Vigorous": 20833,
+ "Brown": 20834,
+ "Dun": 20835,
+ "Realgar": 20836,
+ "Rusty": 20837,
+ "Cinnabar": 20838,
+ "Vermillion": 20839,
+ "Carmine": 20840,
+ "Carbuncle": 20841,
+ "Serrated": 20842,
+ "Scarlet": 20843,
+ "Bloody": 20844,
+ "Sanguinary": 20845,
+ "Pearl": 20846,
+ "Divine": 20847,
+ "Hallowed": 20848,
+ "Sacred": 20849,
+ "Pure": 20850,
+ "Consecrated": 20851,
+ "Assamic": 20852,
+ "Frantic": 20853,
+ "Hellatial": 20854,
+ "Quixotic": 20855,
+ "Smiting": 20856,
+ "Steller": 20857,
+ "Stinging": 20858,
+ "Singing": 20859,
+ "Timeless": 20860,
+ "Original": 20861,
+ "Corporal": 20862,
+ "Lawful": 20863,
+ "Chaotic": 20864,
+ "Fierce": 20865,
+ "Ferocious": 20866,
+ "Perpetual": 20867,
+ "Continuous": 20868,
+ "Laden": 20869,
+ "Pernicious": 20870,
+ "Harmful": 20871,
+ "Evil": 20872,
+ "Insidious": 20873,
+ "Malicious": 20874,
+ "Spiteful": 20875,
+ "Precocious": 20876,
+ "Majestic": 20877,
+ "Sanguine": 20878,
+ "Monumental": 20879,
+ "Irresistible": 20880,
+ "Festering": 20881,
+ "Musty": 20882,
+ "Dusty": 20883,
+ "Decaying": 20884,
+ "Rotting": 20885,
+ "Infectious": 20886,
+ "Foggy": 20887,
+ "Cloudy": 20888,
+ "Hazy": 20889,
+ "Punishing": 20890,
+ "Obsidian": 20891,
+ "Royal": 20892,
+ "Frigid": 20893,
+ "Moldy": 20894,
+ "Gaudy": 20895,
+ "Impecable": 20896,
+ "Soulless": 20897,
+ "Heated": 20898,
+ "Lasting": 20899,
+ "Scorched": 20900,
+ "Marred": 20901,
+ "Lilac": 20902,
+ "Rose": 20903,
+ "Shimmering": 20904,
+ "Wicked": 20906,
+ "Strange": 20907,
+ "Repulsive": 20908,
+ "Reclusive": 20909,
+ "Rude": 20911,
+ "Hermetic": 20912,
+ "Rainbow": 20913,
+ "Colorful": 20914,
+ "Stinky": 20915,
+ "Gritty": 20916,
+ "of Warming": 20917,
+ "of Stoicism": 20918,
+ "of the Dynamo": 20919,
+ "of Grounding": 20920,
+ "of Insulation": 20921,
+ "of Resistance": 20922,
+ "of Faith": 20923,
+ "of Fire Quenching": 20924,
+ "of Amianthus": 20925,
+ "of Incombustibility": 20926,
+ "of Coolness": 20927,
+ "of Anima": 20928,
+ "of Life Everlasting": 20929,
+ "of Sunlight": 20930,
+ "of Frozen Orb": 20931,
+ "of Hydra Shield": 20932,
+ "of Chilling Armor": 20933,
+ "of Blizzard": 20934,
+ "of Energy Shield": 20935,
+ "of Thunder Storm": 20936,
+ "of Meteor": 20937,
+ "of Glacial Spike": 20938,
+ "of Teleport Shield": 20939,
+ "of Chain Lightning": 20940,
+ "of Enchant": 20941,
+ "of Fire Wall": 20942,
+ "of Shiver Armor": 20943,
+ "of Nova Shield": 20944,
+ "of Nova": 20945,
+ "of Fire Ball": 20946,
+ "of Blaze": 20947,
+ "of Ice Blast": 20948,
+ "of Frost Shield": 20949,
+ "of Telekinesis": 20950,
+ "of Static Field": 20951,
+ "of Frozen Armor": 20952,
+ "of Icebolt": 20953,
+ "of Charged Shield": 20954,
+ "of Firebolts": 20955,
+ "of the Elements": 20956,
+ "of the Cobra": 20957,
+ "of the Efreeti": 20958,
+ "of the Phoenix": 20959,
+ "of the Yeti": 20960,
+ "of Grace and Power": 20961,
+ "of Grace": 20962,
+ "of Power": 20963,
+ "of the Elephant": 20964,
+ "of Memory": 20965,
+ "of the Kraken1": 20966,
+ "of Propogation": 20967,
+ "of Replenishing": 20968,
+ "of Ages": 20969,
+ "of Fast Repair": 20970,
+ "of Self-Repair": 20971,
+ "of Acceleration": 20972,
+ "of Traveling": 20973,
+ "of Virility": 20974,
+ "of Atlus": 20975,
+ "of Freedom": 20976,
+ "of the Lamprey": 20977,
+ "of Hope": 20978,
+ "of Spirit": 20979,
+ "of Vita": 20980,
+ "of Substinence": 20981,
+ "of the Whale": 20982,
+ "of the Squid": 20983,
+ "of the Colossus1": 20984,
+ "of Knowledge": 20985,
+ "of Enlightenment": 20986,
+ "of Prosperity": 20987,
+ "of Good Luck": 20988,
+ "of Luck": 20989,
+ "of Avarice": 20990,
+ "of Honor": 20991,
+ "of Revivification": 20992,
+ "of Truth": 20993,
+ "of Daring": 20994,
+ "of Nirvana": 20995,
+ "of Envy": 20996,
+ "of Anthrax": 20997,
+ "of Bliss": 20998,
+ "of Joy": 20999,
+ "of Transcendence": 21000,
+ "of Wrath": 21001,
+ "of Ire": 21002,
+ "of Evisceration": 21003,
+ "of Butchery": 21004,
+ "of Ennui": 21005,
+ "of Storms": 21006,
+ "of Passion": 21007,
+ "of Incineration": 21008,
+ "of Frigidity": 21009,
+ "of Winter": 21010,
+ "of the Icicle": 21011,
+ "of Fervor": 21012,
+ "of Malice": 21013,
+ "of Swords": 21014,
+ "of Razors": 21015,
+ "of Desire": 21016,
+ "of the Sirocco": 21017,
+ "of the Dunes": 21018,
+ "of Thawing": 21019,
+ "Of the Choir": 21020,
+ "Of the Sniper": 21021,
+ "Of the Stiletto": 21022,
+ "Of Bile": 21023,
+ "Of Blitzen": 21024,
+ "Of Cremation": 21025,
+ "Of Darkness": 21026,
+ "Of Disease": 21027,
+ "Of Remorse": 21028,
+ "Of Terror": 21029,
+ "Of the Sky": 21030,
+ "Of Valhalla": 21031,
+ "Of Waste": 21032,
+ "Of Nobility": 21033,
+ "Of Karma": 21034,
+ "Of Grounding": 21035,
+ "Of the River": 21036,
+ "Of the Lake": 21037,
+ "Of the Ocean": 21038,
+ "Of the Bayou": 21039,
+ "Of the Stream": 21040,
+ "Of the Lady": 21041,
+ "Of the Maiden": 21042,
+ "Of the Virgin": 21043,
+ "Of the Hag": 21044,
+ "Of the Witch": 21045,
+ "Of Judgement": 21046,
+ "Of Illusion": 21047,
+ "Of Elusion": 21048,
+ "Of Combat": 21049,
+ "Of Attrition": 21050,
+ "Of Abrasion": 21051,
+ "Of Erosion": 21052,
+ "Of Searing": 21053,
+ "Of Stone": 21054,
+ "Of Stature": 21055,
+ "Of Fortication": 21056,
+ "Of Quickening": 21057,
+ "Of Dispatch": 21058,
+ "Of Daring": 21059,
+ "Of Dread": 21060,
+ "Of Suffering": 21061,
+ "Of Doom": 21062,
+ "Of Vengence": 21063,
+ "Of Redemption": 21064,
+ "Of Luck": 21065,
+ "Of the Avenger": 21066,
+ "Of the Specter": 21067,
+ "Of the Ghost": 21068,
+ "Of the Infantry": 21069,
+ "Of the Mosquito": 21070,
+ "Of the Gnat": 21071,
+ "Of the Fly": 21072,
+ "Of the Plague": 21073,
+ "Of Twilight": 21074,
+ "Of Dusk": 21075,
+ "Of Dawn": 21076,
+ "Of the Imbecile": 21077,
+ "Of the Idiot": 21078,
+ "Of the Retard": 21079,
+ "Of the Jujube": 21080,
+ "Of the Obscenity": 21081,
+ "Of Quota": 21082,
+ "Of the Maggot": 21083,
+ "Of Horror": 21084,
+ "Of Baddass": 21085,
+ "Of the Beast": 21086,
+ "Of Cruelty": 21087,
+ "Of Badness": 21088,
+ "Of the Horde": 21089,
+ "Of the Forest": 21090,
+ "Of the Lilly": 21091,
+ "Of the Grassy Gnoll": 21092,
+ "Of the Stars": 21093,
+ "Of the Moon": 21094,
+ "Of Love": 21095,
+ "Of the Unicorn": 21096,
+ "Of the Walrus": 21097,
+ "Of the Earth": 21098,
+ "Of Vines": 21099,
+ "Of Honor": 21100,
+ "Of Tribute": 21101,
+ "Of Credit": 21102,
+ "Of Admiration": 21103,
+ "Of Sweetness": 21104,
+ "Of Beauty": 21105,
+ "Of Pilfering": 21106,
+ "of Damage Amplification": 21107,
+ "of Hurricane": 21108,
+ "of Armageddon": 21109,
+ "of Tornado": 21110,
+ "of Volcano": 21111,
+ "of Twister": 21112,
+ "of Cyclone Armor": 21113,
+ "of Eruption": 21114,
+ "of Molten Boulders": 21115,
+ "of Firestorms": 21116,
+ "of Battle Command": 21117,
+ "of War Cry": 21118,
+ "of Grim Ward": 21119,
+ "of Battle Orders": 21120,
+ "of Battle Cry": 21121,
+ "of Concentration": 21122,
+ "of Item Finding": 21123,
+ "of Stunning": 21124,
+ "of Shouting": 21125,
+ "of Taunting": 21126,
+ "of Potion Finding": 21127,
+ "of Howling": 21128,
+ "of Fist of the Heavens": 21129,
+ "of Holy Shield": 21130,
+ "of Conversion": 21131,
+ "of Blessed Hammers": 21132,
+ "of Vengeance": 21133,
+ "of Charging": 21134,
+ "of Zeal": 21135,
+ "of Holy Bolts": 21136,
+ "of Sacrifice": 21137,
+ "of Fire Golem Summoning": 21138,
+ "of Bone Spirits": 21139,
+ "of Poison Novas": 21140,
+ "of Lower Resistance": 21141,
+ "of Iron Golem Creation": 21142,
+ "of Bone Imprisonment": 21143,
+ "of Decrepification": 21144,
+ "of Attraction": 21145,
+ "of Blood Golem Summoning": 21146,
+ "of Bone Spears": 21147,
+ "of Poison Explosion": 21148,
+ "of Life Tap": 21149,
+ "of Confusion": 21150,
+ "of Raise Skeletal Mages": 21151,
+ "of Bone Walls": 21152,
+ "of Terror": 21153,
+ "of Iron Maiden": 21154,
+ "of Clay Golem Summoning": 21155,
+ "of Corpse Explosions": 21156,
+ "of Poison Dagger": 21157,
+ "of Weaken": 21158,
+ "of Dim Vision": 21159,
+ "of Raise Skeletons": 21160,
+ "of Bone Armor": 21161,
+ "of Teeth": 21162,
+ "of Amplify Damage": 21163,
+ "of Frozen Orbs": 21164,
+ "of Hydras": 21165,
+ "of Blizzards": 21166,
+ "of Meteors": 21167,
+ "of Glacial Spikes": 21168,
+ "of Teleportation": 21169,
+ "of Enchantment": 21170,
+ "of Fire Walls": 21171,
+ "of Novas": 21172,
+ "of Fire Balls": 21173,
+ "of Blazing": 21174,
+ "of Ice Blasts": 21175,
+ "of Frost Novas": 21176,
+ "of Ice Bolts": 21177,
+ "of Charged Bolts": 21178,
+ "of Fire Bolts": 21179,
+ "of Lightning Fury": 21180,
+ "of Lightning Spear": 21181,
+ "of Freezing Arrows": 21182,
+ "of Fending": 21183,
+ "of Immolating Arrows": 21184,
+ "of Plague Javelin": 21185,
+ "of Charged Spear": 21186,
+ "of Guided Arrows": 21187,
+ "of Ice Arrows": 21188,
+ "of Lightning Javelin": 21189,
+ "of Impaling Spear": 21190,
+ "of Slow Missiles": 21191,
+ "of Exploding Arrows": 21192,
+ "of Poison Javelin": 21193,
+ "of Power Spear": 21194,
+ "of Multiple Shot": 21195,
+ "of Cold Arrows": 21196,
+ "of Jabbing": 21197,
+ "of Inner Sight": 21198,
+ "of Fire Arrows": 21199,
+ "of Magic Arrows": 21200,
+ "Of self-repair": 21201,
+ "of Dawn": 21202,
+ "of Inertia": 21203,
+ "of Joyfulness": 21204,
+ "ModStre8a": 21205,
+ "ModStre8b": 21206,
+ "ModStre8c": 21207,
+ "ModStre8d": 21208,
+ "ModStre8e": 21209,
+ "ModStre8f": 21210,
+ "ModStre8g": 21211,
+ "ModStre8h": 21212,
+ "ModStre8i": 21213,
+ "ModStre8j": 21214,
+ "ModStre8k": 21215,
+ "ModStre8l": 21216,
+ "ModStre8m": 21217,
+ "ModStre8n": 21218,
+ "ModStre8o": 21219,
+ "ModStre8p": 21220,
+ "ModStre8q": 21221,
+ "ModStre8r": 21222,
+ "ModStre8s": 21223,
+ "ModStre8t": 21224,
+ "ModStre8u": 21225,
+ "ModStre8v": 21226,
+ "ModStre8w": 21227,
+ "ModStre8x": 21228,
+ "ModStre8y": 21229,
+ "ModStre8z": 21230,
+ "ModStre9a": 21231,
+ "ModStre9b": 21232,
+ "ModStre9c": 21233,
+ "ModStre9d": 21234,
+ "ModStre9e": 21235,
+ "ModStre9f": 21236,
+ "ModStre9g": 21237,
+ "ModStre9h": 21238,
+ "ModStre9i": 21239,
+ "ModStre9s": 21240,
+ "ModStre9t": 21241,
+ "ModStre9u": 21242,
+ "ModStre9v": 21243,
+ "ModStre9w": 21244,
+ "ModStre9x": 21245,
+ "ModStre9y": 21246,
+ "ModStre9z": 21247,
+ "ModStre10a": 21248,
+ "ModStre10b": 21249,
+ "ModStre10c": 21250,
+ "ModStre10d": 21251,
+ "ModStre10e": 21252,
+ "ModStre10f": 21253,
+ "ModStre10g": 21254,
+ "ModStre10h": 21255,
+ "ModStre10i": 21256,
+ "ModStre10j": 21257,
+ "WeaponDescOrb": 21259,
+ "ItemexpED": 21260,
+ "StrGemX1": 21261,
+ "StrGemX2": 21262,
+ "StrGemX3": 21263,
+ "StrGemX4": 21264,
+ "GemeffectX11": 21265,
+ "GemeffectX12": 21266,
+ "GemeffectX13": 21267,
+ "GemeffectX21": 21268,
+ "GemeffectX22": 21269,
+ "GemeffectX23": 21270,
+ "GemeffectX31": 21271,
+ "GemeffectX32": 21272,
+ "GemeffectX33": 21273,
+ "GemeffectX41": 21274,
+ "GemeffectX42": 21275,
+ "GemeffectX43": 21276,
+ "GemeffectX51": 21277,
+ "GemeffectX52": 21278,
+ "GemeffectX53": 21279,
+ "GemeffectX61": 21280,
+ "GemeffectX62": 21281,
+ "GemeffectX63": 21282,
+ "GemeffectX71": 21283,
+ "GemeffectX72": 21284,
+ "GemeffectX73": 21285,
+ "Coldkill": 21286,
+ "Butchers Cleaver": 21287,
+ "Butcher's Pupil": 21288,
+ "Islestrike": 21289,
+ "Pompe's Wrath": 21290,
+ "Guardian Naga": 21291,
+ "Warlord's Trust": 21292,
+ "Spellsteel": 21293,
+ "Stormrider": 21294,
+ "Boneslayer Blade": 21295,
+ "The Minotaur": 21296,
+ "Suicide Branch": 21297,
+ "Cairn Shard": 21298,
+ "Arm of King Leoric": 21299,
+ "Blackhand Key": 21300,
+ "Dark Clan Crusher": 21301,
+ "Drulan's Tongue": 21302,
+ "Zakrum's Hand": 21303,
+ "The Fetid Sprinkler": 21304,
+ "Hand of Blessed Light": 21305,
+ "Fleshrender": 21306,
+ "Sureshrill Frost": 21307,
+ "Moonfall": 21308,
+ "Baezils Vortex": 21309,
+ "Earthshaker": 21310,
+ "Bloodtree Stump": 21311,
+ "The Gavel of Pain": 21312,
+ "Bloodletter": 21313,
+ "Coldsteal Eye": 21314,
+ "Hexfire": 21315,
+ "Blade of Ali Baba": 21316,
+ "Riftslash": 21317,
+ "Headstriker": 21318,
+ "Plague Bearer": 21319,
+ "The Atlantien": 21320,
+ "Crainte Vomir": 21321,
+ "Bing Sz Wang": 21322,
+ "The Vile Husk": 21323,
+ "Cloudcrack": 21324,
+ "Todesfaelle Flamme": 21325,
+ "Swordguard": 21326,
+ "Spineripper": 21327,
+ "Heart Carver": 21328,
+ "Blackbog's Sharp": 21329,
+ "Stormspike": 21330,
+ "The Impaler": 21331,
+ "Kelpie Snare": 21332,
+ "Soulfeast Tine": 21333,
+ "Hone Sundan": 21334,
+ "Spire of Honor": 21335,
+ "The Meat Scraper": 21336,
+ "Blackleach Blade": 21337,
+ "Athena's Wrath": 21338,
+ "Pierre Tombale Couant": 21339,
+ "Husoldal Evo": 21340,
+ "Grim's Burning Dead": 21341,
+ "Ribcracker": 21342,
+ "Chromatic Ire": 21343,
+ "Warpspear": 21344,
+ "Skullcollector": 21345,
+ "Skystrike": 21346,
+ "Kuko Shakaku": 21347,
+ "Endlessshail": 21348,
+ "Whichwild String": 21349,
+ "Godstrike Arch": 21350,
+ "Langer Briser": 21351,
+ "Pus Spiter": 21352,
+ "Buriza-Do Kyanon": 21353,
+ "Vampiregaze": 21354,
+ "String of Ears": 21355,
+ "Gorerider": 21356,
+ "Lavagout": 21357,
+ "Venom Grip": 21358,
+ "Visceratuant": 21359,
+ "Guardian Angle": 21360,
+ "Shaftstop": 21361,
+ "Skin of the Vipermagi": 21362,
+ "Blackhorn": 21363,
+ "Valkiry Wing": 21364,
+ "Peasent Crown": 21365,
+ "Demon Machine": 21366,
+ "Magewrath": 21367,
+ "Cliffkiller": 21368,
+ "Riphook": 21369,
+ "Razorswitch": 21370,
+ "Meatscrape": 21371,
+ "Coldsteel Eye": 21372,
+ "Pitblood Thirst": 21373,
+ "Gaya Wand": 21374,
+ "Ondal's Wisdom": 21375,
+ "Geronimo's Fury": 21376,
+ "Charsi's Favor": 21377,
+ "Doppleganger's Shadow": 21378,
+ "Deathbit": 21379,
+ "Warshrike": 21380,
+ "Gutsiphon": 21381,
+ "Razoredge": 21382,
+ "Stonerattle": 21383,
+ "Marrowgrinder": 21384,
+ "Gore Ripper": 21385,
+ "Bush Wacker": 21386,
+ "Demonlimb": 21387,
+ "Steelshade": 21388,
+ "Tomb Reaver": 21389,
+ "Death's Web": 21390,
+ "Gaia's Wrath": 21391,
+ "Khalim's Vengance": 21392,
+ "Angel's Song": 21393,
+ "The Reedeemer": 21394,
+ "Fleshbone": 21395,
+ "Odium": 21396,
+ "Blood Comet": 21397,
+ "Bonehew": 21398,
+ "Steelrend": 21399,
+ "Stone Crusher": 21400,
+ "Bul-Kathos' Might": 21401,
+ "Arioc's Needle": 21402,
+ "Shadowdancer": 21403,
+ "Indiego's Fancy": 21404,
+ "Aladdin's Eviserator": 21405,
+ "Tyrael's Mercy": 21406,
+ "Souldrain": 21407,
+ "Runemaster": 21408,
+ "Deathcleaver": 21409,
+ "Executioner's Justice": 21410,
+ "Wallace's Tear": 21411,
+ "Leviathan": 21412,
+ "The Wanderer's Blade": 21413,
+ "Qual'Kek's Enforcer": 21414,
+ "Dawnbringer": 21415,
+ "Dragontooth": 21416,
+ "Wisp": 21417,
+ "Gargoyle's Bite": 21418,
+ "Lacerator": 21419,
+ "Mang Song's Lesson": 21420,
+ "Viperfork": 21421,
+ "Blood Chalice": 21422,
+ "El Espiritu": 21423,
+ "The Long Rod": 21424,
+ "Demonhorn's Edge": 21425,
+ "The Ensanguinator": 21426,
+ "The Reaper's Toll": 21427,
+ "Spiritkeeper": 21428,
+ "Hellrack": 21429,
+ "Alma Negra": 21430,
+ "Darkforge Spawn": 21431,
+ "Rockhew": 21432,
+ "Sankenkur's Resurrection": 21433,
+ "Erion's Bonehandle": 21434,
+ "The Archon Magus": 21435,
+ "Widow maker": 21436,
+ "Catgut": 21437,
+ "Ghostflame": 21438,
+ "Shadowkiller": 21439,
+ "Bling Bling": 21440,
+ "Nebucaneezer's Storm": 21441,
+ "Griffon's Eye": 21442,
+ "Eaglewind": 21443,
+ "Windhammer": 21444,
+ "Thunderstroke": 21445,
+ "Giantmaimer": 21446,
+ "Demon's Arch": 21447,
+ "The Scalper": 21448,
+ "Bloodmoon": 21449,
+ "Djinnslayer": 21450,
+ "Cranebeak": 21451,
+ "Iansang's Frenzy": 21452,
+ "Warhound": 21453,
+ "Gulletwound": 21454,
+ "Headhunter's Glory": 21455,
+ "Mordoc's marauder": 21456,
+ "Talberd's Law": 21457,
+ "Amodeus's Manipulator": 21458,
+ "Darksoul": 21459,
+ "The Black Adder": 21460,
+ "Earthshifter": 21461,
+ "Nature's Peace": 21462,
+ "Horazon's Chalice": 21463,
+ "Seraph's Hymn": 21464,
+ "Zakarum's Salvation": 21465,
+ "Fleshripper": 21466,
+ "Stonerage": 21467,
+ "Blood Rain": 21468,
+ "Horizon's Tornado": 21469,
+ "Nord's Tenderizer": 21470,
+ "Wrath of Cain": 21471,
+ "Siren's call": 21472,
+ "Jadetalon": 21473,
+ "Wraithfang": 21474,
+ "Blademaster": 21475,
+ "Cerebus": 21476,
+ "Archangel's Deliverance": 21477,
+ "Sinblade": 21478,
+ "Runeslayer": 21479,
+ "Excalibur": 21480,
+ "Fuego Del Sol": 21481,
+ "Stoneraven": 21482,
+ "El Infierno": 21483,
+ "Moonrend": 21484,
+ "Larzuk's Champion": 21485,
+ "Nightsummon": 21486,
+ "Bonescapel": 21487,
+ "Rabbit Slayer": 21488,
+ "Pagan's Athame": 21489,
+ "The Swashbuckler": 21490,
+ "Kang's Virtue": 21491,
+ "Snaketongue": 21492,
+ "Lifechoke": 21493,
+ "Ethereal edge": 21494,
+ "Palo Grande": 21495,
+ "Carnageleaver": 21496,
+ "Ghostleach": 21497,
+ "Soulreaper": 21498,
+ "Samual's Caretaker": 21499,
+ "Hell's Whisper": 21500,
+ "The Harvester": 21501,
+ "Raiden's Crutch": 21502,
+ "The TreeEnt": 21503,
+ "Stormwillow": 21504,
+ "Moonshadow": 21505,
+ "Strongoak": 21506,
+ "Demonweb": 21507,
+ "Bloodraven's Charge": 21508,
+ "Shadefalcon": 21509,
+ "Robin's Yolk": 21510,
+ "Glimmershred": 21511,
+ "Wraithflight": 21512,
+ "Lestron's Mark": 21513,
+ "Banshee's Wail": 21514,
+ "Windstrike": 21515,
+ "Medusa's Gaze": 21516,
+ "Titanfist": 21517,
+ "Hadeshorn": 21518,
+ "Rockstopper": 21519,
+ "Stealskull": 21520,
+ "Darksight Helm": 21521,
+ "Crown of Thieves": 21522,
+ "Blackhorn's Face": 21523,
+ "The Spirit Shroud": 21524,
+ "Skin of the Flayed One": 21525,
+ "Ironpelt": 21526,
+ "Spiritforge": 21527,
+ "Crow Caw": 21528,
+ "Duriel's Shell": 21529,
+ "Skullder's Ire": 21530,
+ "Toothrow": 21531,
+ "Atma's Wail": 21532,
+ "Black Hades": 21533,
+ "Corpsemourn": 21534,
+ "Que-hegan's Wisdom": 21535,
+ "Moser's Blessed Circle": 21536,
+ "Stormchaser": 21537,
+ "Tiamat's Rebuke": 21538,
+ "Gerke's Sanctuary": 21539,
+ "Radimant's Sphere": 21540,
+ "Gravepalm": 21541,
+ "Ghoulhide": 21542,
+ "Hellmouth": 21543,
+ "Infernostride": 21544,
+ "Waterwalk": 21545,
+ "Silkweave": 21546,
+ "Wartraveler": 21547,
+ "Razortail": 21548,
+ "Gloomstrap": 21549,
+ "Snowclash": 21550,
+ "Thudergod's Vigor": 21551,
+ "Lidless Wall": 21552,
+ "Lanceguard": 21553,
+ "Squire's Cover": 21554,
+ "Boneflame": 21555,
+ "Steelpillar": 21556,
+ "Nightwing's Veil": 21557,
+ "Hightower's Watch": 21558,
+ "Crown of Ages": 21559,
+ "Andariel's Visage": 21560,
+ "Darkfear": 21561,
+ "Dragonscale": 21562,
+ "Steel Carapice": 21563,
+ "Ashrera's Wired Frame": 21564,
+ "Rainbow Facet": 21565,
+ "Ravenlore": 21566,
+ "Boneshade": 21567,
+ "Nethercrow": 21568,
+ "Hellwarden's Husk": 21569,
+ "Flamebellow": 21570,
+ "Fathom": 21571,
+ "Wolfhowl": 21572,
+ "Spirit Ward": 21573,
+ "Kira's Guardian": 21574,
+ "Orumus' Robes": 21575,
+ "Gheed's Fortune": 21576,
+ "The Vicar": 21577,
+ "Stormlash": 21578,
+ "Halaberd's Reign": 21579,
+ "Parkersor's Calm": 21580,
+ "Warriv's Warder": 21581,
+ "Spike Thorn": 21582,
+ "Dracul's Grasp": 21583,
+ "Frostwind": 21584,
+ "Templar's Might": 21585,
+ "Eschuta's temper": 21620,
+ "Firelizard's Talons": 21587,
+ "Sandstorm Trek": 21588,
+ "Marrowwalk": 21589,
+ "Heaven's Light": 21590,
+ "Merman's Speed": 21591,
+ "Arachnid Mesh": 21592,
+ "Nosferatu's Coil": 21593,
+ "Metalgird": 21594,
+ "Verdugo's Hearty Cord": 21595,
+ "Sigurd's Staunch": 21596,
+ "Carrion Wind": 21597,
+ "Giantskull": 21598,
+ "Ironward": 21599,
+ "Gillian's Brazier": 21600,
+ "Drakeflame": 21601,
+ "Dust Storm": 21602,
+ "Skulltred": 21603,
+ "Alma's Reflection": 21604,
+ "Drulan's Tounge": 21605,
+ "Sacred Charge": 21606,
+ "Bul-Kathos": 21607,
+ "Saracen's Chance": 21608,
+ "Highlord's Wrath": 21609,
+ "Raven Frost": 21610,
+ "Dwarf Star": 21611,
+ "Atma's Scarab": 21612,
+ "Mara's Kaleidoscope": 21613,
+ "Crescent Moon": 21614,
+ "The Rising Sun": 21615,
+ "The Cat's Eye": 21616,
+ "Bul Katho's Wedding Band": 21617,
+ "Rings": 21618,
+ "Metalgrid": 21619,
+ "Stormshield": 21621,
+ "Blackoak Shield": 21622,
+ "Ormus' Robes": 21623,
+ "Arkaine's Valor": 21624,
+ "The Gladiator's Bane": 21625,
+ "Veil of Steel": 21626,
+ "Harlequin Crest": 21627,
+ "Lance Guard": 21628,
+ "Kerke's Sanctuary": 21629,
+ "Mosers Blessed Circle": 21630,
+ "Que-Hegan's Wisdon": 21631,
+ "Guardian Angel": 21632,
+ "Skin of the Flayerd One": 21633,
+ "Armor": 21634,
+ "Windforce": 21635,
+ "Eaglehorn": 21636,
+ "Gimmershred": 21637,
+ "Widowmaker": 21638,
+ "Stormspire": 21639,
+ "Naj's Puzzler": 21640,
+ "Ethereal Edge": 21641,
+ "Wizardspike": 21642,
+ "The Grandfather": 21643,
+ "Doombringer": 21644,
+ "Tyrael's Might": 21645,
+ "Lightsabre": 21646,
+ "The Cranium Basher": 21647,
+ "Schaefer's Hammer": 21648,
+ "Baranar's Star": 21649,
+ "Deaths's Web": 21650,
+ "Messerschmidt's Reaver": 21651,
+ "Hellslayer": 21652,
+ "Endlesshail": 21653,
+ "The Atlantian": 21654,
+ "Riftlash": 21655,
+ "Baezil's Vortex": 21656,
+ "Zakarum's Hand": 21657,
+ "Carin Shard": 21658,
+ "The Minataur": 21659,
+ "Trang-Oul's Avatar": 21660,
+ "Trang-Oul's Guise": 21661,
+ "Trang-Oul's Wing": 21662,
+ "Trang-Oul's Mask": 21663,
+ "Trang-Oul's Scales": 21664,
+ "Trang-Oul's Claws": 21665,
+ "Trang-Oul's Girth": 21666,
+ "Natalya's Odium": 21667,
+ "Natalya's Totem": 21668,
+ "Natalya's Mark": 21669,
+ "Natalya's Shadow": 21670,
+ "Natalya's Soul": 21671,
+ "Griswold's Legacy": 21672,
+ "Griswolds's Redemption": 21673,
+ "Griswold's Honor": 21674,
+ "Griswold's Heart": 21675,
+ "Griswold's Valor": 21676,
+ "Tang's Imperial Robes": 21677,
+ "Tang's Fore-Fathers": 21678,
+ "Tang's Rule": 21679,
+ "Tang's Throne": 21680,
+ "Tang's Battle Standard": 21681,
+ "Ogun's Fierce Visage": 21682,
+ "Ogun's Shadow": 21683,
+ "Ogun's Lash": 21684,
+ "Ogun's Vengeance": 21685,
+ "Bul-Kathos' Warden": 21686,
+ "Bul-Kathos' Children": 21687,
+ "Bul-Kathos' Sacred Charge": 21688,
+ "Bul-Kathos' Tribal Guardian": 21689,
+ "Bul-Kathos' Custodian": 21690,
+ "Flowkrad's Howl": 21691,
+ "Flowkrad's Grin": 21692,
+ "Flowkrad's Fur": 21693,
+ "Flowkrad's Paws": 21694,
+ "Flowkrad's Sinew": 21695,
+ "Aldur's Watchtower": 21696,
+ "Aldur's Stony Gaze": 21697,
+ "Aldur's Deception": 21698,
+ "Aldur's Guantlet": 21699,
+ "Aldur's Advance": 21700,
+ "M'avina's Battle Hymn": 21701,
+ "M'avina's True Sight": 21702,
+ "M'avina's Embrace": 21703,
+ "M'avina's Icy Clutch": 21704,
+ "M'avina's Tenet": 21705,
+ "M'avina's Caster": 21706,
+ "Sazabi's Grand Tribute": 21707,
+ "Sazabi's Cobalt Redeemer": 21708,
+ "Sazabi's Ghost Liberator": 21709,
+ "Sazabi's Mental Sheath": 21710,
+ "Hwanin's Majesty": 21711,
+ "Hwanin's Justice": 21712,
+ "Hwanin's Splendor": 21713,
+ "Hwanin's Refuge": 21714,
+ "Hwanin's Cordon": 21715,
+ "The Disciple": 21716,
+ "Telling of Beads": 21717,
+ "Laying of Hands": 21718,
+ "Rite of Passage": 21719,
+ "Spiritual Custodian": 21720,
+ "Credendum": 21721,
+ "Cow King's Leathers": 21722,
+ "Cow King's Horns": 21723,
+ "Cow King's Hide": 21724,
+ "Cow King's Hoofs": 21725,
+ "Aragon's Masterpiece": 21726,
+ "Aragon's Sunfire": 21727,
+ "Aragon's Icy Stare": 21728,
+ "Aragon's Storm Cloud": 21729,
+ "Orphan's Call": 21730,
+ "Guillaume's Face": 21731,
+ "Willhelm's Pride": 21732,
+ "Magnus' Skin": 21733,
+ "Wihtstan's Guard": 21734,
+ "Titan's Revenge": 21735,
+ "Shakabra's Crux": 21736,
+ "Lycander's Aim": 21737,
+ "Shadow's Touch": 21738,
+ "The Prowler": 21739,
+ "Mortal Crescent": 21740,
+ "Cutthroat": 21741,
+ "Sarmichian Justice": 21742,
+ "Annihilus": 21743,
+ "Arreat's Face": 21744,
+ "The Harbinger": 21745,
+ "Doomseer": 21746,
+ "Howling Visage": 21747,
+ "Terra": 21748,
+ "Syrian": 21749,
+ "Jalal's Mane": 21750,
+ "Malignant": 21751,
+ "Apothecary's Tote": 21752,
+ "Apocrypha": 21753,
+ "Foci of Visjerei": 21754,
+ "Homunculus": 21755,
+ "Aurora's Guard": 21756,
+ "Crest of Morn": 21757,
+ "Herald of Zakarum": 21758,
+ "Akarat's Protector": 21759,
+ "Ancient Eye": 21760,
+ "Globe of Visjerei": 21761,
+ "The Oculus": 21762,
+ "Phoenix Egg": 21763,
+ "Xenos": 21764,
+ "Nagas": 21765,
+ "Wyvern's Head": 21766,
+ "Sightless Veil": 21767,
+ "ChampionFormatX": 21768,
+ "EskillKickSing": 21769,
+ "EskillKickPlur": 21770,
+ "EskillPetLife": 21771,
+ "EskillWolfDef": 21772,
+ "EskillPassiveFeral": 21773,
+ "Eskillperhit12": 21774,
+ "Eskillincasehit": 21775,
+ "Eskillincasemastery": 21776,
+ "Eskillincaseraven": 21777,
+ "pad": 21779,
+ "axf": 21780,
+ "Eskillkickdamage": 21781,
+ "ModStre10k": 21782,
+ "ModStre10L": 21783,
+ "Class Specific": 21784,
+ "fana": 21785,
+ "qsta5q14": 21786,
+ "qstsa5q42a": 21787,
+ "qstsa5q31a": 21788,
+ "qstsa5q21a": 21789,
+ "qstsa5q43a": 21790,
+ "qstsa5q62a": 21791,
+ "qstsa5q61a": 21792,
+ "act1X": 21797,
+ "act2X": 21798,
+ "act3X": 21799,
+ "act4X": 21800,
+ "strepilogueX": 21801,
+ "act5X": 21802,
+ "strlastcinematic": 21803,
+ "CfgSay7": 21804,
+ "0sc": 21805,
+ "tr2": 21806,
+ "of Lightning Strike": 21807,
+ "of Plague Jab": 21808,
+ "of Charged Strike": 21809,
+ "of Impaling Strike": 21810,
+ "of Poison Jab": 21811,
+ "of Power Strike": 21812,
+ "of the Colossus": 21813,
+ "of the Kraken": 21814,
+ "Tal Rasha's Wrappings": 21815,
+ "Tal Rasha's Fire-Spun Cloth": 21816,
+ "Tal Rasha's Adjudication": 21817,
+ "Tal Rasha's Howling Wind": 21818,
+ "Tal Rasha's Lidless Eye": 21819,
+ "Tal Rasha's Horadric Crest": 21820,
+ "Hwanin's Seal": 21821,
+ "Heaven's Brethren": 21822,
+ "Dangoon's Teaching": 21823,
+ "Ondal's Almighty": 21824,
+ "Heaven's Taebaek": 21825,
+ "Haemosu's Adament": 21826,
+ "Lycander's Flank": 21827,
+ "Constricting Ring": 21828,
+ "Ginther's Rift": 21829,
+ "Naj's Ancient Set": 21830,
+ "Naj's Light Plate": 21831,
+ "Naj's Circlet": 21832,
+ "Sander's Superstition": 21833,
+ "Sander's Taboo": 21834,
+ "Sander's Basis": 21835,
+ "Sander's Derby": 21836,
+ "Sander's Court Jester": 21837,
+ "Ghost Liberator": 21838,
+ "Wilhelm's Pride": 21839,
+ "Immortal King's Stone Crusher": 21840,
+ "Immortal King's Pillar": 21841,
+ "Immortal King's Forge": 21842,
+ "Immortal King's Detail": 21843,
+ "Immortal King's Soul Cage \tImmortal King's Soul Cage": 21844,
+ "Immortal King's Will": 21845,
+ "Immortal King": 21846,
+ "Aldur's Gauntlet": 21847,
+ "Ancient Statue 3": 21848,
+ "Ancient Statue 2": 21849,
+ "Ancient Statue 1": 21850,
+ "Baal Subject 1": 21851,
+ "Baal Subject 2": 21852,
+ "Baal Subject 3": 21853,
+ "Baal Subject 4": 21854,
+ "Baal Subject 5": 21855,
+ "Baal Subject 6": 21856,
+ "Baal Subject 6a": 21857,
+ "Baal Subject 6b": 21858,
+ "Baal Crab Clone": 21859,
+ "Baal Crab to Stairs": 21860,
+ "BaalColdMage": 21861,
+ "Baal Subject Mummy": 21862,
+ "Baal Tentacle": 21863,
+ "Baals Minion": 21864,
+ "Hell1": 21865,
+ "Hell2": 21866,
+ "Hell3": 21867,
+ "To Hell1": 21868,
+ "To Hell2": 21869,
+ "To Hell3": 21870,
+ "Lord of Destruction": 21871,
+ "EskillPerBlade": 21873,
+ "ExInsertSockets": 21874,
+ "McAuley's Superstition": 21875,
+ "McAuley's Taboo": 21876,
+ "McAuley's Riprap": 21877,
+ "McAuley's Paragon": 21878,
+ "McAuley's Folly": 21879,
+ "qstsa5q62b": 21881,
+ "of the Plague": 21883,
+ "Go South": 21884,
+ "ItemExpansiveChancX": 21885,
+ "ItemExpansiveChanc1": 21886,
+ "ItemExpansiveChanc2": 21887,
+ "ItemExpcharmdesc": 21888,
+ "StrMercEx12": 21889,
+ "StrMercEx14": 21890,
+ "StrMercEx15": 21891,
+ "Eskillelementaldmg": 21892,
+ "Playersubtitles29": 21893,
+ "Playersubtitles30": 21894,
+ "LeaveCampDru": 21895,
+ "LeaveCampAss": 21896,
+ "EnterDOEAss": 21897,
+ "EnterDOEDru": 21898,
+ "EnterBurialAss": 21899,
+ "EnterBurialDru": 21900,
+ "EnterMonasteryAss": 21901,
+ "EnterMonasteryDru": 21902,
+ "EnterForgottenTAss": 21903,
+ "EnterForgottenTDru": 21904,
+ "EnterJailAss": 21905,
+ "EnterJailDru": 21906,
+ "EnterCatacombsAss": 21907,
+ "EnterCatacombsDru": 21908,
+ "CompletingDOEAss": 21909,
+ "CompletingDOEDru": 21910,
+ "CompletingBurialAss": 21911,
+ "CompletingBurialDru": 21912,
+ "FindingInifusAss": 21913,
+ "FindingInifusDru": 21914,
+ "FindingCairnAss": 21915,
+ "FindingCairnDru": 21916,
+ "FindingTristramAss": 21917,
+ "FindingTristramDru": 21918,
+ "RescueCainAss": 21919,
+ "RescueCainDru": 21920,
+ "HoradricMalusAss": 21921,
+ "HoradricMalusDru": 21922,
+ "CompletingAndarielAss": 21925,
+ "CompletingAndarielDru": 21926,
+ "EnteringRadamentAss": 21927,
+ "EnteringRadamentDru": 21928,
+ "CompletingRadamentAss": 21929,
+ "CompletingRadamentDru": 21930,
+ "BeginTaintedSunAss": 21931,
+ "BeginTaintedSunDru": 21932,
+ "EnteringClawViperAss": 21933,
+ "EnteringClawViperDru": 21934,
+ "CompletingTaintedSunAss": 21935,
+ "CompletingTaintedSunDru": 21936,
+ "EnteringArcaneAss": 21937,
+ "EnteringArcaneDru": 21938,
+ "FindingSummonerAss": 21939,
+ "FindingSummonerDru": 21940,
+ "CompletingSummonerAss": 21941,
+ "CompletingSummonerDru": 21942,
+ "FindingdecoyTombAss": 21943,
+ "FindingdecoyTombDru": 21944,
+ "FindingTrueTombAss": 21945,
+ "FindingTrueTombDru": 21946,
+ "CompletingTombAss": 21947,
+ "CompletingTombDru": 21948,
+ "FindingLamEsenAss": 21949,
+ "FindingLamEsenDru": 21950,
+ "CompletingLamEsenAss": 21952,
+ "CompletingLamEsenDru": 21953,
+ "FindingBeneathCityAss": 21954,
+ "FindingBeneathCityDru": 21955,
+ "FindingDrainLeverAss": 21956,
+ "FindingDrainLeverDru": 21957,
+ "CompletingBeneathCityAss": 21958,
+ "CompletingBeneathCityDru": 21959,
+ "CompletingBladeAss": 21960,
+ "CompletingBladeDru": 21961,
+ "FindingJadeFigAss": 21962,
+ "FindingJadeFigDru": 21963,
+ "FindingTempleAss": 21964,
+ "FindingTempleDru": 21965,
+ "CompletingTempleAss": 21966,
+ "CompletingTempleDru": 21967,
+ "FindingGuardianTowerAss": 21968,
+ "FindingGuardianTowerDru": 21969,
+ "CompletingGuardianTowerAss": 21971,
+ "FreezingIzualAss": 21973,
+ "FreezingIzualDru": 21974,
+ "KillingdDiabloSor": 21975,
+ "KillingdDiabloBar": 21976,
+ "KillingdDiabloNec": 21977,
+ "KillingdDiabloPal": 21978,
+ "KillingdDiabloAms": 21979,
+ "KillingdDiabloAss": 21980,
+ "KillingdDiabloDru": 21981,
+ "LeavingTownAct5Sor": 21982,
+ "LeavingTownAct5Bar": 21983,
+ "LeavingTownAct5Nec": 21984,
+ "LeavingTownAct5Pal": 21985,
+ "LeavingTownAct5Ams": 21986,
+ "LeavingTownAct5Ass": 21987,
+ "LeavingTownAct5Dru": 21988,
+ "CompletingStopSiegeSor": 21989,
+ "CompletingStopSiegeBar": 21990,
+ "CompletingStopSiegeNec": 21991,
+ "CompletingStopSiegePal": 21992,
+ "CompletingStopSiegeAms": 21993,
+ "CompletingStopSiegeAss": 21994,
+ "CompletingStopSiegeDru": 21995,
+ "RescueQual-KehkAct5Sor": 21996,
+ "RescueQual-KehkAct5Bar": 21997,
+ "RescueQual-KehkAct5Nec": 21998,
+ "RescueQual-KehkAct5Pal": 21999,
+ "RescueQual-KehkAct5Ams": 22000,
+ "RescueQual-KehkAct5Ass": 22001,
+ "RescueQual-KehkAct5Dru": 22002,
+ "EnteringNihlathakAct5Sor": 22003,
+ "EnteringNihlathakAct5Bar": 22004,
+ "EnteringNihlathakAct5Nec": 22005,
+ "EnteringNihlathakAct5Pal": 22006,
+ "EnteringNihlathakAct5Ams": 22007,
+ "EnteringNihlathakAct5Ass": 22008,
+ "EnteringNihlathakAct5Dru": 22009,
+ "CompletingNihlathakAct5Sor": 22010,
+ "CompletingNihlathakAct5Bar": 22011,
+ "CompletingNihlathakAct5Nec": 22012,
+ "CompletingNihlathakAct5Pal": 22013,
+ "CompletingNihlathakAct5Ams": 22014,
+ "CompletingNihlathakAct5Ass": 22015,
+ "CompletingNihlathakAct5Dru": 22016,
+ "EnteringTopMountAct5Sor": 22017,
+ "EnteringTopMountAct5Bar": 22018,
+ "EnteringTopMountAct5Nec": 22019,
+ "EnteringTopMountAct5Pal": 22020,
+ "EnteringTopMountAct5Ams": 22021,
+ "EnteringTopMountAct5Ass": 22022,
+ "EnteringTopMountAct5Dru": 22023,
+ "EnteringWorldstoneAct5Sor": 22024,
+ "EnteringWorldstoneAct5Bar": 22025,
+ "EnteringWorldstoneAct5Nec": 22026,
+ "EnteringWorldstoneAct5Pal": 22027,
+ "EnteringWorldstoneAct5Ams": 22028,
+ "EnteringWorldstoneAct5Ass": 22029,
+ "EnteringWorldstoneAct5Dru": 22030,
+ "CompletingDefeatBaalAct5Sor": 22031,
+ "CompletingDefeatBaalAct5Bar": 22032,
+ "CompletingDefeatBaalAct5Nec": 22033,
+ "CompletingDefeatBaalAct5Pal": 22034,
+ "CompletingDefeatBaalAct5Ams": 22035,
+ "CompletingDefeatBaalAct5Ass": 22036,
+ "CompletingDefeatBaalAct5Dru": 22037,
+ "Skillname222": 22038,
+ "Skillsd222": 22039,
+ "Skillld222": 22040,
+ "Skillan222": 22041,
+ "Skillname223": 22046,
+ "Skillsd223": 22047,
+ "Skillld223": 22048,
+ "Skillan223": 22049,
+ "Skillname225": 22050,
+ "Skillsd225": 22051,
+ "Skillld225": 22052,
+ "Skillan225": 22053,
+ "Skillname226": 22054,
+ "Skillsd226": 22055,
+ "Skillld226": 22056,
+ "Skillan226": 22057,
+ "Skillname227": 22058,
+ "Skillsd227": 22059,
+ "Skillld227": 22060,
+ "Skillan227": 22061,
+ "Skillname228": 22062,
+ "Skillsd228": 22063,
+ "Skillld228": 22064,
+ "Skillan228": 22065,
+ "Skillname229": 22066,
+ "Skillsd229": 22067,
+ "Skillld229": 22068,
+ "Skillan229": 22069,
+ "Skillname230": 22070,
+ "Skillsd230": 22071,
+ "Skillld230": 22072,
+ "Skillan230": 22073,
+ "Skillname231": 22074,
+ "Skillsd231": 22075,
+ "Skillld231": 22076,
+ "Skillan231": 22077,
+ "Skillname232": 22078,
+ "Skillsd232": 22079,
+ "Skillld232": 22080,
+ "Skillan232": 22081,
+ "Skillname233": 22082,
+ "Skillsd233": 22083,
+ "Skillld233": 22084,
+ "Skillan233": 22085,
+ "Skillname234": 22086,
+ "Skillsd234": 22087,
+ "Skillld234": 22088,
+ "Skillan234": 22089,
+ "Skillname235": 22090,
+ "Skillsd235": 22091,
+ "Skillld235": 22092,
+ "Skillan235": 22093,
+ "Skillname236": 22094,
+ "Skillsd236": 22095,
+ "Skillld236": 22096,
+ "Skillan236": 22097,
+ "Skillname237": 22098,
+ "Skillsd237": 22099,
+ "Skillld237": 22100,
+ "Skillan237": 22101,
+ "Skillname238": 22102,
+ "Skillsd238": 22103,
+ "Skillld238": 22104,
+ "Skillan238": 22105,
+ "Skillname239": 22106,
+ "Skillsd239": 22107,
+ "Skillld239": 22108,
+ "Skillan239": 22109,
+ "Skillname240": 22110,
+ "Skillsd240": 22111,
+ "Skillld240": 22112,
+ "Skillan240": 22113,
+ "Skillname241": 22114,
+ "Skillsd241": 22115,
+ "Skillld241": 22116,
+ "Skillan241": 22117,
+ "Skillname242": 22118,
+ "Skillsd242": 22119,
+ "Skillld242": 22120,
+ "Skillan242": 22121,
+ "Skillname243": 22122,
+ "Skillsd243": 22123,
+ "Skillld243": 22124,
+ "Skillan243": 22125,
+ "Skillname244": 22126,
+ "Skillsd244": 22127,
+ "Skillld244": 22128,
+ "Skillan244": 22129,
+ "Skillname245": 22130,
+ "Skillsd245": 22131,
+ "Skillld245": 22132,
+ "Skillan245": 22133,
+ "Skillname246": 22134,
+ "Skillsd246": 22135,
+ "Skillld246": 22136,
+ "Skillan246": 22137,
+ "Skillname247": 22138,
+ "Skillsd247": 22139,
+ "Skillld247": 22140,
+ "Skillan247": 22141,
+ "Skillname248": 22142,
+ "Skillsd248": 22143,
+ "Skillld248": 22144,
+ "Skillan248": 22145,
+ "Skillname249": 22146,
+ "Skillsd249": 22147,
+ "Skillld249": 22148,
+ "Skillan249": 22149,
+ "Skillname250": 22150,
+ "Skillsd250": 22151,
+ "Skillld250": 22152,
+ "Skillan250": 22153,
+ "Skillname251": 22154,
+ "Skillsd251": 22155,
+ "Skillld251": 22156,
+ "Skillan251": 22157,
+ "Skillname252": 22158,
+ "Skillsd252": 22159,
+ "Skillld252": 22160,
+ "Skillan252": 22161,
+ "Skillname253": 22162,
+ "Skillsd253": 22163,
+ "Skillld253": 22164,
+ "Skillan253": 22165,
+ "Skillname254": 22166,
+ "Skillsd254": 22167,
+ "Skillld254": 22168,
+ "Skillan254": 22169,
+ "Skillname255": 22170,
+ "Skillsd255": 22171,
+ "Skillld255": 22172,
+ "Skillan255": 22173,
+ "Skillname256": 22174,
+ "Skillsd256": 22175,
+ "Skillld256": 22176,
+ "Skillan256": 22177,
+ "Skillname257": 22178,
+ "Skillsd257": 22179,
+ "Skillld257": 22180,
+ "Skillan257": 22181,
+ "Skillname258": 22182,
+ "Skillsd258": 22183,
+ "Skillld258": 22184,
+ "Skillan258": 22185,
+ "Skillname259": 22186,
+ "Skillsd259": 22187,
+ "Skillld259": 22188,
+ "Skillan259": 22189,
+ "Skillname260": 22190,
+ "Skillsd260": 22191,
+ "Skillld260": 22192,
+ "Skillan260": 22193,
+ "Skillname261": 22194,
+ "Skillsd261": 22195,
+ "Skillld261": 22196,
+ "Skillan261": 22197,
+ "Skillname262": 22198,
+ "Skillsd262": 22199,
+ "Skillld262": 22200,
+ "Skillan262": 22201,
+ "Skillname263": 22202,
+ "Skillsd263": 22203,
+ "Skillld263": 22204,
+ "Skillan263": 22205,
+ "Skillname264": 22206,
+ "Skillsd264": 22207,
+ "Skillld264": 22208,
+ "Skillan264": 22209,
+ "Skillname265": 22210,
+ "Skillsd265": 22211,
+ "Skillld265": 22212,
+ "Skillan265": 22213,
+ "Skillname266": 22214,
+ "Skillsd266": 22215,
+ "Skillld266": 22216,
+ "Skillan266": 22217,
+ "Skillname267": 22218,
+ "Skillsd267": 22219,
+ "Skillld267": 22220,
+ "Skillan267": 22221,
+ "Skillname268": 22222,
+ "Skillsd268": 22223,
+ "Skillld268": 22224,
+ "Skillan268": 22225,
+ "Skillname269": 22226,
+ "Skillsd269": 22227,
+ "Skillld269": 22228,
+ "Skillan269": 22229,
+ "Skillname270": 22230,
+ "Skillsd270": 22231,
+ "Skillld270": 22232,
+ "Skillan270": 22233,
+ "Skillname271": 22234,
+ "Skillsd271": 22235,
+ "Skillld271": 22236,
+ "Skillan271": 22237,
+ "Skillname272": 22238,
+ "Skillsd272": 22239,
+ "Skillld272": 22240,
+ "Skillan272": 22241,
+ "Skillname273": 22242,
+ "Skillsd273": 22243,
+ "Skillld273": 22244,
+ "Skillan273": 22245,
+ "Skillname274": 22246,
+ "Skillsd274": 22247,
+ "Skillld274": 22248,
+ "Skillan274": 22249,
+ "Skillname275": 22250,
+ "Skillsd275": 22251,
+ "Skillld275": 22252,
+ "Skillan275": 22253,
+ "Skillname276": 22254,
+ "Skillsd276": 22255,
+ "Skillld276": 22256,
+ "Skillan276": 22257,
+ "Skillname277": 22258,
+ "Skillsd277": 22259,
+ "Skillld277": 22260,
+ "Skillan277": 22261,
+ "Skillname278": 22262,
+ "Skillsd278": 22263,
+ "Skillld278": 22264,
+ "Skillan278": 22265,
+ "Skillname279": 22266,
+ "Skillsd279": 22267,
+ "Skillld279": 22268,
+ "Skillan279": 22269,
+ "Skillname280": 22270,
+ "Skillsd280": 22271,
+ "Skillld280": 22272,
+ "Skillan280": 22273,
+ "Skillname281": 22274,
+ "Skillsd281": 22275,
+ "Skillld281": 22276,
+ "Skillan281": 22277,
+ "ESkillPerKick": 22286,
+ "EskillLifeSteal": 22287,
+ "Eskillchancetostun": 22288,
+ "Eskillchancetoafflict": 22289,
+ "Eskillpowerup1": 22290,
+ "Eskillpowerup2": 22291,
+ "Eskillpowerup3": 22292,
+ "Eskillpowerupadd": 22293,
+ "Eskillsinishup": 22294,
+ "Eskillpudlife": 22295,
+ "Eskillpudmana": 22296,
+ "Eskillpudburning": 22297,
+ "Eskillpuddgmper": 22298,
+ "Eskilllowerresis": 22299,
+ "Eskilltomeleeattacks": 22300,
+ "EskillManaSteal": 22301,
+ "Eskillferalpets": 22302,
+ "Eskillpercentatt": 22303,
+ "Eskillpercentlif": 22304,
+ "Eskillpercentdmg": 22305,
+ "Eskillfinishmove": 22306,
+ "Eskillmanarecov": 22307,
+ "Eskillphoenix1": 22308,
+ "Eskillphoenix2": 22309,
+ "Eskillphoenix3": 22310,
+ "Eskillthunder1": 22311,
+ "Eskillthunder2": 22312,
+ "Eskillthunder3": 22313,
+ "Eskillfistsoffire1": 22314,
+ "Eskillfistsoffire2": 22315,
+ "Eskillfistsoffire3": 22316,
+ "Eskillbladesofice1": 22317,
+ "Eskillbladesofice2": 22318,
+ "Eskillbladesofice3": 22319,
+ "strUI5": 22320,
+ "strUI6": 22321,
+ "strUI7": 22322,
+ "strUI8": 22323,
+ "strUI9": 22324,
+ "strUI10": 22325,
+ "strUI11": 22326,
+ "strUI12": 22327,
+ "strUI13": 22328,
+ "strUI14": 22329,
+ "UIFenirsui": 22330,
+ "UiRescuedBarUI": 22331,
+ "UiShadowUI": 22332,
+ "StrUI18": 22333,
+ "Spike Generator": 22334,
+ "Charged Bolt Sentry": 22335,
+ "Lightning Sentry": 22336,
+ "Blade Creeper": 22337,
+ "Invis Pet": 22338,
+ "Druid Hawk": 22339,
+ "Druid Wolf": 22340,
+ "Druid Totem": 22341,
+ "Druid Fenris": 22342,
+ "Druid Spirit Wolf": 22343,
+ "Druid Bear": 22344,
+ "Druid Plague Poppy": 22345,
+ "Druid Cycle of Life": 22346,
+ "Vine Creature": 22347,
+ "Eagleexp": 22348,
+ "Wolf": 22349,
+ "Bear": 22350,
+ "Siege Door": 22351,
+ "Siege Beast": 22358,
+ "Hell Temptress": 22389,
+ "Blood Temptress": 22390,
+ "Blood Witch": 22394,
+ "Hell Witch": 22395,
+ "CatapultN": 22411,
+ "CatapultS": 22412,
+ "CatapultE": 22413,
+ "CatapultW": 22414,
+ "Frozen Horror1": 22415,
+ "Frozen Horror2": 22416,
+ "Frozen Horror3": 22417,
+ "Frozen Horror4": 22418,
+ "Frozen Horror5": 22419,
+ "Blood Lord1": 22420,
+ "Blood Lord2": 22421,
+ "Blood Lord3": 22422,
+ "Blood Lord4": 22423,
+ "Blood Lord5": 22424,
+ "Catapult Spotter N": 22425,
+ "Catapult Spotter S": 22426,
+ "Catapult Spotter E": 22427,
+ "Catapult Spotter W": 22428,
+ "Catapult Spotter Siege": 22429,
+ "CatapultSiege": 22430,
+ "Barricade Wall Right": 22431,
+ "Barricade Wall Left": 22432,
+ "Barricade Door": 22433,
+ "Barricade Tower": 22434,
+ "Siege Boss": 22435, // shenk the overseer
+ "Evil hut": 22436,
+ "Death Mauler1": 22437,
+ "Death Mauler2": 22438,
+ "Death Mauler3": 22439,
+ "Death Mauler4": 22440,
+ "Death Mauler5": 22441,
+ "SnowYeti1": 22442,
+ "SnowYeti2": 22443,
+ "SnowYeti3": 22444,
+ "SnowYeti4": 22445,
+ "Baal Throne": 22446,
+ "Baal Crab": 22447,
+ "Baal Taunt": 22448,
+ "Putrid Defiler1": 22449,
+ "Putrid Defiler2": 22450,
+ "Putrid Defiler3": 22451,
+ "Putrid Defiler4": 22452,
+ "Putrid Defiler5": 22453,
+ "Pain Worm1": 22454,
+ "Pain Worm2": 22455,
+ "Pain Worm3": 22456,
+ "Pain Worm4": 22457,
+ "Pain Worm5": 22458,
+ "WolfRider5": 22459,
+ "WolfRider4": 22460,
+ "WolfRider3": 22461,
+ "WolfRider2": 22462,
+ "WolfRider1": 22463,
+ "Oak Sage": 22464,
+ "Heart of Wolverine": 22465,
+ "Spirit of Barbs": 22466,
+ "Shadow Warrior": 22467,
+ "Death Sentry": 22468,
+ "Inferno Sentry": 22469,
+ "Shadow Master": 22470,
+ "Wake of Destruction": 22471,
+ "Ghostly": 22472,
+ "Fanatic": 22473,
+ "Possessed": 22474,
+ "Berserk": 22475,
+ "Larzuk": 22476,
+ "Drehya": 22477,
+ "Malah": 22478,
+ "Nihlathak Town": 22479,
+ "Qual-Kehk": 22480,
+ "Act 5 Townguard": 22481,
+ "Act 5 Combatant": 22482,
+ "Nihlathak": 22483,
+ "POW": 22484,
+ "Moe": 22485,
+ "Curly": 22486,
+ "Larry": 22487,
+ "Ancient Barbarian 3": 22488,
+ "Ancient Barbarian 2": 22489,
+ "Ancient Barbarian 1": 22490,
+ "Blaze Ripper": 22491,
+ "Magma Torquer": 22492,
+ "Sharp Tooth Sayer": 22493,
+ "Vinvear Molech": 22494,
+ "Anodized Elite": 22495,
+ "Snapchip Shatter": 22496,
+ "Pindleskin": 22497,
+ "Threash Socket": 22498,
+ "Eyeback Unleashed": 22499,
+ "Megaflow Rectifier": 22500, // eldritch the rectifier
+ "Dac Farren": 22501,
+ "Bonesaw Breaker": 22502,
+ "Axe Dweller": 22503,
+ "Frozenstein": 22504,
+ "strDruidOnly": 22505,
+ "strAssassinOnly": 22506,
+ "strAmazonOnly": 22507,
+ "strBarbarianOnly": 22508,
+ "StrSklTree26": 22509,
+ "StrSklTree27": 22510,
+ "StrSklTree28": 22511,
+ "StrSklTree29": 22512,
+ "StrSklTree30": 22513,
+ "StrSklTree31": 22514,
+ "StrSklTree32": 22515,
+ "StrSklTree33": 22516,
+ "StrSklTree34": 22517,
+ "chestr": 22520,
+ "barrel wilderness": 22521,
+ "woodchestL": 22522,
+ "burialchestL": 22523,
+ "burialchestR": 22524,
+ "ChestL": 22527,
+ "ChestSL": 22528,
+ "ChestSR": 22529,
+ "woodchestR": 22530,
+ "chestR": 22531,
+ "burningbodies": 22532,
+ "burningpit": 22533,
+ "tribal flag": 22534,
+ "flag widlerness": 22535,
+ "eflg": 22536,
+ "chan": 22537,
+ "jar": 22538,
+ "jar2": 22539,
+ "jar3": 22540,
+ "swingingheads": 22541,
+ "pole": 22542,
+ "animatedskullsandrocks": 22543,
+ "hellgate": 22544,
+ "gate": 22545,
+ "banner1": 22546,
+ "banner2": 22547,
+ "mrpole": 22548,
+ "pene": 22549,
+ "debris": 22550,
+ "woodchest2R": 22551,
+ "woodchest2L": 22552,
+ "object1": 22553,
+ "magic shrine2": 22554,
+ "torch2": 22555,
+ "torch1": 22556,
+ "tomb3": 22557,
+ "tomb2": 22558,
+ "tomb1": 22559,
+ "ttor": 22560,
+ "icecave_torch2": 22561,
+ "icecave_torch1": 22562,
+ "clientsmoke": 22563,
+ "deadbarbarian": 22564,
+ "deadbarbarian18": 22565,
+ "uncle f#%* comedy central(c)\tMoe": 22566,
+ "cagedwussie1": 22567,
+ "icecaveshrine2": 22568,
+ "icecavejar4": 22569,
+ "icecavejar3": 22570,
+ "icecavejar2": 22571,
+ "icecavejar1": 22572,
+ "evilurn": 22573,
+ "secret object": 22574,
+ "Altar": 22575,
+ "Ldeathpole": 22576,
+ "deathpole": 22577,
+ "explodingchest": 22578,
+ "banner 2": 22579,
+ "banner 1": 22580,
+ "pileofskullsandrocks": 22581,
+ "animated skulland rockpile": 22582,
+ "jar1": 22583,
+ "etorch2": 22584,
+ "ettr": 22585,
+ "ecfra": 22586,
+ "etorch1": 22587,
+ "healthshrine": 22588,
+ "explodingbarrel": 22589,
+ "flag wilderness": 22590,
+ "object": 22591,
+ "Shrine2wilderness": 22592,
+ "Shrine3wilderness": 22593,
+ "pyox": 22594,
+ "ptox": 22595,
+ "Siege Control": 22596,
+ "mrjar": 22597,
+ "object2": 22598,
+ "mrbox": 22599,
+ "tomb3L": 22600,
+ "tomb2L": 22601,
+ "tomb1L": 22602,
+ "red light": 22603,
+ "groundtombL": 22604,
+ "groundtomb": 22605,
+ "deadperson": 22606,
+ "candles": 22607,
+ "sbub": 22608,
+ "ubub": 22609,
+ "deadperson2": 22610,
+ "Prison Door": 22611,
+ "ancientsaltar": 22612,
+ "hiddenstash": 22613,
+ "eweaponrackL": 22614,
+ "eweaponrackR": 22615,
+ "earmorstandL": 22616,
+ "earmorstandR": 22617,
+ "qstsa5q1": 22618,
+ "qsta5q11": 22619,
+ "qsta5q12": 22620,
+ "qsta5q13": 22621,
+ "qstsa5q2": 22622,
+ "qstsa5q21": 22623,
+ "qstsa5q22": 22624,
+ "qstsa5q23": 22625,
+ "qstsa5q24": 22626,
+ "qstsa5q3": 22627,
+ "qstsa5q31": 22628,
+ "qstsa5q32": 22629,
+ "qstsa5q33": 22630,
+ "qstsa5q34": 22631,
+ "qstsa5q35": 22632,
+ "qstsa5q4": 22633,
+ "qstsa5q41": 22634,
+ "qstsa5q42": 22635,
+ "qstsa5q43": 22636,
+ "qstsa5q5": 22637,
+ "qstsa5q51": 22638,
+ "qstsa5q52": 22639,
+ "qstsa5q53": 22640,
+ "qstsa5q6": 22641,
+ "qstsa5q61": 22642,
+ "qstsa5q62": 22643,
+ "qstsa5q63": 22644,
+ "qstsa5q64": 22645,
+ "Harrogath": 22646,
+ "Bloody Foothills": 22647,
+ "Rigid Highlands": 22648,
+ "Arreat Plateau": 22649,
+ "Crystalized Cavern Level 1": 22650,
+ "Cellar of Pity": 22651,
+ "Crystalized Cavern Level 2": 22652,
+ "Echo Chamber": 22653,
+ "Tundra Wastelands": 22654,
+ "Glacial Caves Level 1": 22655,
+ "Glacial Caves Level 2": 22656,
+ "Rocky Summit": 22657,
+ "Nihlathaks Temple": 22658,
+ "Halls of Anguish": 22659,
+ "Halls of Death's Calling": 22660,
+ "Halls of Tormented Insanity": 22661,
+ "Halls of Vaught": 22662,
+ "The Worldstone Keep Level 1": 22663,
+ "The Worldstone Keep Level 2": 22664,
+ "The Worldstone Keep Level 3": 22665,
+ "The Worldstone Chamber": 22666,
+ "Throne of Destruction": 22667,
+ "To Harrogath": 22668,
+ "To The Bloody Foothills": 22669,
+ "To The Rigid Highlands": 22670,
+ "To The Arreat Plateau": 22671,
+ "To The Crystalized Cavern Level 1": 22672,
+ "To The Cellar of Pity": 22673,
+ "To The Crystalized Cavern Level 2": 22674,
+ "To The Echo Chamber": 22675,
+ "To The Tundra Wastelands": 22676,
+ "To The Glacier Caves Level 1": 22677,
+ "To The Glacier Caves Level 2": 22678,
+ "To The Rocky Summit": 22679,
+ "To Nihlathaks Temple": 22680,
+ "To The Halls of Anguish": 22681,
+ "To The Halls of Death's Calling": 22682,
+ "To The Halls of Tormented Insanity": 22683,
+ "To The Halls of Vaught": 22684,
+ "To The Worldstone Keep Level 1": 22685,
+ "To The Worldstone Keep Level 2": 22686,
+ "To The Worldstone Keep Level 3": 22687,
+ "To The Worldstone Chamber": 22688,
+ "To The Throne of Destruction": 22689,
+ "hireiconinfo1": 22690,
+ "hireiconinfo2": 22691,
+ "hiredismiss": 22692,
+ "hiredismisshire": 22693,
+ "hirerehire": 22694,
+ "hireresurrect": 22695,
+ "hireresurrect2": 22696,
+ "hirechat1": 22697,
+ "hirechat2": 22698,
+ "hirechat3": 22699,
+ "hirepraise1": 22700,
+ "hirepraise2": 22701,
+ "hiredanger1": 22702,
+ "hiredanger2": 22703,
+ "hiredanger3": 22704,
+ "hiredanger4": 22705,
+ "hiredanger5": 22706,
+ "hiredanger6": 22707,
+ "hirefeelstronger2": 22708,
+ "hirehelp1": 22709,
+ "hirehelp2": 22710,
+ "hirehelp3": 22711,
+ "hirehelp4": 22712,
+ "hiregreets1": 22713,
+ "hiregreets2": 22714,
+ "hiregreets3": 22715,
+ "hiregreets4": 22716,
+ "CfgSkill9": 22717,
+ "CfgSkill10": 22718,
+ "CfgSkill11": 22719,
+ "CfgSkill12": 22720,
+ "CfgSkill13": 22721,
+ "CfgSkill14": 22722,
+ "CfgSkill15": 22723,
+ "CfgSkill16": 22724,
+ "CfgToggleminimap": 22725,
+ "Cfgswapweapons": 22726,
+ "Cfghireling": 22727,
+ "MiniPanelHireinv": 22728,
+ "MiniPanelHire": 22729,
+ "Go North": 22737,
+ "Travel To Harrogath": 22738,
+ "Rename Instruct": 22747,
+ "Addsocketsui": 22748,
+ "Personalizeui": 22749,
+ "Addsocketsui2": 22750,
+ "MercX101": 22751,
+ "MercX102": 22752,
+ "MercX103": 22753,
+ "MercX104": 22754,
+ "MercX105": 22755,
+ "MercX106": 22756,
+ "MercX107": 22757,
+ "MercX108": 22758,
+ "MercX109": 22759,
+ "MercX110": 22760,
+ "MercX111": 22761,
+ "MercX112": 22762,
+ "MercX113": 22763,
+ "MercX114": 22764,
+ "MercX115": 22765,
+ "MercX116": 22766,
+ "MercX117": 22767,
+ "MercX118": 22768,
+ "MercX119": 22769,
+ "MercX120": 22770,
+ "MercX121": 22771,
+ "MercX122": 22772,
+ "MercX123": 22773,
+ "MercX124": 22774,
+ "MercX125": 22775,
+ "MercX126": 22776,
+ "MercX127": 22777,
+ "MercX128": 22778,
+ "MercX129": 22779,
+ "MercX130": 22780,
+ "MercX131": 22781,
+ "MercX132": 22782,
+ "MercX133": 22783,
+ "MercX134": 22784,
+ "MercX135": 22785,
+ "MercX136": 22786,
+ "MercX137": 22787,
+ "MercX138": 22788,
+ "MercX139": 22789,
+ "MercX140": 22790,
+ "MercX141": 22791,
+ "MercX142": 22792,
+ "MercX143": 22793,
+ "MercX144": 22794,
+ "MercX145": 22795,
+ "MercX146": 22796,
+ "MercX147": 22797,
+ "MercX148": 22798,
+ "MercX149": 22799,
+ "MercX150": 22800,
+ "MercX151": 22801,
+ "MercX152": 22802,
+ "MercX153": 22803,
+ "MercX154": 22804,
+ "MercX155": 22805,
+ "MercX156": 22806,
+ "MercX157": 22807,
+ "MercX158": 22808,
+ "MercX159": 22809,
+ "MercX160": 22810,
+ "MercX161": 22811,
+ "MercX162": 22812,
+ "MercX163": 22813,
+ "MercX164": 22814,
+ "MercX165": 22815,
+ "MercX166": 22816,
+ "MercX167": 22817
+ };
+
+ let LocaleStringName = {};
+
+ for (let k in LocaleStringID) {
+ LocaleStringName[LocaleStringID[k]] = k;
+ }
+
+ module.exports = {
+ LocaleStringName: LocaleStringName,
+ LocaleStringID: LocaleStringID
+ };
+})(module);
diff --git a/libs/SoloPlay/Modules/GameData/MissileData.js b/libs/SoloPlay/Modules/GameData/MissileData.js
new file mode 100644
index 00000000..fb1d0c16
--- /dev/null
+++ b/libs/SoloPlay/Modules/GameData/MissileData.js
@@ -0,0 +1,28 @@
+(function (module, require) {
+ /**
+ * MissilesData
+ */
+ const MISSILES_COUNT = 385;
+ const MissilesData = Array(MISSILES_COUNT);
+
+ for (let i = 0; i < MissilesData.length; i++) {
+ let index = i;
+ MissilesData[i] = ({
+ index: index,
+ classID: index,
+ internalName: getBaseStat("missiles", index, "Missile"),
+ velocity: getBaseStat("missiles", index, "Vel"),
+ velocityMax: getBaseStat("missiles", index, "MaxVel"),
+ acceleration: getBaseStat("missiles", index, "Accel"),
+ range: getBaseStat("missiles", index, "Range"),
+ size: getBaseStat("missiles", index, "Size"),
+ minDamage: getBaseStat("missiles", index, "MinDamage"),
+ maxDamage: getBaseStat("missiles", index, "MaxDamage"),
+ eType: getBaseStat("missiles", index, "EType"),
+ eMin: getBaseStat("missiles", index, "EMin"),
+ eMax: getBaseStat("missiles", index, "EMax"),
+ cltSubMissiles: [getBaseStat("missiles", index, "CltSubMissile1"), getBaseStat("missiles", index, "CltSubMissile2"), getBaseStat("missiles", index, "CltSubMissile3")],
+ });
+ }
+ module.exports = MissilesData;
+})(module, require);
diff --git a/libs/SoloPlay/Modules/GameData/MonsterData.js b/libs/SoloPlay/Modules/GameData/MonsterData.js
new file mode 100644
index 00000000..b50221b5
--- /dev/null
+++ b/libs/SoloPlay/Modules/GameData/MonsterData.js
@@ -0,0 +1,111 @@
+(function (module, require) {
+ const MonsterData = (function () {
+ const LocaleStringName = require("./LocaleStringID").LocaleStringName;
+ const MONSTER_INDEX_COUNT = 770;
+ /** @type {Map} */
+ const _monsterData = new Map();
+
+ /**
+ * @constructor
+ * @param {number} index
+ */
+ function MonsterObj (index) {
+ /** @type {number} */
+ this.ClassID = index;
+ /** @type {number} */
+ this.Type = getBaseStat("monstats", index, "MonType");
+ /** @type {number} */
+ this.Level = getBaseStat("monstats", index, "Level"); // normal only, nm/hell are determined by area's LevelEx
+ this.Ranged = getBaseStat("monstats", index, "RangedType");
+ this.Rarity = getBaseStat("monstats", index, "Rarity");
+ this.Threat = getBaseStat("monstats", index, "threat");
+ this.PetIgnore = getBaseStat("monstats", index, "petignore");
+ this.Align = getBaseStat("monstats", index, "Align");
+ this.Melee = getBaseStat("monstats", index, "isMelee");
+ this.NPC = getBaseStat("monstats", index, "npc");
+ this.Demon = getBaseStat("monstats", index, "demon");
+ this.Flying = getBaseStat("monstats", index, "flying");
+ this.Boss = getBaseStat("monstats", index, "boss");
+ this.ActBoss = getBaseStat("monstats", index, "primeevil");
+ this.Killable = getBaseStat("monstats", index, "killable");
+ this.Convertable = getBaseStat("monstats", index, "switchai");
+ this.NeverCount = getBaseStat("monstats", index, "neverCount");
+ this.DeathDamage = getBaseStat("monstats", index, "deathDmg");
+ this.Regeneration = getBaseStat("monstats", index, "DamageRegen");
+ /** @type {string} */
+ this.LocaleString = getLocaleString(getBaseStat("monstats", index, "NameStr"));
+ /** @type {string} */
+ this.InternalName = LocaleStringName[getBaseStat("monstats", index, "NameStr")];
+ this.ExperienceModifier = getBaseStat("monstats", index, ["Exp", "Exp(N)", "Exp(H)"][me.diff]);
+ this.Undead = (getBaseStat("monstats", index, "hUndead") && 2) | (getBaseStat("monstats", index, "lUndead") && 1);
+ this.Drain = getBaseStat("monstats", index, ["Drain", "Drain(N)", "Drain(H)"][me.diff]);
+ this.Block = getBaseStat("monstats", index, ["ToBlock", "ToBlock(N)", "ToBlock(H)"][me.diff]);
+ this.Physical = getBaseStat("monstats", index, ["ResDm", "ResDm(N)", "ResDm(H)"][me.diff]);
+ this.Magic = getBaseStat("monstats", index, ["ResMa", "ResMa(N)", "ResMa(H)"][me.diff]);
+ this.Fire = getBaseStat("monstats", index, ["ResFi", "ResFi(N)", "ResFi(H)"][me.diff]);
+ this.Lightning = getBaseStat("monstats", index, ["ResLi", "ResLi(N)", "ResLi(H)"][me.diff]);
+ this.Cold = getBaseStat("monstats", index, ["ResCo", "ResCo(N)", "ResCo(H)"][me.diff]);
+ this.Poison = getBaseStat("monstats", index, ["ResPo", "ResPo(N)", "ResPo(H)"][me.diff]);
+ this.Minions = ([
+ getBaseStat("monstats", index, "minion1"), getBaseStat("monstats", index, "minion2")
+ ].filter(mon => mon !== 65535));
+ this.MinionCount = ({
+ Min: getBaseStat("monstats", index, "minion1"),
+ Max: getBaseStat("monstats", index, "minion2")
+ });
+ this.GroupCount = ({
+ Min: getBaseStat("monstats", index, "MinGrp"),
+ Max: getBaseStat("monstats", index, "MaxGrp")
+ });
+ this.Velocity = getBaseStat("monstats", index, "Velocity");
+ this.Run = getBaseStat("monstats", index, "Run");
+ this.SizeX = getBaseStat("monstats", index, "SizeX");
+ this.SizeY = getBaseStat("monstats", index, "SizeY");
+ this.Attack1MinDmg = getBaseStat("monstats", index, ["A1MinD", "A1MinD(N)", "A1MinD(H)"][me.diff]);
+ this.Attack1MaxDmg = getBaseStat("monstats", index, ["A1MaxD", "A1MaxD(N)", "A1MaxD(H)"][me.diff]);
+ this.Attack2MinDmg = getBaseStat("monstats", index, ["A2MinD", "A2MinD(N)", "A2MinD(H)"][me.diff]);
+ this.Attack2MaxDmg = getBaseStat("monstats", index, ["A2MaxD", "A2MaxD(N)", "A2MaxD(H)"][me.diff]);
+ this.Skill1MinDmg = getBaseStat("monstats", index, ["S1MinD", "S1MinD(N)", "S1MinD(H)"][me.diff]);
+ this.Skill1MaxDmg = getBaseStat("monstats", index, ["S1MaxD", "S1MaxD(N)", "S1MaxD(H)"][me.diff]);
+ this.MinHp = getBaseStat("monstats", index, "minHP");
+ this.MaxHp = getBaseStat("monstats", index, "maxHP");
+ }
+
+ for (let i = 0; i < MONSTER_INDEX_COUNT; i++) {
+ _monsterData.set(i, new MonsterObj(i));
+ }
+
+ return {
+ /** @param {number} classid */
+ has: function (classid) {
+ return _monsterData.has(classid);
+ },
+
+ /** @param {number} classid */
+ get: function (classid) {
+ return _monsterData.get(classid);
+ },
+
+ /** @param {string} whatToFind */
+ findByName: function (whatToFind) {
+ let matches = [];
+ for (let [, mon] of _monsterData) {
+ let _diffcount = Math.min(
+ whatToFind.diffCount(mon.LocaleString),
+ whatToFind.diffCount(mon.InternalName)
+ );
+ if (_diffcount === 0) {
+ return mon;
+ }
+ matches.push([_diffcount, mon]);
+ }
+ return matches
+ .sort(function (a, b) {
+ return a[0] - b[0];
+ }).first()[1];
+
+ },
+ };
+ })();
+ module.exports = MonsterData;
+})(module, require);
diff --git a/libs/SoloPlay/Modules/GameData/PotData.js b/libs/SoloPlay/Modules/GameData/PotData.js
new file mode 100644
index 00000000..acdc4de0
--- /dev/null
+++ b/libs/SoloPlay/Modules/GameData/PotData.js
@@ -0,0 +1,155 @@
+/**
+ * @description Data about pots
+ * @author ryancrunchi, theBGuy
+ * @type UMD module
+ */
+
+(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.PotData = factory();
+ }
+}(this, function () {
+ "use strict";
+ const PotData = (function () {
+ /**
+ * @typedef {Object} Pot
+ * @property {string} type
+ * @property {number[]} effect
+ * @property {number} cost
+ * @property {number} duration
+ * @property {Array} [recipe]
+ */
+
+ /** @type {Object.} */
+ const _pots = {};
+
+ _pots[sdk.items.MinorHealingPotion] = {
+ type: "hp",
+ effect: [45, 30, 30, 45, 60, 30, 45],
+ cost: 30,
+ duration: 7.68
+ };
+ _pots[sdk.items.LightHealingPotion] = {
+ type: "hp",
+ effect: [90, 60, 60, 90, 120, 60, 90],
+ cost: 67,
+ duration: 6.4
+ };
+ _pots[sdk.items.HealingPotion] = {
+ type: "hp",
+ effect: [150, 100, 100, 150, 200, 100, 150],
+ cost: 112,
+ duration: 6.84
+ };
+ _pots[sdk.items.GreaterHealingPotion] = {
+ type: "hp",
+ effect: [270, 180, 180, 270, 360, 180, 270],
+ cost: 225,
+ duration: 7.68
+ };
+ _pots[sdk.items.SuperHealingPotion] = {
+ type: "hp",
+ effect: [480, 320, 320, 480, 640, 320, 480],
+ cost: undefined,
+ duration: 10.24
+ };
+ _pots[sdk.items.MinorManaPotion] = {
+ type: "mp",
+ effect: [30, 40, 40, 30, 20, 40, 30],
+ cost: 60,
+ duration: 5.12
+ };
+ _pots[sdk.items.LightManaPotion] = {
+ type: "mp",
+ effect: [60, 80, 80, 60, 40, 80, 60],
+ cost: 135,
+ duration: 5.12
+ };
+ _pots[sdk.items.ManaPotion] = {
+ type: "mp",
+ effect: [120, 160, 160, 120, 80, 160, 120],
+ cost: 270,
+ duration: 5.12
+ };
+ _pots[sdk.items.GreaterManaPotion] = {
+ type: "mp",
+ effect: [225, 300, 300, 225, 150, 300, 225],
+ cost: 450,
+ duration: 5.12
+ };
+ _pots[sdk.items.SuperManaPotion] = {
+ type: "mp",
+ effect: [375, 500, 500, 375, 250, 500, 375],
+ cost: undefined,
+ duration: 5.12
+ };
+ _pots[sdk.items.RejuvenationPotion] = {
+ type: "rv",
+ effect: [35, 35, 35, 35, 35, 35, 35],
+ cost: undefined,
+ duration: 0.04,
+ recipe: [
+ [
+ sdk.items.HealingPotion, sdk.items.HealingPotion, sdk.items.HealingPotion,
+ sdk.items.ManaPotion, sdk.items.ManaPotion, sdk.items.ManaPotion,
+ function (item) {
+ return item.itemType === sdk.items.type.ChippedGem;
+ }
+ ]
+ ]
+ };
+ _pots[sdk.items.FullRejuvenationPotion] = {
+ type: "rv",
+ effect: [100, 100, 100, 100, 100, 100, 100],
+ cost: undefined,
+ duration: 0.04,
+ recipe: [
+ // Recipe is either an classid, or an function that returns true on the correct item
+ [
+ sdk.items.RejuvenationPotion, sdk.items.RejuvenationPotion, sdk.items.RejuvenationPotion // 3 normal rv's
+ ],
+ [
+ sdk.items.HealingPotion, sdk.items.HealingPotion, sdk.items.HealingPotion,
+ sdk.items.ManaPotion, sdk.items.ManaPotion, sdk.items.ManaPotion,
+ function (item) {
+ return item.itemType === sdk.items.type.Gem;
+ }
+ ],
+ ]
+ };
+
+ /** @type {Pot[]} */
+ const _mpPots = [
+ _pots[sdk.items.MinorManaPotion], _pots[sdk.items.LightManaPotion], _pots[sdk.items.ManaPotion],
+ _pots[sdk.items.GreaterManaPotion], _pots[sdk.items.SuperManaPotion]
+ ];
+ /** @type {Pot[]} */
+ const _hpPots = [
+ _pots[sdk.items.MinorHealingPotion], _pots[sdk.items.LightHealingPotion], _pots[sdk.items.HealingPotion],
+ _pots[sdk.items.GreaterHealingPotion], _pots[sdk.items.SuperHealingPotion]
+ ];
+
+ Object.keys(_pots)
+ .forEach(key => Object.freeze(_pots[key]));
+ Object.freeze(_mpPots);
+ Object.freeze(_hpPots);
+
+ return {
+ pots: _pots,
+
+ getMpPots: function () {
+ return _mpPots;
+ },
+ getHpPots: function () {
+ return _hpPots;
+ },
+ };
+ })();
+
+ return PotData;
+}));
diff --git a/libs/SoloPlay/Modules/Guard.js b/libs/SoloPlay/Modules/Guard.js
index 5d54bee2..976f328b 100644
--- a/libs/SoloPlay/Modules/Guard.js
+++ b/libs/SoloPlay/Modules/Guard.js
@@ -1,119 +1,96 @@
-(function (module, require, thread) {
- const Messaging = require("../../modules/Messaging");
- const Worker = require("../../modules/Worker");
- const sdk = require("../../modules/sdk");
-
- switch (thread) {
- case "thread": {
- Worker.runInBackground.stackTrace = (new function () {
- let self = this;
- let stack;
-
- let myStack = "";
-
- // recv stack
- Messaging.on("Guard", (data => typeof data === "object" && data && data.hasOwnProperty("stack") && (myStack = data.stack)));
-
- /**
- * @constructor
- * @param {function():string} callback
- */
- function UpdateableText(callback) {
- let element = new Text(callback(), self.x + 15, self.y + (7 * self.hooks.length), 0, 12, 0);
- self.hooks.push(element);
- this.update = () => {
- element.text = callback();
- element.visible = element.visible = [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 < 20; i++) {
- (i => this.hooks.push(new UpdateableText(() => stack && stack.length > i && stack[i] || "")))(i);
- }
-
- this.update = () => {
- stack = myStack.match(/[^\r\n]+/g);
- stack = stack && stack.slice(6/*skip path to here*/).map(el => {
- let line = el.substr(el.lastIndexOf(":") + 1),
- functionName = el.substr(0, el.indexOf("@")),
- 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;
-
- Worker.runInBackground.ping = (new function () {
- let myHeartbeat = 0;
-
- // recv heartbeat
- Messaging.on("Guard", (data => typeof data === "object" && data && data.hasOwnProperty("heartbeat") && (myHeartbeat = data.heartbeat)));
-
- this.update = function () {
- // Do not deal with this shit if default is paused
- const script = getScript("default.dbj");
- if (!script || !script.running) {
- myHeartbeat = getTickCount();
- return true;
- }
- if (myHeartbeat && getTickCount() - myHeartbeat > (2 * 6e4)) {
- console.log("Default.dbj seems to have crashed");
- myHeartbeat = 0;
- if (script) script.stop();
- console.log("Waiting 10 seconds to restart default.dbj");
- delay(1e4);
- load("default.dbj");
- console.log("Starting default.dbj");
- }
- return true;
- };
- }).update;
-
- let quiting = false;
- addEventListener("scriptmsg", data => data === "quit" && (quiting = true));
-
- while (!quiting) delay(1000);
- break;
- }
- case "started": {
- 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;
- });
-
- let timer = getTickCount();
- Worker.runInBackground.heartbeatForGuard = function () {
- if ((getTickCount() - timer) < 1000 || (timer = getTickCount()) && false) return true;
-
- // Every second or so, we send a heartbeat tick
- Messaging.send({Guard: {heartbeat: getTickCount()}});
-
- return true;
- };
- break;
- }
- case "loaded": {
- break;
- }
- }
-
-}).call(null, typeof module === "object" && module || {}, typeof require === "undefined" && (include("require.js") && require) || require, getScript.startAsThread());
+(function (module, require, thread, globalThis) {
+ "use strict";
+ const _Messaging = require("../../modules/Messaging");
+ const Worker = require("../../modules/Worker");
+ const sdk = require("../../modules/sdk");
+
+ switch (thread) {
+ case "thread": {
+ Worker.runInBackground.stackTrace = (new function () {
+ let self = this;
+ let stack;
+
+ let myStack = "";
+
+ // recv stack
+ _Messaging.on("Guard", (data => typeof data === "object" && data && data.hasOwnProperty("stack") && (myStack = data.stack)));
+
+ /**
+ * @constructor
+ * @param {function():string} callback
+ */
+ function UpdateableText (callback) {
+ let element = new Text(callback(), self.x + 15, self.y + (7 * self.hooks.length), 0, 12, 0);
+ self.hooks.push(element);
+ this.update = () => {
+ element.text = callback();
+ element.visible = [
+ sdk.uiflags.Inventory,
+ sdk.uiflags.SkillWindow,
+ sdk.uiflags.TradePrompt,
+ sdk.uiflags.QuickSkill
+ ].every(function (f) {
+ return !getUIFlag(f);
+ });
+ };
+ }
+
+ this.hooks = [];
+ this.x = me.screensize ? 500 : 400;
+ this.y = (me.screensize ? 600 : 500) - (400 + (self.hooks.length * 15));
+
+ for (let i = 0; i < 22; i++) {
+ (i => this.hooks.push(new UpdateableText(() => stack && stack.length > i && stack[i] || "")))(i);
+ }
+
+ this.update = () => {
+ stack = myStack.match(/[^\r\n]+/g);
+ stack = stack && stack.slice(6/*skip path to here*/).map(el => {
+ let line = el.substr(el.lastIndexOf(":") + 1);
+ let functionName = el.substr(0, el.indexOf("@"));
+ let filename = el.substr(el.lastIndexOf("\\") + 1);
+
+ filename = filename.substr(0, filename.indexOf("."));
+
+ return filename + "ÿc::ÿc0" + line + "ÿc:@ÿc0" + functionName;
+ }).reverse();
+ this.hooks.filter(hook => hook.hasOwnProperty("update") && typeof hook.update === "function" && hook.update());
+ return true;
+ };
+
+ }).update;
+
+ let quiting = false;
+ addEventListener("scriptmsg", data => data === "quit" && (quiting = true));
+
+ // eslint-disable-next-line dot-notation
+ globalThis["main"] = function () {
+ while (!quiting) delay(3);
+ //@ts-ignore
+ getScript(true).stop();
+ };
+ break;
+ }
+ case "started": {
+ 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/libs/SoloPlay/Modules/LocaleStringID.js b/libs/SoloPlay/Modules/LocaleStringID.js
deleted file mode 100644
index 7f4f9e3d..00000000
--- a/libs/SoloPlay/Modules/LocaleStringID.js
+++ /dev/null
@@ -1,7806 +0,0 @@
-/**
-* @filename LocaleStringID.js
-* @author Nishimura-Katsuo
-* @desc locale string indexes from NameStr ids
-*/
-(function (module, require) {
- let LocaleStringID = {
- "WarrivAct1IntroGossip1": 0,
- "WarrivAct1IntroPalGossip1": 1,
- "WarrivGossip1": 2,
- "WarrivGossip2": 3,
- "WarrivGossip3": 4,
- "WarrivGossip4": 5,
- "WarrivGossip5": 6,
- "WarrivGossip6": 7,
- "WarrivGossip7": 8,
- "WarrivGossip8": 9,
- "WarrivGossip9": 10,
- "AkaraIntroGossip1": 11,
- "AkaraIntroSorGossip1": 12,
- "AkaraGossip1": 13,
- "AkaraGossip2": 14,
- "AkaraGossip3": 15,
- "AkaraGossip4": 16,
- "AkaraGossip5": 17,
- "AkaraGossip6": 18,
- "AkaraGossip7": 19,
- "AkaraGossip8": 20,
- "AkaraGossip9": 21,
- "AkaraGossip10": 22,
- "AkaraGossip11": 23,
- "KashyaIntroGossip1": 24,
- "KashyaIntroAmaGossip1": 25,
- "KashyaGossip1": 26,
- "KashyaGossip2": 27,
- "KashyaGossip3": 28,
- "KashyaGossip4": 29,
- "KashyaGossip5": 30,
- "KashyaGossip6": 31,
- "KashyaGossip7": 32,
- "KashyaGossip8": 33,
- "KashyaGossip9": 34,
- "KashyaGossip10": 35,
- "CharsiIntroGossip1": 36,
- "CharsiIntroBarGossip1": 37,
- "CharsiGossip1": 38,
- "CharsiGossip2": 39,
- "CharsiGossip3": 40,
- "CharsiGossip4": 41,
- "CharsiGossip5": 42,
- "CharsiGossip6": 43,
- "CharsiGossip7": 44,
- "GheedIntroGossip1": 45,
- "GheedIntroNecGossip1": 46,
- "GheedGossip1": 47,
- "GheedGossip2": 48,
- "GheedGossip3": 49,
- "GheedGossip4": 50,
- "GheedGossip5": 51,
- "GheedGossip6": 52,
- "GheedGossip7": 53,
- "CainGossip1": 54,
- "CainGossip2": 55,
- "CainGossip3": 56,
- "CainGossip4": 57,
- "CainGossip5": 58,
- "RogueSignpostGossip1": 59,
- "RogueSignpostGossip2": 60,
- "RogueSignpostGossip3": 61,
- "RogueSignpostGossip4": 62,
- "RogueSignpostGossip5": 63,
- "A1Q1InitAkara": 64,
- "A1Q1AfterInitAkara": 65,
- "A1Q1AfterInitKashya": 66,
- "A1Q1AfterInitCharsiMain": 67,
- "A1Q1AfterInitCharsiAlt": 68,
- "A1Q1AfterInitGheed": 69,
- "A1Q1AfterInitWarriv": 70,
- "A1Q1EarlyReturnAkara": 71,
- "A1Q1EarlyReturnKashya": 72,
- "A1Q1EarlyReturnCharsi": 73,
- "A1Q1EarlyReturnGheed": 74,
- "A1Q1EarlyReturnWarriv": 75,
- "A1Q1SuccessfulAkara": 76,
- "A1Q1SuccessfulKashya": 77,
- "A1Q1SuccessfulCharsi": 78,
- "A1Q1SuccessfulGheed": 79,
- "A1Q1SuccessfulWarriv": 80,
- "A1Q2InitKashya": 81,
- "A1Q2AfterInitKashya": 82,
- "A1Q2AfterInitCharsi": 83,
- "A1Q2AfterInitGheed": 84,
- "A1Q2AfterInitAkara": 85,
- "A1Q2AfterInitWarriv": 86,
- "A1Q2EarlyReturnKashya": 87,
- "A1Q2EarlyReturnAkara": 88,
- "A1Q2EarlyReturnCharsi": 89,
- "A1Q2EarlyReturnGheed": 90,
- "A1Q2EarlyReturnWarriv": 91,
- "A1Q2SuccessfulKashya": 92,
- "A1Q2SuccessfulAkara": 93,
- "A1Q2SuccessfulCharsi": 94,
- "A1Q2SuccessfulGheed": 95,
- "A1Q2SuccessfulWarriv": 96,
- "A1Q4InitAkara": 97,
- "A1Q4AfterInitScrollKashya": 98,
- "A1Q4AfterInitScrollAkara": 99,
- "A1Q4AfterInitScrollCharsi": 100,
- "A1Q4AfterInitScrollWarriv": 101,
- "A1Q4AfterInitScrollGheed": 102,
- "A1Q4InstructionsCharsi": 103,
- "A1Q4EarlyReturnSAkara": 104,
- "A1Q4EarlyReturnSKashya": 105,
- "A1Q4EarlyReturnSGheed": 106,
- "A1Q4EarlyReturnSWarriv": 107,
- "A1Q4SuccessfulScrollKashya": 108,
- "A1Q4SuccessfulScrollCharsi": 109,
- "A1Q4SuccessfulScrollGheed": 110,
- "A1Q4SuccessfulScrollWarriv": 111,
- "A1Q4InstructionsAkara": 112,
- "A1Q4EarlyReturnKashya": 113,
- "A1Q4EarlyReturnCharsi": 114,
- "A1Q4EarlyReturnGheed": 115,
- "A1Q4EarlyReturnWarriv": 116,
- "A1Q4EarlyReturnAkara": 117,
- "A1Q4QuestSuccessfulAkara": 118,
- "A1Q4QuestSuccessfulKashya": 119,
- "A1Q4QuestSuccessfulGheed": 120,
- "A1Q4QuestSuccessfulCharsi": 121,
- "A1Q4QuestSuccessfulWarriv": 122,
- "A1Q4QuestSuccessfulCain": 123,
- "A1Q4RescuedByHeroCain": 124,
- "A1Q4RescuedByRoguesCain": 125,
- "A1Q4TragedyOfTristramCain": 126,
- "A1Q5InitQuestTome": 127,
- "A1Q5AfterInitGheed": 128,
- "A1Q5AfterInitCharsi": 129,
- "A1Q5AfterInitAkara": 130,
- "A1Q5AfterInitCain": 131,
- "A1Q5AfterInitWarriv": 132,
- "A1Q5AfterInitKashya": 133,
- "A1Q5EarlyReturnKashya": 134,
- "A1Q5EarlyReturnCain": 135,
- "A1Q5EarlyReturnWarriv": 136,
- "A1Q5EarlyReturnCharsi": 137,
- "A1Q5EarlyReturnAkara": 138,
- "A1Q5EarlyReturnGheed": 139,
- "A1Q5SuccessfulKashya": 140,
- "A1Q5SuccessfulWarriv": 141,
- "A1Q5SuccessfulGheed": 142,
- "A1Q5SuccessfulAkara": 143,
- "A1Q5SuccessfulCharsi": 144,
- "A1Q5SuccessfulCain": 145,
- "A1Q3InitCharsi": 146,
- "A1Q3AfterInitCain": 147,
- "A1Q3AfterInitAkara": 148,
- "A1Q3AfterInitKashya": 149,
- "A1Q3AfterInitCharsi": 150,
- "A1Q3AfterInitGheed": 151,
- "A1Q3AfterInitGheedAlt": 152,
- "A1Q3AfterInitWarriv": 153,
- "A1Q3EarlyReturnCain": 154,
- "A1Q3EarlyReturnAkara": 155,
- "A1Q3EarlyReturnKashya": 156,
- "A1Q3EarlyReturnCharsi": 157,
- "A1Q3EarlyReturnGheed": 158,
- "A1Q3EarlyReturnWarriv": 159,
- "A1Q3SuccessfulCain": 160,
- "A1Q3SuccessfulAkara": 161,
- "A1Q3SuccessfulKashya": 162,
- "A1Q3SuccessfulCharsi": 163,
- "A1Q3SuccessfulGheed": 164,
- "A1Q3SuccessfulWarriv": 165,
- "A1Q6InitCain": 166,
- "A1Q6AfterInitCain": 167,
- "A1Q6AfterInitAkara": 168,
- "A1Q6AfterInitCharsi": 169,
- "A1Q6AfterInitGheed": 170,
- "A1Q6AfterInitWarriv": 171,
- "A1Q6AfterInitKashya": 172,
- "A1Q6EarlyReturnCain": 173,
- "A1Q6EarlyReturnAkara": 174,
- "A1Q6EarlyReturnGheed": 175,
- "A1Q6EarlyReturnCharsi": 176,
- "A1Q6EarlyReturnWarriv": 177,
- "A1Q6EarlyReturn2Kashya": 178,
- "A1Q6SuccessfulAkara": 179,
- "A1Q6SuccessfulCharsi": 180,
- "A1Q6SuccessfulKashya": 181,
- "A1Q6SuccessfulGheed": 182,
- "A1Q6SuccessfulWarriv": 183,
- "A1Q6SuccessfulCain": 184,
- "PalaceGuardGossip1": 185,
- "PalaceGuardGossip2": 186,
- "PalaceGuardGossip3": 187,
- "PalaceGuardGossip4": 188,
- "PalaceGuardGossip5": 189,
- "GriezIntroGossip1": 190,
- "GriezGossip1": 191,
- "GriezGossip2": 192,
- "GriezGossip3": 193,
- "GriezGossip4": 194,
- "GriezGossip5": 195,
- "GriezGossip6": 196,
- "GriezGossip7": 197,
- "GriezGossip8": 198,
- "GriezGossip9": 199,
- "GriezGossip10": 200,
- "GriezGossip11": 201,
- "GriezGossip12": 202,
- "ElzixIntroGossip1": 203,
- "ElzixIntroNecGossip1": 204,
- "ElzixGossip1": 205,
- "ElzixGossip2": 206,
- "ElzixGossip3": 207,
- "ElzixGossip4": 208,
- "ElzixGossip5": 209,
- "ElzixGossip6": 210,
- "ElzixGossip7": 211,
- "ElzixGossip8": 212,
- "ElzixGossip9": 213,
- "ElzixGossip10": 214,
- "WarrivAct2IntroGossip1": 215,
- "WarrivAct2Gossip1": 216,
- "WarrivAct2Gossip2": 217,
- "WarrivAct2Gossip3": 218,
- "WarrivAct2Gossip4": 219,
- "WarrivAct2Gossip5": 220,
- "AtmaIntroGossip1": 221,
- "AtmaGossip1": 222,
- "AtmaGossip2": 223,
- "AtmaGossip3": 224,
- "AtmaGossip4": 225,
- "AtmaGossip5": 226,
- "AtmaGossip6": 227,
- "AtmaGossip7": 228,
- "AtmaGossip8": 229,
- "GeglashIntroGossip1": 230,
- "GeglashIntroBarGossip1": 231,
- "GeglashGossip1": 232,
- "GeglashGossip2": 233,
- "GeglashGossip3": 234,
- "GeglashGossip4": 235,
- "GeglashGossip5": 236,
- "GeglashGossip6": 237,
- "GeglashGossip7": 238,
- "GeglashGossip8": 239,
- "GeglashGossip9": 240,
- "MeshifIntroGossip1": 241,
- "MeshifIntroAmaGossip1": 242,
- "MeshifGossip1": 243,
- "MeshifGossip2": 244,
- "MeshifGossip3": 245,
- "MeshifGossip4": 246,
- "MeshifGossip5": 247,
- "MeshifGossip6": 248,
- "MeshifGossip7": 249,
- "MeshifGossip8": 250,
- "MeshifGossip9": 251,
- "MeshifGossip10": 252,
- "JerhynActIntroGossip1": 253,
- "JerhynActIntroMoreGossip1": 254,
- "JerhynIntroGossip1": 255,
- "JerhynGossip1": 256,
- "JerhynGossip2": 257,
- "JerhynGossip3": 258,
- "JerhynGossip4": 259,
- "JerhynGossip5": 260,
- "JerhynGossip6": 261,
- "JerhynGossip7": 262,
- "FaraIntroGossip1": 263,
- "FaraIntroPalGossip1": 264,
- "FaraGossip1": 265,
- "FaraGossip2": 266,
- "FaraGossip3": 267,
- "FaraGossip4": 268,
- "FaraGossip5": 269,
- "FaraGossip6": 270,
- "FaraGossip7": 271,
- "FaraGossip8": 272,
- "FaraGossip9": 273,
- "LysanderIntroGossip1": 274,
- "LysanderGossip1": 275,
- "LysanderGossip2": 276,
- "LysanderGossip3": 277,
- "LysanderGossip4": 278,
- "LysanderGossip5": 279,
- "LysanderGossip6": 280,
- "LysanderGossip7": 281,
- "LysanderGossip8": 282,
- "LysanderGossip9": 283,
- "LysanderGossip10": 284,
- "DrognanIntroGossip1": 285,
- "DrognanIntroSorGossip1": 286,
- "DrognanGossip1": 287,
- "DrognanGossip2": 288,
- "DrognanGossip3": 289,
- "DrognanGossip4": 290,
- "DrognanGossip5": 291,
- "DrognanGossip6": 292,
- "DrognanGossip7": 293,
- "DrognanGossip8": 294,
- "DrognanGossip9": 295,
- "DrognanGossip10": 296,
- "CainAct2Gossip1": 297,
- "CainAct2Gossip2": 298,
- "CainAct2Gossip3": 299,
- "CainAct2Gossip4": 300,
- "CainAct2Gossip5": 301,
- "TyraelGossip1": 302,
- "Desert2GuardGossip1": 303,
- "A2Q1InitAtma": 304,
- "A2Q1AfterInitGreiz": 305,
- "A2Q1AfterInitElzix": 306,
- "A2Q1AfterInitWarrivAct2": 307,
- "A2Q1AfterInitGeglash": 308,
- "A2Q1AfterInitFara": 309,
- "A2Q1AfterInitAtma": 310,
- "A2Q1AfterInitMeshif": 311,
- "A2Q1AfterInitDrognan": 312,
- "A2Q1AfterInitLysander": 313,
- "A2Q1AfterInitCain": 314,
- "A2Q1EarlyReturnWarrivAct2": 315,
- "A2Q1EarlyReturnMeshif": 316,
- "A2Q1EarlyReturnAtma": 317,
- "A2Q1EarlyReturnGreiz": 318,
- "A2Q1EarlyReturnGeglash": 319,
- "A2Q1EarlyReturnElzix": 320,
- "A2Q1EarlyReturnLysander": 321,
- "A2Q1EarlyReturnDrognan": 322,
- "A2Q1EarlyReturnFara": 323,
- "A2Q1EarlyReturnCain": 324,
- "A2Q1SuccessfulGreiz": 325,
- "A2Q1SuccessfulDrognan": 326,
- "A2Q1SuccessfulLysander": 327,
- "A2Q1SuccessfulMeshif": 328,
- "A2Q1SuccessfulGeglash": 329,
- "A2Q1SuccessfulElzix": 330,
- "A2Q1SuccessfulWarrivAct2": 331,
- "A2Q1SuccessfulFara": 332,
- "A2Q1SuccessfulCain": 333,
- "A2Q1SuccessfulAtma": 334,
- "A2Q2EarlyReturnScrollCain": 335,
- "A2Q2EarlyReturnCapCain": 336,
- "A2Q2EarlyReturnStaveCain": 337,
- "A2Q2EarlyReturnCubeCain": 338,
- "A2Q2SuccessfulStaffCain": 339,
- "A2Q3AfterInitJerhyn": 340,
- "A2Q3AfterInitGreiz": 341,
- "A2Q3AfterInitElzix": 342,
- "A2Q3AfterInitWarrivAct2": 343,
- "A2Q3AfterInitAtma": 344,
- "A2Q3AfterInitGeglash": 345,
- "A2Q3AfterInitFara": 346,
- "A2Q3AfterInitLysander": 347,
- "A2Q3AfterInitDrognan": 348,
- "A2Q3AfterInitMeshif": 349,
- "A2Q3AfterInitCain": 350,
- "A2Q3EarlyReturnJerhyn": 351,
- "A2Q3EarlyReturnGreiz": 352,
- "A2Q3EarlyReturnWarrivAct2": 353,
- "A2Q3EarlyReturnGeglash": 354,
- "A2Q3EarlyReturnMeshif": 355,
- "A2Q3EarlyReturnFara": 356,
- "A2Q3EarlyReturnLysander": 357,
- "A2Q3EarlyReturnDrognan": 358,
- "A2Q3EarlyReturnElzix": 359,
- "A2Q3EarlyReturnCain": 360,
- "A2Q3EarlyReturnAtma": 361,
- "A2Q3SuccessfulJerhyn": 362,
- "A2Q3SuccessfulGreiz": 363,
- "A2Q3SuccessfulElzix": 364,
- "A2Q3SuccessfulGeglash": 365,
- "A2Q3SuccessfulWarrivAct2": 366,
- "A2Q3SuccessfulMeshif": 367,
- "A2Q3SuccessfulAtma": 368,
- "A2Q3SuccessfulFara": 369,
- "A2Q3SuccessfulLysander": 370,
- "A2Q3SuccessfulDrognan": 371,
- "A2Q3SuccessfulCain": 372,
- "A2Q4InitDrognan": 373,
- "A2Q4AfterInitFara": 374,
- "A2Q4AfterInitGreiz": 375,
- "A2Q4AfterInitElzix": 376,
- "A2Q4AfterInitJerhyn": 377,
- "A2Q4AfterInitCain": 378,
- "A2Q4AfterInitGeglash": 379,
- "A2Q4AfterInitAtma": 380,
- "A2Q4AfterInitWarrivAct2": 381,
- "A2Q4AfterInitLysander": 382,
- "A2Q4AfterInitDrognan": 383,
- "A2Q4AfterInitMeshif": 384,
- "A2Q4EarlyReturnElzix": 385,
- "A2Q4EarlyReturnJerhyn": 386,
- "A2Q4EarlyReturnGreiz": 387,
- "A2Q4EarlyReturnDrognan": 388,
- "A2Q4EarlyReturnLysander": 389,
- "A2Q4EarlyReturnFara": 390,
- "A2Q4EarlyReturnGeglash": 391,
- "A2Q4EarlyReturnMeshif": 392,
- "A2Q4EarlyReturnAtma": 393,
- "A2Q4EarlyReturnWarrivAct2": 394,
- "A2Q4EarlyReturnCain": 395,
- "A2Q4SuccessfulNarrator": 396,
- "A2Q4SuccessfulGriez": 397,
- "A2Q4SuccessfulJerhyn": 398,
- "A2Q4SuccessfulDrognan": 399,
- "A2Q4SuccessfulElzix": 400,
- "A2Q4SuccessfulGeglash": 401,
- "A2Q4SuccessfulMeshif": 402,
- "A2Q4SuccessfulWarrivAct2": 403,
- "A2Q4SuccessfulFara": 404,
- "A2Q4SuccessfulLysander": 405,
- "A2Q4SuccessfulAtma": 406,
- "A2Q4SuccessfulCain": 407,
- "A2Q5EarlyReturnGreiz": 408,
- "A2Q5EarlyReturnJerhyn": 409,
- "A2Q5EarlyReturnDrognan": 410,
- "A2Q5EarlyReturnLysander": 411,
- "A2Q5EarlyReturnMeshif": 412,
- "A2Q5EarlyReturnWarrivAct2": 413,
- "A2Q5EarlyReturnAtma": 414,
- "A2Q5EarlyReturnGeglash": 415,
- "A2Q5EarlyReturnFara": 416,
- "A2Q5EarlyReturnElzix": 417,
- "A2Q5EarlyReturnCain": 418,
- "A2Q5SuccessfulGreiz": 419,
- "A2Q5SuccessfulGeglash": 420,
- "A2Q5SuccessfulJerhyn": 421,
- "A2Q5SuccessfulDrognan": 422,
- "A2Q5SuccessfulElzix": 423,
- "A2Q5SuccessfulWarrivAct2": 424,
- "A2Q5SuccessfulMeshif": 425,
- "A2Q5SuccessfulLysander": 426,
- "A2Q5SuccessfulAtma": 427,
- "A2Q5SuccessfulFara": 428,
- "A2Q5SuccessfulCain": 429,
- "A2Q6InitJerhyn": 430,
- "A2Q6AfterInitJerhyn": 431,
- "A2Q6AfterInitElzix": 432,
- "A2Q6AfterInitWarrivAct2": 433,
- "A2Q6AfterInitAtma": 434,
- "A2Q6AfterInitGeglash": 435,
- "A2Q6AfterInitMeshif": 436,
- "A2Q6AfterInitFara": 437,
- "A2Q6AfterInitLysander": 438,
- "A2Q6AfterInitDrognan": 439,
- "A2Q6AfterInitCain": 440,
- "A2Q6AfterInitGreiz": 441,
- "A2Q6SuccessfulJerhyn": 442,
- "A2Q6SuccessfulElzix": 443,
- "A2Q6SuccessfulLysander": 444,
- "A2Q6SuccessfulAtma": 445,
- "A2Q6SuccessfulWarrivAct2": 446,
- "A2Q6SuccessfulFara": 447,
- "A2Q6SuccessfulGeglash": 448,
- "A2Q6SuccessfulDrognan": 449,
- "A2Q6SuccessfulMeshif": 450,
- "A2Q6SuccessfulGreiz": 451,
- "A2Q6SuccessfulCain": 452,
- "NatalyaIntroGossip1": 453,
- "NatalyaGossip1": 454,
- "NatalyaGossip2": 455,
- "NatalyaGossip3": 456,
- "NatalyaGossip4": 457,
- "CainAct3IntroGossip1": 458,
- "CainAct3Gossip1": 459,
- "CainAct3Gossip2": 460,
- "CainAct3Gossip3": 461,
- "CainAct3Gossip4": 462,
- "CainAct3Gossip5": 463,
- "CainAct3Gossip6": 464,
- "HratliActIntroGossip1": 465,
- "HratliActIntroSorGossip1": 466,
- "HratliGossip1": 467,
- "HratliGossip2": 468,
- "HratliGossip3": 469,
- "HratliGossip4": 470,
- "HratliGossip5": 471,
- "HratliGossip6": 472,
- "HratliGossip7": 473,
- "HratliGossip8": 474,
- "HratliGossip9": 475,
- "HratliGossip10": 476,
- "HratliGossip11": 477,
- "MeshifAct3IntroGossip1": 478,
- "MeshifAct3IntroBarGossip1": 479,
- "MeshifAct3Gossip1": 480,
- "MeshifAct3Gossip2": 481,
- "MeshifAct3Gossip3": 482,
- "MeshifAct3Gossip4": 483,
- "MeshifAct3Gossip5": 484,
- "MeshifAct3Gossip6": 485,
- "MeshifAct3Gossip7": 486,
- "MeshifAct3Gossip8": 487,
- "MeshifAct3Gossip9": 488,
- "MeshifAct3Gossip10": 489,
- "AshearaIntroGossip1": 490,
- "AshearaIntroAmaGossip1": 491,
- "AshearaGossip1": 492,
- "AshearaGossip2": 493,
- "AshearaGossip3": 494,
- "AshearaGossip4": 495,
- "AshearaGossip5": 496,
- "AshearaGossip6": 497,
- "AshearaGossip7": 498,
- "AshearaGossip8": 499,
- "AshearaGossip9": 500,
- "AlkorIntroGossip1": 501,
- "AlkorIntroNecGossip1": 502,
- "AlkorGossip1": 503,
- "AlkorGossip2": 504,
- "AlkorGossip3": 505,
- "AlkorGossip4": 506,
- "AlkorGossip5": 507,
- "AlkorGossip6": 508,
- "AlkorGossip7": 509,
- "AlkorGossip8": 510,
- "AlkorGossip9": 511,
- "AlkorGossip10": 512,
- "AlkorGossip11": 513,
- "OrmusIntroGossip1": 514,
- "OrmusIntroPalGossip1": 515,
- "OrmusGossip1": 516,
- "OrmusGossip2": 517,
- "OrmusGossip3": 518,
- "OrmusGossip4": 519,
- "OrmusGossip5": 520,
- "OrmusGossip6": 521,
- "OrmusGossip7": 522,
- "OrmusGossip8": 523,
- "OrmusGossip9": 524,
- "OrmusGossip10": 525,
- "OrmusGossip11": 526,
- "A3Q4Init1CainAct3": 527,
- "A3Q4Init1Asheara": 528,
- "A3Q4Init2MeshifAct3": 529,
- "A3Q4Init2Natalya": 530,
- "A3Q4Init3CainAct3": 531,
- "A3Q4Init3Hratli": 532,
- "A3Q4Init3Asheara": 533,
- "A3Q4AfterInitAlkor": 534,
- "A3Q4AfterInitOrmus": 535,
- "A3Q4AfterInitHratli": 536,
- "A3Q4AfterInitNatalya": 537,
- "A3Q4SuccessfulAlkor": 538,
- "A3Q4SuccessfulMeshifAct3": 539,
- "A3Q4SuccessfulCainAct3": 540,
- "A3Q4SuccessfulOrmus": 541,
- "A3Q4SuccessfulNatalya": 542,
- "A3Q2InitCain": 543,
- "A3Q2EarlyReturnHeartCain": 544,
- "A3Q2EarlyReturnEyeCain": 545,
- "A3Q2EarlyReturnBrainCain": 546,
- "A3Q2EarlyReturnFlailCain": 547,
- "A3Q2SuccessfulCain": 548,
- "A3Q1InitAlkor": 549,
- "A3Q1AfterInitAlkor": 550,
- "A3Q1AfterInitOrmus": 551,
- "A3Q1AfterInitMeshifAct3": 552,
- "A3Q1AfterInitAsheara": 553,
- "A3Q1AfterInitHratli": 554,
- "A3Q1AfterInitCainAct3": 555,
- "A3Q1AfterInitNatalya": 556,
- "A3Q1EarlyReturnAlkor": 557,
- "A3Q1EarlyReturnOrmus": 558,
- "A3Q1EarlyReturnMeshifAct3": 559,
- "A3Q1EarlyReturnAsheara": 560,
- "A3Q1EarlyReturnHratli": 561,
- "A3Q1EarlyReturnCainAct3": 562,
- "A3Q1EarlyReturnNatalya": 563,
- "A3Q1SuccessfulAlkor": 564,
- "A3Q1SuccessfulOrmus": 565,
- "A3Q1SuccessfulMeshifAct3": 566,
- "A3Q1SuccessfulAsheara": 567,
- "A3Q1SuccessfulHratli": 568,
- "A3Q1SuccessfulCainAct3": 569,
- "A3Q1SuccessfulNatalya": 570,
- "A3Q3InitHratli": 571,
- "A3Q3AfterInitAlkor": 572,
- "A3Q3AfterInitOrmus": 573,
- "A3Q3AfterInitMeshifAct3": 574,
- "A3Q3AfterInitAsheara": 575,
- "A3Q3AfterInitHratli": 576,
- "A3Q3AfterInitCainAct3": 577,
- "A3Q3AfterInitNatalya": 578,
- "A3Q3EarlyReturnAlkor": 579,
- "A3Q3EarlyReturnOrmus": 580,
- "A3Q3EarlyReturnMeshifAct3": 581,
- "A3Q3EarlyReturnAsheara": 582,
- "A3Q3EarlyReturnHratli": 583,
- "A3Q3EarlyReturnCainAct3": 584,
- "A3Q3EarlyReturnNatalya": 585,
- "A3Q3SuccessfulAlkor": 586,
- "A3Q3SuccessfulOrmus": 587,
- "A3Q3SuccessfulMeshifAct3": 588,
- "A3Q3SuccessfulAsheara": 589,
- "A3Q3SuccessfulHratli": 590,
- "A3Q3SuccessfulCainAct3": 591,
- "A3Q3SuccessfulNatalya": 592,
- "A3Q3RewardOrmus": 593,
- "A3Q5InitOrmus": 594,
- "A3Q5AfterInitAlkor": 595,
- "A3Q5AfterInitAlkorVA": 596,
- "A3Q5AfterInitOrmus": 597,
- "A3Q5AfterInitOrmusVA": 598,
- "A3Q5AfterInitMeshifAct3": 599,
- "A3Q5AfterInitMeshifAct3VA": 600,
- "A3Q5AfterInitAsheara": 601,
- "A3Q5AfterInitAshearaVA": 602,
- "A3Q5AfterInitHratli": 603,
- "A3Q5AfterInitHratliVA": 604,
- "A3Q5AfterInitCainAct3": 605,
- "A3Q5AfterInitCainAct3VA": 606,
- "A3Q5AfterInitNatalya": 607,
- "A3Q5AfterInitNatalyaVA": 608,
- "A3Q5EarlyReturnAlkor": 609,
- "A3Q5EarlyReturnAlkorVA": 610,
- "A3Q5EarlyReturnOrmus": 611,
- "A3Q5EarlyReturnMeshifAct3": 612,
- "A3Q5EarlyReturnMeshifAct3VA": 613,
- "A3Q5EarlyReturnAsheara": 614,
- "A3Q5EarlyReturnAshearaVA": 615,
- "A3Q5EarlyReturnHratli": 616,
- "A3Q5EarlyReturnHratliVA": 617,
- "A3Q5EarlyReturnCainAct3": 618,
- "A3Q5EarlyReturnNatalya": 619,
- "A3Q5EarlyReturnNatalyaVA": 620,
- "A3Q5SuccessfulAlkor": 621,
- "A3Q5SuccessfulOrmus": 622,
- "A3Q5SuccessfulMeshifAct3": 623,
- "A3Q5SuccessfulAsheara": 624,
- "A3Q5SuccessfulHratli": 625,
- "A3Q5SuccessfulCainAct3": 626,
- "A3Q5SuccessfulNatalya": 627,
- "A3Q6InitOrmus": 628,
- "A3Q6AfterInitAlkor": 629,
- "A3Q6AfterInitAlkorVA": 630,
- "A3Q6AfterInitOrmus": 631,
- "A3Q6AfterInitOrmusVA": 632,
- "A3Q6AfterInitMeshifAct3": 633,
- "A3Q6AfterInitMeshifAct3VA": 634,
- "A3Q6AfterInitAsheara": 635,
- "A3Q6AfterInitAshearaVA": 636,
- "A3Q6AfterInitHratli": 637,
- "A3Q6AfterInitHratliVA": 638,
- "A3Q6AfterInitCainAct3": 639,
- "A3Q6AfterInitCainAct3VA": 640,
- "A3Q6AfterInitNatalya": 641,
- "A3Q6AfterInitNatalyaVA": 642,
- "A3Q6EarlyReturnAlkor": 643,
- "A3Q6EarlyReturnAlkorVA": 644,
- "A3Q6EarlyReturnOrmus": 645,
- "A3Q6EarlyReturnOrmusVA": 646,
- "A3Q6EarlyReturnMeshifAct3": 647,
- "A3Q6EarlyReturnMeshifAct3VA": 648,
- "A3Q6EarlyReturnAsheara": 649,
- "A3Q6EarlyReturnAshearaVA": 650,
- "A3Q6EarlyReturnHratli": 651,
- "A3Q6EarlyReturnHratliVA": 652,
- "A3Q6EarlyReturnCainAct3": 653,
- "A3Q6EarlyReturnCainAct3VA": 654,
- "A3Q6EarlyReturnNatalya": 655,
- "A3Q6EarlyReturnNatalyaVA": 656,
- "A3Q6SuccessfulAlkor": 657,
- "A3Q6SuccessfulOrmus": 658,
- "A3Q6SuccessfulMeshifAct3": 659,
- "A3Q6SuccessfulAsheara": 660,
- "A3Q6SuccessfulHratli": 661,
- "A3Q6SuccessfulCainAct3": 662,
- "A3Q6SuccessfulNatalya": 663,
- "TyraelActIntroGossip1": 664,
- "TyraelAct4Gossip1": 665,
- "CainAct4IntroGossip1": 666,
- "CainAct4Gossip1": 667,
- "HellsAngelGossip1": 668,
- "HellsAngelGossip2": 669,
- "A4Q1InitTyrael": 670,
- "A4Q1AfterInitTyrael": 671,
- "A4Q1AfterInitCain": 672,
- "A4Q1EarlyReturnTyrael": 673,
- "A4Q1EarlyReturnCain": 674,
- "A4Q1SuccessfulIzual": 675,
- "A4Q1SuccessfulTyrael": 676,
- "A4Q1SuccessfulCain": 677,
- "A4Q3InitHasStoneCain": 678,
- "A4Q3InitNoStoneCain": 679,
- "A4Q3SuccessfulCain": 680,
- "A4Q2InitTyrael": 681,
- "A4Q2AfterInitCain": 682,
- "A4Q2AfterInitTyrael": 683,
- "A4Q2SuccessfulTyrael": 684,
- "A4Q2SuccessfulCain": 685,
- "D2bnetHelp50": 686,
- "D2bnetHelp": 687,
- "D2bnetHelp2a": 688,
- "D2bnetHelpa": 689,
- "D2bnetHelp1": 690,
- "D2bnetHelp2": 691,
- "D2bnetHelp3": 692,
- "D2bnetHelp4": 693,
- "D2bnetHelp5": 694,
- "D2bnetHelp5a": 695,
- "D2bnetHelp6": 696,
- "D2bnetHelp7": 697,
- "D2bnetHelp8": 698,
- "D2bnetHelp9": 699,
- "D2bnetHelp10": 700,
- "D2bnetHelp11": 701,
- "D2bnetHelp36": 702,
- "D2bnetHelp36a": 703,
- "D2bnetHelp37": 704,
- "D2bnetHelp37a": 705,
- "D2bnetHelp38": 706,
- "D2bnetHelp39": 707,
- "D2bnetHelp40": 708,
- "D2bnetHelp41": 709,
- "D2bnetHelp42": 710,
- "D2bnetHelp42a": 711,
- "D2bnetHelp43": 712,
- "D2bnetHelp44": 713,
- "D2bnetHelp44ab": 714,
- "D2bnetHelp44a": 715,
- "D2bnetHelp45": 716,
- "D2bnetHelp45b": 717,
- "D2bnetHelp45a": 718,
- "D2bnetHel46": 719,
- "D2bnetHelp46a": 720,
- "D2bnetHelp47": 721,
- "D2bnetHelp48": 722,
- "D2bnetHelp49": 723,
- "D2bnetHelp12": 724,
- "D2bnetHelp12c": 725,
- "D2bnetHelp12b": 726,
- "D2bnetHelp12a": 727,
- "D2bnetHelp13": 728,
- "D2bnetHelp13b": 729,
- "D2bnetHelp13a": 730,
- "D2bnetHelp14": 731,
- "D2bnetHelp14a": 732,
- "D2bnetHelp15": 733,
- "D2bnetHelp15b": 734,
- "D2bnetHelp15a": 735,
- "D2bnetHelp16": 736,
- "D2bnetHelp16b": 737,
- "D2bnetHelp16a": 738,
- "D2bnetHelp17": 739,
- "D2bnetHelp17a": 740,
- "D2bnetHelp18": 741,
- "D2bnetHelp18a": 742,
- "D2bnetHelp19": 743,
- "D2bnetHelp19a": 744,
- "D2bnetHelp20": 745,
- "D2bnetHelp20a": 746,
- "D2bnetHelp21": 747,
- "D2bnetHelp21a": 748,
- "D2bnetHelp22": 749,
- "D2bnetHelp22a": 750,
- "D2bnetHelp23": 751,
- "D2bnetHelp23a": 752,
- "D2bnetHelp24": 753,
- "D2bnetHelp24a": 754,
- "D2bnetHelp25": 755,
- "D2bnetHelp25a": 756,
- "D2bnetHelp26": 757,
- "D2bnetHelp26b": 758,
- "D2bnetHelp26a": 759,
- "D2bnetHelp27": 760,
- "D2bnetHelp27a": 761,
- "D2bnetHelp28": 762,
- "D2bnetHelp28a": 763,
- "D2bnetHelp29": 764,
- "D2bnetHelp29a": 765,
- "D2bnetHelp30": 766,
- "D2bnetHelp30a": 767,
- "D2bnetHelp31": 768,
- "D2bnetHelp31a": 769,
- "D2bnetHelp32": 770,
- "D2bnetHelp32a": 771,
- "D2bnetHelp33": 772,
- "D2bnetHelp34": 773,
- "D2bnetHelp35": 774,
- "D2bnetHelp51": 775,
- "D2bnetHelp52": 776,
- "D2bnetHelp53": 777,
- "D2bnetHelp54": 778,
- "D2bnetHelp55": 779,
- "D2bnetHelp56": 780,
- "D2bnetHelp57": 781,
- "D2bnetHelp58": 782,
- "D2bnetHelp59": 783,
- "D2bnetHelp60": 784,
- "D2bnetHelp61": 785,
- "D2bnetHelp62": 786,
- "D2bnetHelp63": 787,
- "Moo Moo Farm": 788,
- "Chaos Sanctum": 789,
- "The Pandemonium Fortress": 790,
- "River of Flame": 791,
- "Outer Steppes": 792,
- "Plains of Despair": 793,
- "City of the Damned": 794,
- "Durance of Hate Level 3": 795,
- "Durance of Hate Level 2": 796,
- "Durance of Hate Level 1": 797,
- "Disused Reliquary": 798,
- "Ruined Fane": 799,
- "Forgotten Temple": 800,
- "Forgotten Reliquary": 801,
- "Disused Fane": 802,
- "Ruined Temple": 803,
- "Flayer Dungeon Level 3": 804,
- "Flayer Dungeon Level 2": 805,
- "Flayer Dungeon Level 1": 806,
- "Swampy Pit Level 3": 807,
- "Swampy Pit Level 2": 808,
- "Swampy Pit Level 1": 809,
- "Spider Cave": 810,
- "Spider Cavern": 811,
- "Travincal": 812,
- "Kurast Causeway": 813,
- "Upper Kurast": 814,
- "Kurast Bazaar": 815,
- "Lower Kurast": 816,
- "Flayer Jungle": 817,
- "Great Marsh": 818,
- "Spider Forest": 819,
- "Kurast Docktown": 820,
- "Durance of Hate": 821,
- "Flayer Dungeon": 822,
- "Swampy Pit": 823,
- "Arcane Sanctuary": 824,
- "Duriel's Lair": 825,
- "Tal Rasha's Tomb": 826,
- "Ancient Tunnels": 827,
- "Maggot Lair Level 3": 828,
- "Maggot Lair Level 2": 829,
- "Maggot Lair Level 1": 830,
- "Claw Viper Temple Level 2": 831,
- "Halls of the Dead Level 3": 832,
- "Stony Tomb Level 2": 833,
- "Claw Viper Temple Level 1": 834,
- "Halls of the Dead Level 2": 835,
- "Halls of the Dead Level 1": 836,
- "Stony Tomb Level 1": 837,
- "Palace Cellar Level 3": 838,
- "Palace Cellar Level 2": 839,
- "Palace Cellar Level 1 \tPalace Cellar Level 1": 840,
- "Harem Level 2": 841,
- "Harem Level 1": 842,
- "Sewers Level 3": 843,
- "Sewers Level 2": 844,
- "Sewers Level 1": 845,
- "Canyon of the Magi": 846,
- "Valley of Snakes": 847,
- "Lost City": 848,
- "Far Oasis": 849,
- "Dry Hills": 850,
- "Rocky Waste": 851,
- "Lut Gholein": 852,
- "Maggot Lair": 853,
- "Claw Viper Temple": 854,
- "Halls of the Dead": 855,
- "Stony Tomb": 856,
- "Palace Cellar": 857,
- "Harem": 858,
- "Sewers": 859,
- "To The Moo Moo Farm": 860,
- "To Chaos Sanctum": 861,
- "To The River of Flame": 862,
- "To The Outer Steppes": 863,
- "To The Plains of Despair": 864,
- "To The City of the Damned": 865,
- "To The Pandemonium Fortress": 866,
- "To The Durance of Hate Level 3": 867,
- "To The Durance of Hate Level 2": 868,
- "To The Durance of Hate Level 1": 869,
- "To The Disused Reliquary": 870,
- "To The Ruined Fane": 871,
- "To The Forgotten Temple": 872,
- "To The Forgotten Reliquary": 873,
- "To The Disused Fane": 874,
- "To The Ruined Temple": 875,
- "To The Flayer Dungeon Level 1": 876,
- "To The Flayer Dungeon Level 2": 877,
- "To The Flayer Dungeon Level 3": 878,
- "To The Swampy Pit Level 3": 879,
- "To The Swampy Pit Level 2": 880,
- "To The Swampy Pit Level 1": 881,
- "To The Spider Cave": 882,
- "To The Spider Cavern": 883,
- "To Travincal": 884,
- "To The Kurast Causeway": 885,
- "To Upper Kurast": 886,
- "To The Kurast Bazaar": 887,
- "To Lower Kurast": 888,
- "To The Flayer Jungle": 889,
- "To The Great Marsh": 890,
- "To The Spider Forest": 891,
- "To The Kurast Docktown": 892,
- "To The Arcane Sanctuary": 893,
- "To Duriel's Lair": 894,
- "To Tal Rasha's Tomb": 895,
- "To The Ancient Tunnels": 896,
- "To The Maggot Lair Level 3": 897,
- "To The Maggot Lair Level 2": 898,
- "To The Maggot Lair Level 1": 899,
- "To The Claw Viper Temple Level 2": 900,
- "To The Halls of the Dead Level 3": 901,
- "To The Stony Tomb Level 2": 902,
- "To The Claw Viper Temple Level 1": 903,
- "To The Halls of the Dead Level 2": 904,
- "To The Halls of the Dead Level 1": 905,
- "To The Stony Tomb Level 1": 906,
- "To The Palace Cellar Level 3": 907,
- "To The Palace Cellar Level 2": 908,
- "To The Palace Cellar Level 1 \tTo The Palace Cellar Level 1 ": 909,
- "To The Harem Level 2": 910,
- "To The Harem Level 1": 911,
- "To The Sewers Level 3": 912,
- "To The Sewers Level 2": 913,
- "To The Sewers Level 1": 914,
- "To The Canyon of the Magi": 915,
- "To The Valley of Snakes": 916,
- "To The Lost City": 917,
- "To The Far Oasis": 918,
- "To The Dry Hills": 919,
- "To The Rocky Waste": 920,
- "To Lut Gholein": 921,
- "qstsa2q0": 922,
- "qstsa2q1": 923,
- "qstsa2q2": 924,
- "qstsa2q3": 925,
- "qstsa2q4": 926,
- "qstsa2q5": 927,
- "qstsa2q6": 928,
- "qstsa3q0": 929,
- "qstsa3q1": 930,
- "qstsa3q2": 931,
- "qstsa3q3": 932,
- "qstsa3q4": 933,
- "qstsa3q5": 934,
- "qstsa3q6": 935,
- "qstsa4q0": 936,
- "qstsa4q1": 937,
- "qstsa4q2": 938,
- "qstsa4q3": 939,
- "qstsa2q01": 940,
- "qstsa2q11": 941,
- "qstsa2q12": 942,
- "qstsa2q13": 943,
- "qstsa2q21": 944,
- "qstsa2q22": 945,
- "qstsa2q23": 946,
- "qstsa2q24": 947,
- "qstsa2q25": 948,
- "qstsa2q31": 949,
- "qstsa2q31a": 950,
- "qstsa2q32": 951,
- "qstsa2q33": 952,
- "qstsa2q41": 953,
- "qstsa2q41a": 954,
- "qstsa2q42": 955,
- "qstsa2q43": 956,
- "qstsa2q51": 957,
- "qstsa2q52": 958,
- "qstsa2q53": 959,
- "qstsa2q61": 960,
- "qstsa2q61a": 961,
- "qstsa2q62": 962,
- "qstsa2q63": 963,
- "qstsa2q63a": 964,
- "qstsa2q64": 965,
- "qstsa2q65": 966,
- "qstsa3q01": 967,
- "qstsa3q11": 968,
- "qstsa3q12": 969,
- "qstsa3q21": 970,
- "qstsa3q22": 971,
- "qstsa3q23": 972,
- "qstsa3q24": 973,
- "qstsa3q25": 974,
- "qstsa3q26": 975,
- "qstsa3q21a": 976,
- "qstsa3q31": 977,
- "qstsa3q32": 978,
- "qstsa3q33": 979,
- "qstsa3q34": 980,
- "qstsa3q35": 981,
- "qstsa3q41": 982,
- "qstsa3q42": 983,
- "qstsa3q43": 984,
- "qstsa3q44": 985,
- "qstsa3q45": 986,
- "qstsa3q51": 987,
- "qstsa3q52": 988,
- "qstsa3q53": 989,
- "qstsa3q61": 990,
- "qstsa3q62": 991,
- "qstsa3q63": 992,
- "qstsa3q31a": 993,
- "qstsa3q51a": 994,
- "qstsa3q61a": 995,
- "qstsa4q11": 996,
- "qstsa4q12": 997,
- "qstsa4q13a": 998,
- "qstsa4q13": 999,
- "qstsa4q31": 1000,
- "qstsa4q32": 1001,
- "qstsa4q33": 1002,
- "qstsa4q34": 1003,
- "qstsa4q21": 1004,
- "qstsa4q22": 1005,
- "qstsa4q23": 1006,
- "qstsa4q24": 1007,
- "asheara": 1008,
- "hratli": 1009,
- "alkor": 1010,
- "ormus": 1011,
- "nikita": 1012,
- "tyrael": 1013,
- "Izual": 1014,
- "izual": 1015,
- "Jamella": 1016,
- "halbu": 1017,
- "Malachai": 1018,
- "merca201": 1019,
- "merca202": 1020,
- "merca203": 1021,
- "merca204": 1022,
- "merca205": 1023,
- "merca206": 1024,
- "merca207": 1025,
- "merca208": 1026,
- "merca209": 1027,
- "merca210": 1028,
- "merca211": 1029,
- "merca212": 1030,
- "merca213": 1031,
- "merca214": 1032,
- "merca215": 1033,
- "merca216": 1034,
- "merca217": 1035,
- "merca218": 1036,
- "merca219": 1037,
- "merca220": 1038,
- "merca221": 1039,
- "merca222": 1040,
- "merca223": 1041,
- "merca224": 1042,
- "merca225": 1043,
- "merca226": 1044,
- "merca227": 1045,
- "merca228": 1046,
- "merca229": 1047,
- "merca230": 1048,
- "merca231": 1049,
- "merca232": 1050,
- "merca233": 1051,
- "merca234": 1052,
- "merca235": 1053,
- "merca236": 1054,
- "merca237": 1055,
- "merca238": 1056,
- "merca239": 1057,
- "merca240": 1058,
- "merca241": 1059,
- "qf1": 1060,
- "qf2": 1061,
- "KhalimFlail": 1062,
- "SuperKhalimFlail": 1063,
- "qey": 1064,
- "qbr": 1065,
- "qhr": 1066,
- "The Feature Creep": 1067,
- "Hell Bovine": 1068,
- "Playersubtitles00": 1069,
- "Playersubtitles01": 1070,
- "Playersubtitles02": 1071,
- "Playersubtitles03": 1072,
- "Playersubtitles04": 1073,
- "Playersubtitles05": 1074,
- "Playersubtitles06": 1075,
- "Playersubtitles07": 1076,
- "Playersubtitles09": 1077,
- "Playersubtitles10": 1078,
- "Playersubtitles11": 1079,
- "Playersubtitles12": 1080,
- "Playersubtitles13": 1081,
- "Playersubtitles14": 1082,
- "Playersubtitles15": 1083,
- "Playersubtitles16": 1084,
- "Playersubtitles17": 1085,
- "Playersubtitles18": 1086,
- "Playersubtitles21": 1087,
- "Playersubtitles22": 1088,
- "Playersubtitles23": 1089,
- "Playersubtitles24": 1090,
- "Playersubtitles25": 1091,
- "Playersubtitles26": 1092,
- "Playersubtitles27": 1093,
- "Playersubtitles28": 1094,
- "LeaveCampAma": 1095,
- "LeaveCampBar": 1096,
- "LeaveCampPal": 1097,
- "LeaveCampSor": 1098,
- "LeaveCampNec": 1099,
- "EnterDOEAma": 1100,
- "EnterDOEBar": 1101,
- "EnterDOEPal": 1102,
- "EnterDOESor": 1103,
- "EnterDOENec": 1104,
- "EnterBurialAma": 1105,
- "EnterBurialBar": 1106,
- "EnterBurialPal": 1107,
- "EnterBurialSor": 1108,
- "EnterBurialNec": 1109,
- "EnterMonasteryAma": 1110,
- "EnterMonasteryBar": 1111,
- "EnterMonasteryPal": 1112,
- "EnterMonasterySor": 1113,
- "EnterMonasteryNec": 1114,
- "EnterForgottenTAma": 1115,
- "EnterForgottenTBar": 1116,
- "EnterForgottenTPal": 1117,
- "EnterForgottenTSor": 1118,
- "EnterForgottenTNec": 1119,
- "EnterJailAma": 1120,
- "EnterJailBar": 1121,
- "EnterJailPal": 1122,
- "EnterJailSor": 1123,
- "EnterJailNec": 1124,
- "Barracksremoved": 1129,
- "EnterCatacombsAma": 1130,
- "EnterCatacombsBar": 1131,
- "EnterCatacombsPal": 1132,
- "EnterCatacombsSor": 1133,
- "EnterCatacombsNec": 1134,
- "CompletingDOEAma": 1135,
- "CompletingDOEBar": 1136,
- "CompletingDOEPal": 1137,
- "CompletingDOESor": 1138,
- "CompletingDOENec": 1139,
- "CompletingBurialAma": 1140,
- "CompletingBurialBar": 1141,
- "CompletingBurialPal": 1142,
- "CompletingBurialSor": 1143,
- "CompletingBurialNec": 1144,
- "FindingInifusAma": 1145,
- "FindingInifusBar": 1146,
- "FindingInifusPal": 1147,
- "FindingInifusSor": 1148,
- "FindingInifusNec": 1149,
- "FindingCairnAma": 1150,
- "FindingCairnBar": 1151,
- "FindingCairnPal": 1152,
- "FindingCairnSor": 1153,
- "FindingCairnNec": 1154,
- "FindingTristramAma": 1155,
- "FindingTristramBar": 1156,
- "FindingTristramPal": 1157,
- "FindingTristramSor": 1158,
- "FindingTristramNec": 1159,
- "RescueCainAma": 1160,
- "RescueCainBar": 1161,
- "RescueCainPal": 1162,
- "RescueCainSor": 1163,
- "RescueCainNec": 1164,
- "HoradricMalusAma": 1165,
- "HoradricMalusBar": 1166,
- "HoradricMalusPal": 1167,
- "HoradricMalusSor": 1168,
- "HoradricMalusNec": 1169,
- "CompletingForgottenTAma": 1170,
- "CompletingForgottenTBar": 1171,
- "CompletingForgottenTPal": 21924,
- "CompletingForgottenTSor": 1173,
- "CompletingForgottenTNec": 1174,
- "CompletingAndarielAma": 1175,
- "CompletingAndarielBar": 1176,
- "CompletingAndarielPal": 1177,
- "CompletingAndarielSor": 1178,
- "CompletingAndarielNec": 1179,
- "EnteringRadamentAma": 1180,
- "EnteringRadamentBar": 1181,
- "EnteringRadamentPal": 1182,
- "EnteringRadamentSor": 1183,
- "EnteringRadamentNec": 1184,
- "CompletingRadamentAma": 1185,
- "CompletingRadamentBar": 1186,
- "CompletingRadamentPal": 1187,
- "CompletingRadamentSor": 1188,
- "CompletingRadamentNec": 1189,
- "BeginTaintedSunAma": 1190,
- "BeginTaintedSunBar": 1191,
- "BeginTaintedSunPal": 1192,
- "BeginTaintedSunSor": 1193,
- "BeginTaintedSunNec": 1194,
- "EnteringClawViperAma": 1195,
- "EnteringClawViperBar": 1196,
- "EnteringClawViperPal": 1197,
- "EnteringClawViperSor": 1198,
- "EnteringClawViperNec": 1199,
- "CompletingTaintedSunAma": 1200,
- "CompletingTaintedSunBar": 1201,
- "CompletingTaintedSunPal": 1202,
- "CompletingTaintedSunSor": 1203,
- "CompletingTaintedSunNec": 1204,
- "EnteringArcaneAma": 1205,
- "EnteringArcaneBar": 1206,
- "EnteringArcanePal": 1207,
- "EnteringArcaneSor": 1208,
- "EnteringArcaneNec": 1209,
- "FindingSummonerAma": 1210,
- "FindingSummonerBar": 1211,
- "FindingSummonerPal": 1212,
- "FindingSummonerSor": 1213,
- "FindingSummonerNec": 1214,
- "CompletingSummonerAma": 1215,
- "CompletingSummonerBar": 1216,
- "CompletingSummonerPal": 1217,
- "CompletingSummonerSor": 1218,
- "CompletingSummonerNec": 1219,
- "FindingdecoyTombAma": 1220,
- "FindingdecoyTombBar": 1221,
- "FindingdecoyTombPal": 1222,
- "FindingdecoyTombSor": 1223,
- "FindingdecoyTombNec": 1224,
- "FindingTrueTombAma": 1225,
- "FindingTrueTombBar": 1226,
- "FindingTrueTombPal": 1227,
- "FindingTrueTombSor": 1228,
- "FindingTrueTombNec": 1229,
- "CompletingTombAma": 1230,
- "CompletingTombBar": 1231,
- "CompletingTombPal": 1232,
- "CompletingTombSor": 1233,
- "CompletingTombNec": 1234,
- "nodarkwanderer": 1235,
- "FindingLamEsenAma": 1236,
- "FindingLamEsenBar": 1237,
- "FindingLamEsenPal": 1238,
- "FindingLamEsenSor": 1239,
- "FindingLamEsenNec": 1240,
- "CompletingLamEsenAma": 1241,
- "CompletingLamEsenBar": 1242,
- "CompletingLamEsenPal": 1243,
- "CompletingLamEsenSor": 1244,
- "CompletingLamEsenNec": 1245,
- "FindingBeneathCityAma": 1246,
- "FindingBeneathCityBar": 1247,
- "FindingBeneathCityPal": 1248,
- "FindingBeneathCitySor": 1249,
- "FindingBeneathCityNec": 1250,
- "FindingDrainLeverAma": 1251,
- "FindingDrainLeverBar": 1252,
- "FindingDrainLeverPal": 1253,
- "FindingDrainLeverSor": 1254,
- "FindingDrainLeverNec": 1255,
- "CompletingBeneathCityAma": 1256,
- "CompletingBeneathCityBar": 1257,
- "CompletingBeneathCityPal": 1258,
- "CompletingBeneathCitySor": 1259,
- "CompletingBeneathCityNec": 1260,
- "CompletingBladeAma": 1261,
- "CompletingBladeBar": 1262,
- "CompletingBladePal": 1263,
- "CompletingBladeSor": 1264,
- "CompletingBladeNec": 1265,
- "FindingJadeFigAma": 1270,
- "FindingTempleAma": 1271,
- "FindingTempleBar": 1272,
- "FindingTemplePal": 1273,
- "FindingTempleSor": 1274,
- "FindingTempleNec": 1275,
- "CompletingTempleAma": 1276,
- "CompletingTempleBar": 1277,
- "CompletingTemplePal": 1278,
- "CompletingTempleSor": 1279,
- "CompletingTempleNec": 1280,
- "FindingGuardianTowerAma": 1281,
- "FindingGuardianTowerBar": 1282,
- "FindingGuardianTowerPal": 1283,
- "FindingGuardianTowerSor": 1284,
- "FindingGuardianTowerNec": 1285,
- "CompletingGuardianTowerAma": 1286,
- "CompletingGuardianTowerBar": 1287,
- "CompletingGuardianTowerPal": 1288,
- "CompletingGuardianTowerSor": 1289,
- "CompletingGuardianTowerNec": 1290,
- "FreezingIzualAma": 21972,
- "FreezingIzualBar": 1292,
- "FreezingIzualPal": 1293,
- "FreezingIzualSor": 1294,
- "FreezingIzualNec": 1295,
- "Eskillname0": 1296,
- "Eskillsd0": 1297,
- "Eskillld0": 1298,
- "Eskillan0": 1299,
- "EskillnameExp1": 1300,
- "EskillsExpd1": 1301,
- "EskilllExpd1": 1302,
- "EskillExpan1": 1303,
- "Eskillname2": 1304,
- "Eskillsd2": 1305,
- "Eskillld2": 1306,
- "Eskillan2": 1307,
- "Eskillname3": 1308,
- "Eskillsd3": 1309,
- "Eskillld3": 1310,
- "Eskillan3": 1311,
- "Eskillname4": 1312,
- "Eskillsd4": 1313,
- "Eskillld4": 1314,
- "Eskillan4": 1315,
- "Eskillname5": 1316,
- "Eskillsd5": 1317,
- "Eskillld5": 1318,
- "Eskillan5": 1319,
- "Eskillname6": 1320,
- "Eskillsd6": 1321,
- "Eskillld6": 1322,
- "Eskillan6": 1323,
- "Eskillname7": 1324,
- "Eskillsd7": 1325,
- "Eskillld7": 1326,
- "Eskillan7": 1327,
- "Eskillname8": 1328,
- "Eskillsd8": 1329,
- "Eskillld8": 1330,
- "Eskillan8": 1331,
- "Eskillname9": 1332,
- "Eskillsd9": 1333,
- "Eskillld9": 1334,
- "Eskillan9": 1335,
- "Eskillname10": 1336,
- "Eskillsd10": 1337,
- "Eskillld10": 1338,
- "Eskillan10": 1339,
- "Eskillname11": 1340,
- "Eskillsd11": 1341,
- "Eskillld11": 1342,
- "Eskillan11": 1343,
- "Eskillname12": 1344,
- "Eskillsd12": 1345,
- "Eskillld12": 1346,
- "Eskillan12": 1347,
- "Eskillname13": 1348,
- "Eskillsd13": 1349,
- "Eskillld13": 1350,
- "Eskillan13": 1351,
- "Eskillname14": 1352,
- "Eskillsd14": 1353,
- "Eskillld14": 1354,
- "Eskillan14": 1355,
- "Eskillname15": 1356,
- "Eskillsd15": 1357,
- "Eskillld15": 1358,
- "Eskillan15": 1359,
- "Eskillname16": 1360,
- "Eskillsd16": 1361,
- "Eskillld16": 1362,
- "Eskillan16": 1363,
- "Eskillname17": 1364,
- "Eskillsd17": 1365,
- "Eskillld17": 1366,
- "Eskillan17": 1367,
- "Eskillname18": 1368,
- "Eskillsd18": 1369,
- "Eskillld18": 1370,
- "Eskillan18": 1371,
- "Eskillname19": 1372,
- "Eskillsd19": 1373,
- "Eskillld19": 1374,
- "Eskillan19": 1375,
- "Eskillname20": 1376,
- "Eskillsd20": 1377,
- "Eskillld20": 1378,
- "Eskillan20": 1379,
- "Eskillname21": 1380,
- "Eskillsd21": 1381,
- "Eskillld21": 1382,
- "Eskillan21": 1383,
- "Eskillname22": 1384,
- "Eskillsd22": 1385,
- "Eskillld22": 1386,
- "Eskillan22": 1387,
- "Eskillname23": 1388,
- "Eskillsd23": 1389,
- "Eskillld23": 1390,
- "Eskillan23": 1391,
- "Eskillname24": 1392,
- "Eskillsd24": 1393,
- "Eskillld24": 1394,
- "Eskillan24": 1395,
- "Eskillname25": 1396,
- "Eskillsd25": 1397,
- "Eskillld25": 1398,
- "Eskillan25": 1399,
- "Eskillname26": 1400,
- "Eskillsd26": 1401,
- "Eskillld26": 1402,
- "Eskillan26": 1403,
- "Eskillname27": 1404,
- "Eskillsd27": 1405,
- "Eskillld27": 1406,
- "Eskillan27": 1407,
- "Eskillname28": 1408,
- "Eskillsd28": 1409,
- "Eskillld28": 1410,
- "Eskillan28": 1411,
- "Eskillname29": 1412,
- "Eskillsd29": 1413,
- "Eskillld29": 1414,
- "Eskillan29": 1415,
- "Eskillname30": 1416,
- "Eskillsd30": 1417,
- "Eskillld30": 1418,
- "Eskillan30": 1419,
- "Eskillname31": 1420,
- "Eskillsd31": 1421,
- "Eskillld31": 1422,
- "Eskillan31": 1423,
- "Eskillname32": 1424,
- "Eskillsd32": 1425,
- "Eskillld32": 1426,
- "Eskillan32": 1427,
- "Eskillname33": 1428,
- "Eskillsd33": 1429,
- "Eskillld33": 1430,
- "Eskillan33": 1431,
- "Eskillname34": 1432,
- "Eskillsd34": 1433,
- "Eskillld34": 1434,
- "Eskillan34": 1435,
- "Eskillname35": 1436,
- "Eskillsd35": 1437,
- "Eskillld35": 1438,
- "Eskillan35": 1439,
- "Eskillname36": 1440,
- "Eskillsd36": 1441,
- "Eskillld36": 1442,
- "Eskillan36": 1443,
- "Eskillname37": 1444,
- "Eskillsd37": 1445,
- "Eskillld37": 1446,
- "Eskillan37": 1447,
- "Eskillname38": 1448,
- "Eskillsd38": 1449,
- "Eskillld38": 1450,
- "Eskillan38": 1451,
- "Eskillname39": 1452,
- "Eskillsd39": 1453,
- "Eskillld39": 1454,
- "Eskillan39": 1455,
- "Eskillname40": 1456,
- "Eskillsd40": 1457,
- "Eskillld40": 1458,
- "Eskillan40": 1459,
- "Eskillname41": 1460,
- "Eskillsd41": 1461,
- "Eskillld41": 1462,
- "Eskillan41": 1463,
- "Eskillname42": 1464,
- "Eskillsd42": 1465,
- "Eskillld42": 1466,
- "Eskillan42": 1467,
- "Eskillname43": 1468,
- "Eskillsd43": 1469,
- "Eskillld43": 1470,
- "Eskillan43": 1471,
- "Eskillname44": 1472,
- "Eskillsd44": 1473,
- "Eskillld44": 1474,
- "Eskillan44": 1475,
- "Eskillname45": 1476,
- "Eskillsd45": 1477,
- "Eskillld45": 1478,
- "Eskillan45": 1479,
- "Eskillname46": 1480,
- "Eskillsd46": 1481,
- "Eskillld46": 1482,
- "Eskillan46": 1483,
- "Eskillname47": 1484,
- "Eskillsd47": 1485,
- "Eskillld47": 1486,
- "Eskillan47": 1487,
- "Eskillname48": 1488,
- "Eskillsd48": 1489,
- "Eskillld48": 1490,
- "Eskillan48": 1491,
- "Eskillname49": 1492,
- "Eskillsd49": 1493,
- "Eskillld49": 1494,
- "Eskillan49": 1495,
- "Eskillname50": 1496,
- "Eskillsd50": 1497,
- "Eskillld50": 1498,
- "Eskillan50": 1499,
- "Eskillname51": 1500,
- "Eskillsd51": 1501,
- "Eskillld51": 1502,
- "Eskillan51": 1503,
- "Eskillname52": 1504,
- "Eskillsd52": 1505,
- "Eskillld52": 1506,
- "Eskillan52": 1507,
- "Eskillname53": 1508,
- "Eskillsd53": 1509,
- "Eskillld53": 1510,
- "Eskillan53": 1511,
- "Eskillname54": 1512,
- "Eskillsd54": 1513,
- "Eskillld54": 1514,
- "Eskillan54": 1515,
- "Eskillname55": 1516,
- "Eskillsd55": 1517,
- "Eskillld55": 1518,
- "Eskillan55": 1519,
- "Eskillname56": 1520,
- "Eskillsd56": 1521,
- "Eskillld56": 1522,
- "Eskillan56": 1523,
- "Eskillname57": 1524,
- "Eskillsd57": 1525,
- "Eskillld57": 1526,
- "Eskillan57": 1527,
- "Eskillname58": 1528,
- "Eskillsd58": 1529,
- "Eskillld58": 1530,
- "Eskillan58": 1531,
- "Eskillname59": 1532,
- "Eskillsd59": 1533,
- "Eskillld59": 1534,
- "Eskillan59": 1535,
- "ESkillHawk": 22278,
- "ESkillSpikes": 22279,
- "ESkillStars": 22280,
- "ESkillWolf": 22281,
- "ESkillWolves": 22282,
- "ESkillShoots": 22283,
- "ESkillTimes": 22284,
- "ESkillSpikes2": 22285,
- "ob1": 20281,
- "ob2": 20282,
- "ob3": 20283,
- "ob4": 20284,
- "ob5": 21778,
- "ne1": 20332,
- "ne2": 20333,
- "ne3": 20334,
- "ne4": 20335,
- "ne5": 20336,
- "dr1": 20320,
- "dr2": 20318,
- "dr3": 20319,
- "dr4": 20317,
- "dr5": 20321,
- "as1": 20285,
- "as2": 20286,
- "as3": 20287,
- "as4": 20288,
- "as5": 20289,
- "as6": 20290,
- "as7": 20291,
- "AmaOnly": 20426,
- "SorOnly": 20427,
- "NecOnly": 20428,
- "PalOnly": 20429,
- "BarOnly": 20430,
- "DruOnly": 20431,
- "AssOnly": 20432,
- "WeaponDescH2H": 21258,
- "Seige Tower": 22352,
- "RotWalker": 22353,
- "ReanimatedHorde": 22354,
- "ProwlingDead": 22355,
- "UnholyCorpse": 22356,
- "DefiledWarrior": 22357,
- "Seige Beast": 1580,
- "CrushBiest": 22359,
- "BloodBringer": 22360,
- "GoreBearer": 22361,
- "DeamonSteed": 22362,
- "WailingSpirit": 22363,
- "LifeSeeker": 22364,
- "LifeStealer": 22365,
- "DeathlyVisage": 22366,
- "BoundSpirit": 22367,
- "BanishedSoul": 22368,
- "Deathexp": 22369,
- "Minionexp": 22370,
- "Slayerexp": 22371,
- "IceBoar": 22372,
- "FireBoar": 22373,
- "HellSpawn": 22374,
- "IceSpawn": 22375,
- "GreaterHellSpawn": 22376,
- "GreaterIceSpawn": 22377,
- "FanaticMinion": 22378,
- "BerserkSlayer": 22379,
- "ConsumedFireBoar": 22380,
- "ConsumedIceBoar": 22381,
- "FrenziedHellSpawn": 22382,
- "FrenziedIceSpawn": 22383,
- "InsaneHellSpawn": 22384,
- "InsaneIceSpawn": 22385,
- "Succubusexp": 22386,
- "VileTemptress": 22387,
- "StygianHarlot": 22388,
- "BlightWing": 1611,
- "BloodWitch": 1612,
- "Dominus": 22391,
- "VileWitch": 22392,
- "StygianFury": 22393,
- "MageWing": 1616,
- "HellWitch": 1617,
- "OverSeer": 22396,
- "Lasher": 22397,
- "OverLord": 22398,
- "BloodBoss": 22399,
- "HellWhip": 22400,
- "MinionSpawner": 22401,
- "MinionSlayerSpawner": 22402,
- "MinionIce/fireBoarSpawner": 22403,
- "Minionice/hellSpawnSpawner": 22404,
- "MinionGreaterIce/hellSpawnSpawner": 22405,
- "Imp1": 22406,
- "Imp2": 22407,
- "Imp3": 22408,
- "Imp4": 22409,
- "Imp5": 22410,
- "CapsJoinMenu4": 1633,
- "CapsJoinMenu5": 1634,
- "Guild 1": 1635,
- "Guild 2": 1636,
- "Guild 3": 1637,
- "Guild 4": 1638,
- "Guild 5": 1639,
- "To Guild 5": 1640,
- "To Guild 4": 1641,
- "To Guild 3": 1642,
- "To Guild 2": 1643,
- "To Guild 1": 1644,
- "CapsBnet9": 1645,
- "CapsBnet10": 1646,
- "CapsBnet11": 1647,
- "CapsBnet12": 1648,
- "CapsBnet13": 1649,
- "CapsBnet14": 1650,
- "CapsBnet15": 1651,
- "CapsGuildName": 1652,
- "CapsGuildTag": 1653,
- "GuildText1": 1654,
- "GuildText2": 1655,
- "Ladder3": 1656,
- "Ladder7": 1657,
- "gmGuildTitle": 1658,
- "gmGuildName": 1659,
- "gmGuildTag": 1660,
- "gmWWW": 1661,
- "gmGuildCharter": 1662,
- "gmGuildCurrentGolds": 1663,
- "gmGuildNextLevel": 1664,
- "gmGuildMaster": 1665,
- "gmOfficer": 1666,
- "gmName": 1667,
- "gmClass": 1668,
- "gmLevel": 1669,
- "gmDonate": 1670,
- "gmRemove": 1671,
- "gmPal": 1672,
- "gmSor": 1673,
- "gmAma": 1674,
- "gmNec": 1675,
- "gmBar": 1676,
- "gmChangeSym": 1677,
- "gmChangeCharter": 1678,
- "gmChangeWebLink": 1679,
- "Guild Portal": 1680,
- "createdguildsuccess": 1681,
- "createdguildfailure": 1682,
- "inviteguildsuccess": 1683,
- "inviteguildfailure": 1684,
- "inviteguildins": 1685,
- "joinedguildsuccess": 1686,
- "joinedguildfailure": 1687,
- "quitguildsuccess": 1688,
- "quitguildfailure": 1689,
- "guildentererror": 1690,
- "strGuildMasterKicked": 1691,
- "strGuildPerk1": 1692,
- "strGuildPerk2": 1693,
- "strGuildPerk3": 1694,
- "strGuildPerk4": 1695,
- "strGuildPerk5": 1696,
- "strGuildPerk6": 1697,
- "strGuildGoldDonated": 1698,
- "strGuildDonateGold": 1699,
- "gmGuildCurrentGoldPopup": 1700,
- "gmGuildNextLevelPopup": 1701,
- "gmGuildDonateGoldPopup": 1702,
- "Message Board": 1703,
- "Trophy Case": 1704,
- "Guild Vault": 1705,
- "Steeg Stone": 1706,
- "guildaccepticon": 1707,
- "guildmsgtext": 1708,
- "ScrollFormat": 1709,
- "BookFormat": 1710,
- "HiqualityFormat": 1711,
- "LowqualityFormat": 1712,
- "HerbFormat": 1713,
- "MagicFormat": 1714,
- "GemmedNormalName": 1715,
- "BodyPartsFormat": 1716,
- "PlayerBodyPartFormat": 1717,
- "RareFormat": 1718,
- "SetItemFormat": 1719,
- "ChampionFormat": 1720,
- "Monster1Format": 1721,
- "Monster2Format": 1722,
- "Low Quality": 1723,
- "Damaged": 1724,
- "Cracked": 1725,
- "Crude": 20910,
- "Hiquality": 1727,
- "Gemmed": 1728,
- "Resiliant": 1729,
- "Sturdy": 1730,
- "Strong": 1731,
- "Glorious": 1732,
- "Blessed": 1733,
- "Saintly": 1734,
- "Holy": 1735,
- "Devious": 1736,
- "Fortified": 1737,
- "Urgent": 1738,
- "Fleet": 1739,
- "Muscular": 1740,
- "Jagged": 1741,
- "Deadly": 1742,
- "Vicious": 1743,
- "Brutal": 1744,
- "Massive": 1745,
- "Savage": 1746,
- "Merciless": 1747,
- "Vulpine": 1748,
- "Swift": 1749,
- "Artful": 1750,
- "Skillful": 1751,
- "Adroit": 1752,
- "Tireless": 1753,
- "Rugged": 1754,
- "Bronze": 1755,
- "Iron": 1756,
- "Steel": 1757,
- "Silver": 1758,
- "Gold": 1759,
- "Platinum": 1760,
- "Meteoric": 1761,
- "Sharp": 1762,
- "Fine": 1763,
- "Warrior's": 1764,
- "Soldier's": 1765,
- "Knight's": 1766,
- "Lord's": 1767,
- "King's": 1768,
- "Howling": 1769,
- "Fortuitous": 1770,
- "Brilliant": 1771,
- "Omniscient": 1772,
- "Sage": 1773,
- "Shrewd": 1774,
- "Vivid": 1775,
- "Glimmering": 1776,
- "Glowing": 1777,
- "Bright": 1778,
- "Solar": 1779,
- "Lizard's": 1780,
- "Forceful": 1781,
- "Snake's": 1782,
- "Serpent's": 1783,
- "Drake's": 1784,
- "Dragon's": 1785,
- "Wyrm's": 1786,
- "Dazzling": 1787,
- "Facinating": 1788,
- "Prismatic": 1789,
- "Azure": 1790,
- "Lapis": 1791,
- "Cobalt": 1792,
- "Indigo": 1793,
- "Sapphire": 1794,
- "Cerulean": 1795,
- "Red": 1796,
- "Crimson": 1797,
- "Burgundy": 1798,
- "Garnet": 1799,
- "Russet": 1800,
- "Ruby": 1801,
- "Vermilion": 1802,
- "Orange": 1803,
- "Ocher": 1804,
- "Tangerine": 1805,
- "Coral": 1806,
- "Crackling": 1807,
- "Amber": 1808,
- "Forked": 1809,
- "Green": 20905,
- "Beryl": 1811,
- "Jade": 1812,
- "Viridian": 1813,
- "Vital": 1814,
- "Emerald": 1815,
- "Enduring": 1816,
- "Fletcher's": 1817,
- "Archer's": 1818,
- "Monk's": 1819,
- "Priest's": 1820,
- "Summoner's": 1821,
- "Necromancer's": 1822,
- "Angel's": 1823,
- "Arch-Angel's": 1824,
- "Slayer's": 1825,
- "Berserker's": 2507,
- "Kicking": 1827,
- "Triumphant": 1828,
- "Mighty": 1829,
- "Energizing": 1830,
- "Strengthening": 1831,
- "Empowering": 1832,
- "Brisk": 1833,
- "Tough": 1834,
- "Hardy": 1835,
- "Robust": 1836,
- "of Health": 1837,
- "of Protection": 1838,
- "of Absorption": 1839,
- "of Warding": 1840,
- "of the Sentinel": 1841,
- "of Guarding": 1842,
- "of Negation": 1843,
- "of Piercing": 1844,
- "of Bashing": 1845,
- "of Puncturing": 1846,
- "of Thorns": 1847,
- "of Spikes": 1848,
- "of Readiness": 1849,
- "of Alacrity": 1850,
- "of Swiftness": 1851,
- "of Quickness": 1852,
- "of Blocking": 1853,
- "of Deflecting": 1854,
- "of the Apprentice": 1855,
- "of the Magus": 1856,
- "of Frost": 1857,
- "of the Glacier": 1858,
- "of Warmth": 1859,
- "of Flame": 1860,
- "of Fire": 1861,
- "of Burning": 1862,
- "of Shock": 1863,
- "of Lightning": 1864,
- "of Thunder": 1865,
- "of Craftsmanship": 1866,
- "of Quality": 1867,
- "of Maiming": 1868,
- "of Slaying": 1869,
- "of Gore": 1870,
- "of Carnage": 1871,
- "of Slaughter": 1872,
- "of Worth": 1873,
- "of Measure": 1874,
- "of Excellence": 1875,
- "of Performance": 1876,
- "of Blight": 1877,
- "of Venom": 1878,
- "of Pestilence": 1879,
- "of Dexterity": 1880,
- "of Skill": 1881,
- "of Accuracy": 1882,
- "of Precision": 1883,
- "of Perfection": 1884,
- "of Balance": 1885,
- "of Stability": 1886,
- "of the Horse": 1887,
- "of Regeneration": 1888,
- "of Regrowth": 1889,
- "of Vileness": 1890,
- "of Greed": 1891,
- "of Wealth": 1892,
- "of Chance": 1893,
- "of Fortune": 1894,
- "of Energy": 1895,
- "of the Mind": 1896,
- "of Brilliance": 1897,
- "of Sorcery": 1898,
- "of Wizardry": 1899,
- "of the Bear": 1900,
- "of Light": 1901,
- "of Radiance": 1902,
- "of the Sun": 1903,
- "of Life": 1904,
- "of the Jackal": 1905,
- "of the Fox": 1906,
- "of the Wolf": 1907,
- "of the Tiger": 1908,
- "of the Mammoth": 1909,
- "of the Colosuss": 1910,
- "of the Leech": 1911,
- "of the Locust": 1912,
- "of the Bat": 1913,
- "of the Vampire": 1914,
- "of Defiance": 1915,
- "of Remedy": 1916,
- "of Amelioration": 1917,
- "of Ice": 1918,
- "of Simplicity": 1919,
- "of Ease": 1920,
- "of the Mule": 1921,
- "of Strength": 1922,
- "of Might": 1923,
- "of the Ox": 1924,
- "of the Giant": 1925,
- "of the Titan": 1926,
- "of Pacing": 1927,
- "of Haste": 1928,
- "of Speed": 1929,
- "cap": 1930,
- "skp": 1931,
- "hlm": 1932,
- "fhl": 1933,
- "ghm": 1934,
- "crn": 1935,
- "msk": 1936,
- "qui": 1937,
- "lea": 1938,
- "hla": 1939,
- "stu": 1940,
- "rng": 1941,
- "scl": 1942,
- "chn": 1943,
- "brs": 1944,
- "spl": 1945,
- "plt": 1946,
- "fld": 1947,
- "gth": 1948,
- "ful": 1949,
- "aar": 1950,
- "ltp": 1951,
- "buc": 1952,
- "sml": 1953,
- "lrg": 1954,
- "kit": 1955,
- "tow": 1956,
- "gts": 1957,
- "lgl": 1958,
- "vgl": 1959,
- "mgl": 1960,
- "tgl": 1961,
- "hgl": 1962,
- "lbt": 1963,
- "vbt": 1964,
- "mbt": 1965,
- "tbt": 1966,
- "hbt": 1967,
- "lbl": 1968,
- "vbl": 1969,
- "mbl": 1970,
- "tbl": 1971,
- "hbl": 1972,
- "bhm": 1973,
- "bsh": 1974,
- "spk": 1975,
- "hax": 1976,
- "axe": 1977,
- "2ax": 1978,
- "mpi": 1979,
- "wax": 1980,
- "lax": 1981,
- "bax": 1982,
- "btx": 1983,
- "gax": 1984,
- "gix": 1985,
- "wnd": 1986,
- "ywn": 1987,
- "bwn": 1988,
- "gwn": 1989,
- "clb": 1990,
- "scp": 1991,
- "gsc": 1992,
- "wsp": 1993,
- "spc": 1994,
- "mac": 1995,
- "mst": 1996,
- "fla": 1997,
- "whm": 1998,
- "mau": 1999,
- "gma": 2000,
- "ssd": 2001,
- "scm": 2002,
- "sbr": 2003,
- "flc": 2004,
- "crs": 2005,
- "bsd": 2006,
- "lsd": 2007,
- "wsd": 2008,
- "2hs": 2009,
- "clm": 2010,
- "gis": 2011,
- "bsw": 2012,
- "flb": 2013,
- "gsd": 2014,
- "dgr": 2015,
- "dir": 2016,
- "kri": 2017,
- "bld": 2018,
- "tkf": 2019,
- "tax": 2020,
- "bkf": 2021,
- "bal": 2022,
- "jav": 2023,
- "pil": 2024,
- "ssp": 2025,
- "glv": 2026,
- "tsp": 2027,
- "spr": 2028,
- "tri": 2029,
- "brn": 2030,
- "spt": 2031,
- "pik": 2032,
- "bar": 2033,
- "vou": 2034,
- "scy": 2035,
- "pax": 2036,
- "hal": 2037,
- "wsc": 2038,
- "sst": 2039,
- "lst": 2040,
- "cst": 2041,
- "bst": 2042,
- "wst": 2043,
- "sbw": 2044,
- "hbw": 2045,
- "lbw": 2046,
- "cbw": 2047,
- "sbb": 2048,
- "lbb": 2049,
- "swb": 2050,
- "lwb": 2051,
- "lxb": 2052,
- "mxb": 2053,
- "hxb": 2054,
- "rxb": 2055,
- "xpk": 2056,
- "xsh": 2057,
- "xh9": 2058,
- "zhb": 2059,
- "ztb": 2060,
- "zmb": 2061,
- "zvb": 2062,
- "zlb": 2063,
- "xhb": 2064,
- "xtb": 2065,
- "xmb": 2066,
- "xvb": 2067,
- "xlb": 2068,
- "xhg": 2069,
- "xtg": 2070,
- "xmg": 2071,
- "xvg": 2072,
- "xlg": 2073,
- "xts": 2074,
- "xow": 2075,
- "xit": 2076,
- "xrg": 2077,
- "xml": 2078,
- "xuc": 2079,
- "xtp": 2080,
- "xar": 2081,
- "xul": 2082,
- "xth": 2083,
- "xld": 2084,
- "xlt": 2085,
- "xpl": 2086,
- "xrs": 2087,
- "xhn": 2088,
- "xcl": 2089,
- "xng": 2090,
- "xtu": 2091,
- "xla": 2092,
- "xea": 2093,
- "xui": 2094,
- "xsk": 2095,
- "xrn": 2096,
- "xhm": 2097,
- "xhl": 2098,
- "xlm": 2099,
- "xkp": 2100,
- "xap": 2101,
- "8rx": 2102,
- "8hx": 2103,
- "8mx": 2104,
- "8lx": 2105,
- "8lw": 2106,
- "8sw": 2107,
- "8l8": 2108,
- "8s8": 2109,
- "8cb": 2110,
- "8lb": 2111,
- "8hb": 2112,
- "8sb": 2113,
- "8ws": 2114,
- "8bs": 2115,
- "8cs": 2116,
- "8ls": 2117,
- "8ss": 2118,
- "9wc": 2119,
- "9h9": 2120,
- "9pa": 2121,
- "9s8": 2122,
- "9vo": 2123,
- "9b7": 2124,
- "9p9": 2125,
- "9st": 2126,
- "9br": 2127,
- "9tr": 2128,
- "9sr": 2129,
- "9ts": 2130,
- "9gl": 2131,
- "9s9": 2132,
- "9pi": 2133,
- "9ja": 2134,
- "9b8": 2135,
- "9bk": 2136,
- "9ta": 2137,
- "9tk": 2138,
- "9bl": 2139,
- "9kr": 2140,
- "9di": 2141,
- "9dg": 2142,
- "9gd": 2143,
- "9fb": 2144,
- "9gs": 2145,
- "9cm": 2146,
- "92h": 2147,
- "9wd": 2148,
- "9ls": 2149,
- "9bs": 2150,
- "9cr": 2151,
- "9fc": 2152,
- "9sb": 2153,
- "9sm": 2154,
- "9ss": 2155,
- "9gm": 2156,
- "9m9": 2157,
- "9wh": 2158,
- "9fl": 2159,
- "9mt": 2160,
- "9ma": 2161,
- "9sp": 2162,
- "9ws": 2163,
- "9qs": 2164,
- "9sc": 2165,
- "9cl": 2166,
- "9gw": 2167,
- "9bw": 2168,
- "9yw": 2169,
- "9wn": 2170,
- "9gi": 2171,
- "9ga": 2172,
- "9bt": 2173,
- "9ba": 2174,
- "9la": 2175,
- "9wa": 2176,
- "9mp": 2177,
- "92a": 2178,
- "9ax": 2179,
- "9ha": 2180,
- "9b9": 2181,
- "gpl": 2182,
- "opl": 2183,
- "gpm": 2184,
- "opm": 2185,
- "gps": 2186,
- "ops": 2187,
- "gidbinn": 2188,
- "g33": 2189,
- "d33": 2190,
- "leg": 2191,
- "Malus": 2192,
- "hdm": 2193,
- "hfh": 2194,
- "hst": 2195,
- "msf": 2196,
- "orifice": 2197,
- "elx": 2198,
- "tbk": 2199,
- "tsc": 2200,
- "ibk": 2201,
- "isc": 2202,
- "RightClicktoUse": 2203,
- "RightClicktoOpen": 2204,
- "RightClicktoRead": 2205,
- "InsertScrolls": 2206,
- "vps": 2207,
- "yps": 2208,
- "rvs": 2209,
- "rvl": 2210,
- "wms": 2211,
- "amu": 2212,
- "vip": 2213,
- "rin": 2214,
- "gld": 2215,
- "bks": 2216,
- "bkd": 2217,
- "aqv": 2218,
- "tch": 2219,
- "cqv": 2220,
- "Key": 2221,
- "key": 2222,
- "luv": 2223,
- "xyz": 2224,
- "shrine": 2225,
- "teleport pad": 2226,
- "j34": 2227,
- "g34": 2228,
- "bbb": 2229,
- "LamTome": 2230,
- "box": 2231,
- "tr1": 2232,
- "mss": 2233,
- "ass": 2234,
- "ear": 2235,
- "gcv": 2236,
- "gfv": 2237,
- "gsv": 2238,
- "gzv": 2239,
- "gpv": 2240,
- "gcy": 2241,
- "gfy": 2242,
- "gsy": 2243,
- "gly": 2244,
- "gpy": 2245,
- "gcb": 2246,
- "gfb": 2247,
- "gsb": 2248,
- "glb": 2249,
- "gpb": 2250,
- "gcg": 2251,
- "gfg": 2252,
- "glg": 2253,
- "gsg": 2254,
- "gpg": 2255,
- "gcr": 2256,
- "gfr": 2257,
- "gsr": 2258,
- "glr": 2259,
- "gpr": 2260,
- "gcw": 2261,
- "gfw": 2262,
- "gsw": 2263,
- "glw": 2264,
- "gpw": 2265,
- "hp1": 2266,
- "hp2": 2267,
- "hp3": 2268,
- "hp4": 2269,
- "hp5": 2270,
- "mp1": 2271,
- "mp2": 2272,
- "mp3": 2273,
- "mp4": 2274,
- "mp5": 2275,
- "hrb": 20434,
- "skc": 2277,
- "skf": 2278,
- "sku": 2279,
- "skl": 2280,
- "skz": 2281,
- "Beast": 2282,
- "Eagle": 2283,
- "Raven": 2284,
- "Viper": 2285,
- "GhoulRI": 2286,
- "Skull": 2287,
- "Blood": 2288,
- "Dread": 2289,
- "Doom": 2290,
- "Grim": 2291,
- "Bone": 2292,
- "Death": 2293,
- "Shadow": 2294,
- "Storm": 2295,
- "Rune": 2296,
- "PlagueRI": 2297,
- "Stone": 2298,
- "Wraith": 2989,
- "Spirit": 2300,
- "Demon": 2301,
- "Cruel": 2302,
- "Empyrion": 2303,
- "Bramble": 2304,
- "Pain": 2305,
- "Loath": 2306,
- "Glyph": 2307,
- "Imp": 2308,
- "Fiend": 2309,
- "Hailstone": 2310,
- "Gale": 2311,
- "Dire": 2312,
- "Soul": 2313,
- "Brimstone": 2314,
- "Corpse": 2315,
- "Carrion": 2316,
- "Holocaust": 2317,
- "Havoc": 2318,
- "Bitter": 2319,
- "Entropy": 2320,
- "Chaos": 2321,
- "Order": 2322,
- "Rift": 2323,
- "Corruption": 2324,
- "bite": 2325,
- "scratch": 2326,
- "scalpel": 2327,
- "fang": 2328,
- "gutter": 2329,
- "thirst": 2330,
- "razor": 2331,
- "scythe": 2332,
- "edge": 2333,
- "saw": 2334,
- "splitter": 2335,
- "cleaver": 2336,
- "sever": 2337,
- "sunder": 2338,
- "rend": 2339,
- "mangler": 2340,
- "slayer": 2341,
- "reaver": 2342,
- "Spawn": 2343,
- "gnash": 2344,
- "star": 2345,
- "blow": 2346,
- "smasher": 2347,
- "Bane": 2348,
- "crusher": 2349,
- "breaker": 2350,
- "grinder": 2351,
- "crack": 2352,
- "mallet": 2353,
- "knell": 2354,
- "lance": 2355,
- "spike": 2356,
- "impaler": 2357,
- "skewer": 2358,
- "prod": 2359,
- "scourge": 2360,
- "wand": 2361,
- "wrack": 2362,
- "barb": 2363,
- "needle": 2364,
- "dart": 2365,
- "bolt": 2366,
- "quarrel": 2367,
- "fletch": 2368,
- "flight": 2369,
- "nock": 2370,
- "horn": 2371,
- "stinger": 2372,
- "quill": 2373,
- "goad": 2374,
- "branch": 2375,
- "spire": 2376,
- "song": 2377,
- "call": 2378,
- "cry": 2379,
- "spell": 2380,
- "chant": 2381,
- "weaver": 2382,
- "gnarl": 2383,
- "visage": 2384,
- "crest": 2385,
- "circlet": 2386,
- "veil": 2387,
- "hood": 2388,
- "mask": 2389,
- "brow": 2390,
- "casque": 2391,
- "visor": 2392,
- "cowl": 2393,
- "hide": 2394,
- "Pelt": 2395,
- "carapace": 2396,
- "coat": 2397,
- "wrap": 2398,
- "suit": 2399,
- "cloak": 2400,
- "shroud": 2401,
- "jack": 2402,
- "mantle": 2403,
- "guard": 2404,
- "badge": 2405,
- "rock": 2406,
- "aegis": 2407,
- "ward": 2408,
- "tower": 2409,
- "shield": 2410,
- "wing": 2411,
- "mark": 2412,
- "emblem": 2413,
- "hand": 2414,
- "fist": 2415,
- "claw": 2416,
- "clutches": 2417,
- "grip": 2418,
- "grasp": 2419,
- "hold": 2420,
- "touch": 2421,
- "finger": 2422,
- "knuckle": 2423,
- "shank": 2424,
- "spur": 2425,
- "tread": 2426,
- "stalker": 2427,
- "greave": 2428,
- "blazer": 2429,
- "nails": 2430,
- "trample": 2431,
- "Brogues": 2432,
- "track": 2433,
- "slippers": 2434,
- "clasp": 2435,
- "buckle": 2436,
- "harness": 2437,
- "lock": 2438,
- "fringe": 2439,
- "winding": 2440,
- "chain": 2441,
- "strap": 2442,
- "lash": 2443,
- "cord": 2444,
- "knot": 2445,
- "circle": 2446,
- "loop": 2447,
- "eye": 2448,
- "turn": 2449,
- "spiral": 2450,
- "coil": 2451,
- "gyre": 2452,
- "band": 2453,
- "whorl": 2454,
- "talisman": 2455,
- "heart": 2456,
- "noose": 2457,
- "necklace": 2458,
- "collar": 2459,
- "beads": 2460,
- "torc": 2461,
- "gorget": 2462,
- "scarab": 2463,
- "wood": 2464,
- "brand": 2465,
- "bludgeon": 2466,
- "cudgel": 2467,
- "loom": 2468,
- "harp": 2469,
- "master": 2470,
- "barRI": 2471,
- "hew": 2472,
- "crook": 2473,
- "mar": 2474,
- "shell": 2475,
- "stake": 2476,
- "picket": 2477,
- "pale": 2478,
- "flange": 2479,
- "Civerb's Vestments": 2480,
- "Hsarus' Trim": 2481,
- "Cleglaw's Brace": 2482,
- "Iratha's Finery": 2483,
- "Isenhart's Armory": 2484,
- "Vidala's Rig": 2485,
- "Milabrega's Regalia": 2486,
- "Cathan's Traps": 2487,
- "Tancred's Battlegear": 2488,
- "Sigon's Complete Steel": 2489,
- "Infernal Tools": 2490,
- "Berserker's Garb": 2491,
- "Death's Disguise": 2492,
- "Angelical Raiment": 2493,
- "Arctic Gear": 2494,
- "Arcanna's Tricks": 2495,
- "Civerb's": 2496,
- "Hsarus'\tHsaru's": 2497,
- "Cleglaw's": 2498,
- "Iratha's": 2499,
- "Isenhart's": 2500,
- "Vidala's": 2501,
- "Milabrega's": 2502,
- "Cathan's": 2503,
- "Tancred's": 2504,
- "Sigon's": 2505,
- "Infernal": 2506,
- "Death's": 2508,
- "Angelical": 2509,
- "Arctic": 2510,
- "Arcanna's": 2511,
- "Ward": 2512,
- "Iron Heel": 2513,
- "Tooth": 2514,
- "Collar": 2515,
- "Lightbrand": 2516,
- "Barb": 2517,
- "Orb": 2518,
- "Rule": 2519,
- "Crowbill": 2520,
- "Visor": 2521,
- "Cranium": 2522,
- "Headgear": 2523,
- "Hand": 2524,
- "Sickle": 2525,
- "Horn": 2526,
- "Sign": 2527,
- "Icon": 2528,
- "Iron Fist": 2529,
- "Claw": 2530,
- "Cuff": 2531,
- "Parry": 2532,
- "Fetlock": 2533,
- "Rod": 2534,
- "Mesh": 2535,
- "Spine": 2536,
- "Shelter": 2537,
- "Torch": 2538,
- "Hauberk": 2539,
- "Guard": 2540,
- "Mantle": 2541,
- "Furs": 2542,
- "Deathwand": 2543,
- "CudgelSI3S": 2544,
- "Iron Stay": 2545,
- "Pincers": 2546,
- "Coil": 2547,
- "Case": 2548,
- "Ambush": 2549,
- "Diadem": 2550,
- "Visage": 2551,
- "Hobnails": 2552,
- "Gage": 2553,
- "SignSI3S": 2554,
- "Hatchet": 2555,
- "Touch": 2556,
- "Halo": 2557,
- "Binding": 2558,
- "Head": 2559,
- "Horns": 2560,
- "Snare": 2561,
- "Robe": 2562,
- "Sigil": 2563,
- "Weird": 2564,
- "Sabot": 2565,
- "Wings": 2566,
- "Mitts": 2567,
- "Flesh": 2568,
- "Cord": 2569,
- "Seal": 2570,
- "SkullSI5S": 2571,
- "Wrap": 2572,
- "GuardSI6S": 2573,
- "The Gnasher": 2574,
- "Deathspade": 2575,
- "Bladebone": 2576,
- "Mindrend": 2577,
- "Rakescar": 2578,
- "Fechmars Axe": 2579,
- "Goreshovel": 2580,
- "The Chieftan": 2581,
- "Brainhew": 2582,
- "The Humongous": 2583,
- "Iros Torch": 2584,
- "Maelstromwrath": 2585,
- "Gravenspine": 2586,
- "Umes Lament": 2587,
- "Felloak": 2588,
- "Knell Striker": 2589,
- "Rusthandle": 2590,
- "Stormeye": 2591,
- "Stoutnail": 2592,
- "Crushflange": 2593,
- "Bloodrise": 2594,
- "The Generals Tan Do Li Ga": 2595,
- "Ironstone": 2596,
- "Bonesob": 2597,
- "Steeldriver": 2598,
- "Rixots Keen": 2599,
- "Blood Crescent": 2600,
- "Krintizs Skewer": 2601,
- "Gleamscythe": 2602,
- "Azurewrath": 2603,
- "Griswolds Edge": 2604,
- "Hellplague": 2605,
- "Culwens Point": 2606,
- "Shadowfang": 2607,
- "Soulflay": 2608,
- "Kinemils Awl": 2609,
- "Blacktongue": 2610,
- "Ripsaw": 2611,
- "The Patriarch": 2612,
- "Gull": 2613,
- "The Diggler": 2614,
- "The Jade Tan Do": 2615,
- "Irices Shard": 2616,
- "The Dragon Chang": 2617,
- "Razortine": 2618,
- "Bloodthief": 2619,
- "Lance of Yaggai": 2620,
- "The Tannr Gorerod": 2621,
- "Dimoaks Hew": 2622,
- "Steelgoad": 2623,
- "Soul Harvest": 2624,
- "The Battlebranch": 2625,
- "Woestave": 2626,
- "The Grim Reaper": 2627,
- "Bane Ash": 2628,
- "Serpent Lord": 2629,
- "Lazarus Spire": 2630,
- "The Salamander": 2631,
- "The Iron Jang Bong": 2632,
- "Pluckeye": 2633,
- "Witherstring": 2634,
- "Rimeraven": 2635,
- "Piercerib": 2636,
- "Pullspite": 2637,
- "Wizendraw": 2638,
- "Hellclap": 2639,
- "Blastbark": 2640,
- "Leadcrow": 2641,
- "Ichorsting": 2642,
- "Hellcast": 2643,
- "Doomspittle": 2644,
- "War Bonnet": 2645,
- "Tarnhelm": 2646,
- "Coif of Glory": 2647,
- "Duskdeep": 2648,
- "Wormskull": 2649,
- "Howltusk": 2650,
- "Undead Crown": 2651,
- "The Face of Horror": 2652,
- "Greyform": 2653,
- "Blinkbats Form": 2654,
- "The Centurion": 2655,
- "Twitchthroe": 2656,
- "Darkglow": 2657,
- "Hawkmail": 2658,
- "Sparking Mail": 2659,
- "Venomsward": 2660,
- "Iceblink": 2661,
- "Boneflesh": 2662,
- "Rockfleece": 2663,
- "Rattlecage": 2664,
- "Goldskin": 2665,
- "Victors Silk": 2666,
- "Heavenly Garb": 2667,
- "Pelta Lunata": 2668,
- "Umbral Disk": 2669,
- "Stormguild": 2670,
- "Wall of the Eyeless": 2671,
- "Swordback Hold": 2672,
- "Steelclash": 2673,
- "Bverrit Keep": 2674,
- "The Ward": 2675,
- "The Hand of Broc": 2676,
- "Bloodfist": 2677,
- "Chance Guards": 2678,
- "Magefist": 2679,
- "Frostburn": 2680,
- "Hotspur": 2681,
- "Gorefoot": 2682,
- "Treads of Cthon": 2683,
- "Goblin Toe": 2684,
- "Tearhaunch": 2685,
- "Lenyms Cord": 2686,
- "Snakecord": 2687,
- "Nightsmoke": 2688,
- "Goldwrap": 2689,
- "Bladebuckle": 2690,
- "Nokozan Relic": 2691,
- "The Eye of Etlich": 2692,
- "The Mahim-Oak Curio": 2693,
- "Nagelring": 2694,
- "Manald Heal": 2695,
- "Gorgethroat": 2696,
- "Amulet of the Viper": 2697,
- "Staff of Kings": 2698,
- "Horadric Staff": 2699,
- "Hell Forge Hammer": 2700,
- "The Stone of Jordan": 2701,
- "GloomUM": 2702,
- "Gray": 2703,
- "DireUM": 2704,
- "Black": 2705,
- "ShadowUM": 2706,
- "Haze": 2707,
- "Wind": 2708,
- "StormUM": 2709,
- "Warp": 2710,
- "Night": 2711,
- "Moon": 2712,
- "Star": 2713,
- "Pit": 2714,
- "Fire": 2715,
- "Cold": 2716,
- "Seethe": 2717,
- "SharpUM": 2718,
- "AshUM": 2719,
- "Blade": 2720,
- "SteelUM": 2721,
- "StoneUM": 2722,
- "Rust": 2723,
- "Mold": 2724,
- "Blight": 2725,
- "Plague": 2726,
- "Rot": 2727,
- "Ooze": 2728,
- "Puke": 2729,
- "Snot": 2730,
- "Bile": 2731,
- "BloodUM": 2732,
- "Pulse": 2733,
- "Gut": 2734,
- "Gore": 2735,
- "FleshUM": 2736,
- "BoneUM": 2737,
- "SpineUM": 2738,
- "Mind": 2739,
- "SpiritUM": 2740,
- "SoulUM": 2741,
- "Wrath": 2742,
- "GriefUM": 2743,
- "Foul": 2744,
- "Vile": 2745,
- "Sin": 2746,
- "ChaosUM": 2747,
- "DreadUM": 2748,
- "DoomUM": 2749,
- "BaneUM": 2750,
- "DeathUM": 2751,
- "ViperUM": 2752,
- "Dragon": 2753,
- "Devil": 2754,
- "touchUM": 2755,
- "spellUM": 2756,
- "feast": 2757,
- "wound": 2758,
- "grin": 2759,
- "maim": 2760,
- "hack": 2761,
- "biteUM": 2762,
- "rendUM": 2763,
- "burn": 2764,
- "rip": 2765,
- "kill": 2766,
- "callUM": 2767,
- "vex": 2768,
- "jade": 2769,
- "web": 2770,
- "shieldUM": 2771,
- "KillerUM": 2772,
- "RazorUM": 2773,
- "drinker": 2774,
- "shifter": 2775,
- "crawler": 2776,
- "dancer": 2777,
- "bender": 2778,
- "weaverUM": 2779,
- "eater": 2780,
- "widow": 2781,
- "maggot": 2782,
- "spawn": 2783,
- "wight": 2784,
- "GrumbleUM": 2785,
- "GrowlerUM": 2786,
- "SnarlUM": 2787,
- "wolf": 2788,
- "crow": 2789,
- "raven": 2790,
- "hawk": 2791,
- "cloud": 2792,
- "BangUM": 2793,
- "head": 2794,
- "skullUM": 2795,
- "browUM": 2796,
- "eyeUM": 2797,
- "maw": 2798,
- "tongue": 2799,
- "fangUM": 2800,
- "hornUM": 2801,
- "thorn": 2802,
- "clawUM": 2803,
- "fistUM": 2804,
- "heartUM": 2805,
- "shankUM": 2806,
- "skinUM": 2807,
- "wingUM": 2808,
- "pox": 2809,
- "fester": 2810,
- "blister": 3291,
- "pus": 2812,
- "SlimeUM": 2813,
- "drool": 2814,
- "froth": 2815,
- "sludge": 2816,
- "venom": 2817,
- "poison": 2818,
- "break": 2819,
- "shard": 2820,
- "flame": 2821,
- "maul": 2822,
- "thirstUM": 2823,
- "lust": 2824,
- "the Hammer": 2825,
- "the Axe": 2826,
- "the Sharp": 2827,
- "the Jagged": 2828,
- "the Flayer": 2829,
- "the Slasher": 2830,
- "the Impaler": 2831,
- "the Hunter": 2832,
- "the Slayer": 2833,
- "the Mauler": 2834,
- "the Destroyer": 2835,
- "theQuick": 2836,
- "the Witch": 2837,
- "the Mad": 2838,
- "the Wraith": 2839,
- "the Shade": 2840,
- "the Dead": 2841,
- "the Unholy": 2842,
- "the Howler": 2843,
- "the Grim": 2844,
- "the Dark": 2845,
- "the Tainted": 2846,
- "the Unclean": 2847,
- "the Hungry": 2848,
- "the Cold": 2849,
- "The Cow King": 2850,
- "Grand Vizier of Chaos": 2851,
- "Lord De Seis": 2852,
- "Infector of Souls": 2853,
- "Riftwraith the Cannibal": 2854,
- "Taintbreeder": 2855,
- "The Tormentor": 2856,
- "Winged Death": 2857,
- "Maffer Dragonhand": 2858,
- "Wyand Voidfinger": 2859,
- "Toorc Icefist": 2860,
- "Bremm Sparkfist": 2861,
- "Geleb Flamefinger": 2862,
- "Ismail Vilehand": 2863,
- "Icehawk Riftwing": 2864,
- "Sarina the Battlemaid": 2865,
- "Stormtree": 2866,
- "Witch Doctor Endugu": 2867,
- "Web Mage the Burning": 2868,
- "Bishibosh": 2869,
- "Bonebreak": 2870,
- "Coldcrow": 2871,
- "Rakanishu": 2872,
- "Treehead WoodFist": 2873,
- "Griswold": 2874,
- "The Countess": 2875,
- "Pitspawn Fouldog": 2876,
- "Flamespike the Crawler": 2877,
- "Boneash": 2878,
- "Radament": 2879,
- "Bloodwitch the Wild": 2880,
- "Fangskin": 2881,
- "Beetleburst": 2882,
- "Leatherarm": 2883,
- "Coldworm the Burrower": 2884,
- "Fire Eye": 2885,
- "Dark Elder": 2886,
- "The Summoner": 2887,
- "Ancient Kaa the Soulless": 2888,
- "The Smith": 2889,
- "DeckardCain": 2890,
- "Gheed": 2891,
- "Akara": 2892,
- "Kashya": 2893,
- "Charsi": 2894,
- "Wariv": 2895,
- "Warriv": 2896,
- "Rogue": 2897,
- "StygianDoll": 2898,
- "SoulKiller": 2899,
- "Flayer": 2900,
- "Fetish": 2901,
- "RatMan": 2902,
- "Undead StygianDoll": 2903,
- "Undead SoulKiller": 2904,
- "Undead Flayer": 2905,
- "Undead Fetish": 2906,
- "Undead RatMan": 2907,
- "DarkFamiliar": 2908,
- "BloodDiver": 2909,
- "Gloombat": 2910,
- "DesertWing": 2911,
- "Banished": 2912,
- "BloodLord": 2913,
- "DarkLord": 2914,
- "NightLord": 2915,
- "GhoulLord": 2916,
- "Spikefist": 2917,
- "Thrasher": 2918,
- "BrambleHulk": 2919,
- "ThornedHulk": 2920,
- "SpiderMagus": 2921,
- "FlameSpider": 2922,
- "PoisonSpinner": 2923,
- "SandFisher": 2924,
- "Arach": 2925,
- "BloodWing": 2926,
- "BloodHook": 2927,
- "Feeder": 2928,
- "Sucker": 2929,
- "WingedNightmare": 2930,
- "HellBuzzard": 2931,
- "UndeadScavenger": 2932,
- "CarrionBird": 2933,
- "Unraveler": 2934,
- "Guardian": 2935,
- "HollowOne": 2936,
- "Horadrim Ancient": 2937,
- "AlbinoRoach": 2938,
- "SteelWeevil": 2939,
- "Scarab": 2940,
- "SandWarrior": 2941,
- "DungSoldier": 2942,
- "HellSwarm": 2943,
- "PlagueBugs": 2944,
- "BlackLocusts": 2945,
- "Itchies": 2946,
- "HellCat": 2947,
- "NightTiger": 2948,
- "SaberCat": 2949,
- "Huntress": 2950,
- "RazorPitDemon": 2951,
- "TreeLurker": 2952,
- "CaveLeaper": 2953,
- "TombCreeper": 2954,
- "SandLeaper": 2955,
- "TombViper": 2956,
- "PitViper": 2957,
- "Salamander": 2958,
- "ClawViper": 2959,
- "SerpentMagus": 2960,
- "WorldKiller": 2961,
- "GiantLamprey": 2962,
- "Devourer": 2963,
- "RockWorm": 2964,
- "SandMaggot": 2965,
- "JungleUrchin": 2966,
- "RazorSpine": 2967,
- "ThornBeast": 2968,
- "SpikeFiend": 2969,
- "QuillRat": 2970,
- "HellClan": 2971,
- "MoonClan": 2972,
- "NightClan": 2973,
- "DeathClan": 2974,
- "BloodClan": 2975,
- "TempleGuard": 2976,
- "DoomApe": 2977,
- "JungleHunter": 2978,
- "RockDweller": 2979,
- "DuneBeast": 2980,
- "FleshHunter": 2981,
- "BlackRogue": 2982,
- "DarkStalker": 2983,
- "VileHunter": 2984,
- "DarkHunter": 2985,
- "DarkShape": 2986,
- "Apparition": 2987,
- "Specter": 2988,
- "Ghost": 2990,
- "Assailant": 2991,
- "Infidel": 2992,
- "Invader": 2993,
- "Marauder": 2994,
- "SandRaider": 2995,
- "GargantuanBeast": 2996,
- "WailingBeast": 2997,
- "Yeti": 2998,
- "Crusher": 2999,
- "Brute": 3000,
- "CloudStalker": 3001,
- "BlackVulture": 3002,
- "BlackRaptor": 3003,
- "BloodHawk": 3004,
- "FoulCrow": 3005,
- "PlagueBearer": 3006,
- "Ghoul": 3007,
- "DrownedCarcass": 3008,
- "HungryDead": 3009,
- "Zombie": 3010,
- "Skeleton": 3011,
- "Horror": 3012,
- "Returned": 3013,
- "BurningDead": 3014,
- "BoneWarrior": 3015,
- "Damned": 3016,
- "Disfigured": 3017,
- "Misshapen": 3018,
- "Tainted": 3019,
- "Afflicted": 3020,
- "Andariel": 3021,
- "Natalya": 3022,
- "Drognan": 3023,
- "Atma": 3024,
- "Fara": 3025,
- "Lysander": 3026,
- "Jerhyn": 3027,
- "jerhyn": 3028,
- "Geglash": 3029,
- "Elzix": 3030,
- "Greiz": 3031,
- "Meshif": 3032,
- "Camel": 3033,
- "Cadaver": 3034,
- "PreservedDead": 3035,
- "Embalmed": 3036,
- "DriedCorpse": 3037,
- "Decayed": 3038,
- "Urdar": 3039,
- "Mauler": 3040,
- "Gorbelly": 3041,
- "Blunderbore": 3042,
- "WorldKillerYoung": 3043,
- "GiantLampreyYoung": 3044,
- "DevourerYoung": 3045,
- "RockWormYoung": 3046,
- "SandMaggotYoung": 3047,
- "WorldKillerEgg": 3048,
- "GiantLampreyEgg": 3049,
- "DevourerEgg": 3050,
- "RockWormEgg": 3051,
- "SandMaggotEgg": 3052,
- "Maggot": 3053,
- "Duriel": 3054,
- "BloodHawkNest": 3055,
- "FlyingScimitar": 3056,
- "CloudStalkerNest": 3057,
- "BlackVultureNest": 3058,
- "FoulCrowNest": 3059,
- "Diablo": 3060,
- "Baal": 3061,
- "Mephisto": 3062,
- "Cantor": 3063,
- "Heirophant": 3064,
- "Sexton": 3065,
- "Zealot": 3066,
- "Faithful": 3067,
- "Zakarumite": 3068,
- "BlackSoul": 3069,
- "BurningSoul": 3070,
- "SwampGhost": 3071,
- "Gloam": 3072,
- "WarpedShaman": 3073,
- "DarkShaman": 3074,
- "DevilkinShaman": 3075,
- "CarverShaman": 3076,
- "FallenShaman": 3077,
- "WarpedFallen": 3078,
- "DarkOne": 3079,
- "Devilkin": 3080,
- "Carver": 3081,
- "Fallen": 3082,
- "ReturnedArcher": 3083,
- "HorrorArcher": 3084,
- "BurningDeadArcher": 3085,
- "BoneArcher": 3086,
- "CorpseArcher": 3087,
- "SkeletonArcher": 3088,
- "FleshLancer": 3089,
- "BlackLancer": 3090,
- "DarkLancer": 3091,
- "VileLancer": 3092,
- "DarkSpearwoman": 3093,
- "FleshArcher": 3094,
- "BlackArcher": 3095,
- "DarkRanger": 3096,
- "VileArcher": 3097,
- "DarkArcher": 3098,
- "Summoner": 3099,
- "StygianDollShaman": 3100,
- "SoulKillerShaman": 3101,
- "FlayerShaman": 3102,
- "FetishShaman": 3103,
- "RatManShaman": 3104,
- "HorrorMage": 3105,
- "BurningDeadMage": 3106,
- "BoneMage": 3107,
- "CorpseMage": 3108,
- "ReturnedMage": 3109,
- "GargoyleTrap": 3110,
- "Bloodraven": 3111,
- "navi": 3112,
- "Kaelan": 3113,
- "meshif": 3114,
- "StygianWatcherHead": 3115,
- "RiverStalkerHead": 3116,
- "WaterWatcherHead": 3117,
- "StygianWatcherLimb": 3118,
- "RiverStalkerLimb": 3119,
- "WaterWatcherLimb": 3120,
- "NightMarauder": 3121,
- "FireGolem": 3122,
- "IronGolem": 3123,
- "BloodGolem": 3124,
- "ClayGolem": 3125,
- "WorldKillerQueen": 3126,
- "GiantLampreyQueen": 3127,
- "DevourerQueen": 3128,
- "RockWormQueen": 3129,
- "SandMaggotQueen": 3130,
- "Slime Prince": 3131,
- "Bog Creature": 3132,
- "Swamp Dweller": 3133,
- "GiantUrchin": 3134,
- "RazorBeast": 3135,
- "ThornBrute": 3136,
- "SpikeGiant": 3137,
- "QuillBear": 3138,
- "Council Member": 3139,
- "youngdiablo": 3140,
- "darkwanderer": 3141,
- "HellSlinger": 3142,
- "NightSlinger": 3143,
- "SpearCat": 3144,
- "Slinger": 3145,
- "FireTower": 3146,
- "LightningSpire": 3147,
- "PitLord": 3148,
- "Balrog": 3149,
- "VenomLord": 3150,
- "Iron Wolf": 3151,
- "InvisoSpawner": 3152,
- "OblivionKnight": 3153,
- "Mage": 3154,
- "AbyssKnight": 3155,
- "Fighter Mage": 3156,
- "DoomKnight": 3157,
- "Fighter": 3158,
- "MawFiend": 3159,
- "CorpseSpitter": 3160,
- "Corpulent": 3161,
- "StormCaster": 3162,
- "Strangler": 3163,
- "Groper": 3164,
- "GrotesqueWyrm": 3165,
- "StygianDog": 3166,
- "FleshBeast": 3167,
- "Grotesque": 3168,
- "StygianHag": 3169,
- "FleshSpawner": 3170,
- "RogueScout": 3171,
- "BloodWingNest": 3172,
- "BloodHookNest": 3173,
- "FeederNest": 3174,
- "SuckerNest": 3175,
- "NecroMage": 3176,
- "NecroSkeleton": 3177,
- "TrappedSoul": 3178,
- "Valkyrie": 3179,
- "Dopplezon": 3180,
- "Raises Fetishes": 3181,
- "Raises Undead": 3182,
- "Lays Eggs": 3183,
- "Raises Fallen": 3184,
- "heals Zealots and Cantors": 3185,
- "drains mana and stamina": 3186,
- "drains mana": 3187,
- "drains stamina": 3188,
- "stun attack": 3189,
- "eats and spits corspes": 3190,
- "homing missiles": 3191,
- "raises Stygian Dolls": 3192,
- "raises Soul Killers": 3193,
- "raises Flayers": 3194,
- "raises Fetishes": 3195,
- "raises Ratmen": 3196,
- "steals life": 3197,
- "raises undead": 3198,
- "raises Dark Ones": 3199,
- "raises Devilkin": 3200,
- "raises Carvers": 3201,
- "raises Fallen": 3202,
- "raises Warped Fallen": 3203,
- "shocking hit": 3204,
- "uniquextrastrong": 3205,
- "uniqueextrafast": 3206,
- "uniquecursed": 3207,
- "uniquemagicresistance": 3208,
- "uniquefireenchanted": 3209,
- "monsteruniqueprop1": 3210,
- "monsteruniqueprop2": 3211,
- "monsteruniqueprop3": 3212,
- "monsteruniqueprop4": 3213,
- "monsteruniqueprop5": 3214,
- "monsteruniqueprop6": 3215,
- "monsteruniqueprop7": 3216,
- "monsteruniqueprop8": 3217,
- "monsteruniqueprop9": 3218,
- "This Cow Bites": 3219,
- "Champion": 3220,
- "minion": 3221,
- "Barrel": 3222,
- "Lever1": 3223,
- "BarrelEx": 3224,
- "Door": 3225,
- "Portal": 3226,
- "ODoor": 3227,
- "BlockedDoor": 3228,
- "LockedDoor": 3229,
- "StoneAlpha": 3230,
- "StoneBeta": 3231,
- "StoneDelta": 3232,
- "StoneGamma": 3233,
- "StoneLambda": 3234,
- "StoneTheta": 3235,
- "Crate": 3236,
- "Casket": 3237,
- "Cabinet": 3238,
- "Vase": 3239,
- "Inifuss": 3240,
- "corpse": 3241,
- "RogueCorpse": 3242,
- "CorpseOnStick": 3243,
- "TowerTome": 3244,
- "Gibbet": 3245,
- "MummyGenerator": 3246,
- "ArmorStand": 3247,
- "WeaponRack": 3248,
- "Sarcophagus": 3249,
- "Trap Door": 3250,
- "LargeUrn": 3251,
- "CanopicJar": 3252,
- "Obelisk": 3253,
- "HoleAnim": 3254,
- "Shrine": 3255,
- "Urn": 3256,
- "Waypoint": 22526,
- "Well": 3258,
- "bag": 3259,
- "Chest": 3260,
- "chest": 3261,
- "lockedchest": 3262,
- "HorazonsJournal": 3263,
- "templeshrine": 3264,
- "stair": 3265,
- "coffin": 3266,
- "bookshelf": 3267,
- "loose boulder": 3268,
- "loose rock": 3269,
- "hollow log": 3270,
- "hiding spot": 3271,
- "fire": 3328,
- "Chest3": 3273,
- "hidden stash": 3274,
- "GuardCorpse": 3275,
- "bowl": 3276,
- "jug": 3277,
- "AmbientSound": 3278,
- "ratnest": 3279,
- "burning body": 3280,
- "well": 22525,
- "door": 3282,
- "skeleton": 3283,
- "skullpile": 3284,
- "cocoon": 3285,
- "gidbinn altar": 3286,
- "cowa": 3287,
- "manashrine": 3288,
- "bed": 3289,
- "ratchest": 3290,
- "bank": 3292,
- "goo pile": 3293,
- "holyshrine": 3294,
- "teleportation pad": 3295,
- "ratchest-r": 3296,
- "skull pile": 3297,
- "body": 3298,
- "hell bridge": 3299,
- "compellingorb": 3300,
- "basket": 3301,
- "Basket": 3302,
- "RockPIle": 3303,
- "Tome": 3304,
- "dead body": 3305,
- "eunuch": 3306,
- "dead guard": 3307,
- "portal": 3308,
- "sarcophagus": 3309,
- "dead villager": 3310,
- "sewer lever": 3311,
- "sewer stairs": 3312,
- "magic shrine": 3313,
- "wirt's body": 3314,
- "stash": 3315,
- "guyq": 3316,
- "taintedsunaltar": 3317,
- "Hellforge": 3318,
- "Corpsefire": 3319,
- "fissure": 3320,
- "BoneChest": 3321,
- "casket": 3322,
- "HungSkeleton": 3323,
- "pillar": 3324,
- "Hydra": 3325,
- "Turret": 3326,
- "a trap": 3327,
- "cost": 3329,
- "Repair": 3330,
- "Sell": 3331,
- "Identify": 3332,
- "priceless": 3333,
- "NPCMenuTradeRepair": 3334,
- "NPCPurchaseItems": 3335,
- "NPCSellItems": 3336,
- "NPCHeal": 3337,
- "NPCRepairItems": 3338,
- "NPCNextPage": 3339,
- "NPCPreviousPage": 3340,
- "strUiMenu2": 4131,
- "TransactionMenu1a": 3342,
- "TransactionMenu1f": 3343,
- "VerifyTransaction1": 3344,
- "VerifyTransaction2": 3345,
- "VerifyTransaction3": 3346,
- "VerifyTransaction4": 3347,
- "VerifyTransaction5": 3348,
- "VerifyTransaction6": 3349,
- "VerifyTransaction7": 3350,
- "VerifyTransaction8": 3351,
- "VerifyTransaction9": 3352,
- "TransactionResults1": 3353,
- "TransactionResults2": 3354,
- "TransactionResults3": 3355,
- "TransactionResults4": 3356,
- "TransactionResults5": 3357,
- "TransactionResults6": 3358,
- "TransactionResults7": 3359,
- "TransactionResults8": 3360,
- "TransactionResults9": 3361,
- "TransactionResults10": 3362,
- "TransactionResults11": 3363,
- "ItemDesc1s": 3364,
- "ItemDesc1t": 3365,
- "HP": 3366,
- "AC": 3367,
- "Level": 3368,
- "Cost": 3369,
- "Damage": 3370,
- "strhirespecial1": 3371,
- "strhirespecial2": 3372,
- "strhirespecial3": 3373,
- "strhirespecial4": 3374,
- "strhirespecial5": 3375,
- "strhirespecial6": 3376,
- "strhirespecial7": 3377,
- "strhirespecial8": 3378,
- "strhirespecial9": 3379,
- "strhirespecial10": 3380,
- "TalkMenu": 3381,
- "WarrivMenu1b": 3382,
- "WarrivMenu1c": 3383,
- "MeshifMenuEast": 3384,
- "MeshifMenuWest": 3385,
- "NPCMenuNews0": 3386,
- "NPCMenuNews1": 3387,
- "NPCMenuNews2": 3388,
- "NPCMenuNews3": 3389,
- "NPCMenuNews4": 3390,
- "NPCMenuTalkMore": 3391,
- "NPCTownMore0": 3392,
- "NPCTownMore1": 3393,
- "NPCMenuLeave": 3394,
- "NPCGossipMenu": 3395,
- "NPCMenuTrade": 3396,
- "NPCMenuHire": 3397,
- "gamble": 3398,
- "Intro": 3399,
- "Back": 3400,
- "ok": 3401,
- "cancel": 3402,
- "Continue": 3403,
- "strMenuMain15": 3404,
- "strOptMusic": 3405,
- "strOptSound": 3406,
- "strOptGamma": 3407,
- "strOptRender": 3408,
- "strOptPrevious": 3409,
- "cfgCtrl": 3410,
- "merc01": 3411,
- "merc02": 3412,
- "merc03": 3413,
- "merc04": 3414,
- "merc05": 3415,
- "merc06": 3416,
- "merc07": 3417,
- "merc08": 3418,
- "merc09": 3419,
- "merc10": 3420,
- "merc11": 3421,
- "merc12": 3422,
- "merc13": 3423,
- "merc14": 3424,
- "merc15": 3425,
- "merc16": 3426,
- "merc17": 3427,
- "merc18": 3428,
- "merc19": 3429,
- "merc20": 3430,
- "merc21": 3431,
- "merc22": 3432,
- "merc23": 3433,
- "merc24": 3434,
- "merc25": 3435,
- "merc26": 3436,
- "merc27": 3437,
- "merc28": 3438,
- "merc29": 3439,
- "merc30": 3440,
- "merc31": 3441,
- "merc32": 3442,
- "merc33": 3443,
- "merc34": 3444,
- "merc35": 3445,
- "merc36": 3446,
- "merc37": 3447,
- "merc38": 3448,
- "merc39": 3449,
- "merc40": 3450,
- "merc41": 3451,
- "merclevelup": 3452,
- "Socketable": 3453,
- "ItemStats1a": 3454,
- "ItemStats1b": 3455,
- "ItemStats1c": 3456,
- "ItemStats1d": 3457,
- "ItemStats1e": 3458,
- "ItemStats1f": 3459,
- "ItemStats1g": 3460,
- "ItemStats1h": 3461,
- "ItemStats1i": 3462,
- "ItemStats1j": 3463,
- "ItemStast1k": 3464,
- "ItemStats1l": 3465,
- "ItemStats1m": 3466,
- "ItemStats1n": 3467,
- "ItemStats1o": 3468,
- "ItemStats1p": 3469,
- "ItemStats1q": 3470,
- "ItemStatsrejuv1": 3471,
- "ItemStatsrejuv2": 3472,
- "ModStr1a": 3473,
- "ModStr1b": 3474,
- "ModStr1c": 3475,
- "ModStr1d": 3476,
- "ModStr1e": 3477,
- "ModStr1f": 3478,
- "ModStr1g": 3479,
- "ModStr1h": 3480,
- "ModStr1i": 3481,
- "ModStr1j": 3482,
- "ModStr1k": 3483,
- "ModStr1l": 3484,
- "ModStr1m": 3485,
- "ModStr1n": 3486,
- "ModStr1o": 3487,
- "ModStr1p": 3488,
- "ModStr1q": 3489,
- "ModStr1r": 3490,
- "ModStr1s": 3491,
- "ModStr1t": 3492,
- "ModStr1u": 3493,
- "ModStr1v": 3494,
- "ModStr1w": 3495,
- "ModStr1x": 3496,
- "ModStr1y": 3497,
- "ModStr1z": 3498,
- "ModStr2a": 3499,
- "ModStr2b": 3500,
- "ModStr2c": 3501,
- "ModStr2d": 3502,
- "ModStr2e": 3503,
- "ModStr2f": 3504,
- "ModStr2g": 3505,
- "ModStr2h": 3506,
- "ModStr2i": 3507,
- "ModStr2j": 3508,
- "ModStr2k": 3509,
- "ModStr2l": 3510,
- "ModStr2m": 3511,
- "ModStr2n": 3512,
- "ModStr2o": 3513,
- "ModStr2p": 3514,
- "ModStr2q": 3515,
- "ModStr2r": 3516,
- "ModStr2s": 3517,
- "ModStr2t": 3518,
- "ModStr2u": 3519,
- "Modstr2v": 3520,
- "ModStr2w": 3521,
- "ModStr2x": 3522,
- "ModStr2y": 3523,
- "ModStr2z": 3524,
- "ModStr3a": 3525,
- "ModStr3b": 3526,
- "ModStr3c": 3527,
- "ModStr3d": 3528,
- "ModStr3e": 3529,
- "ModStr3f": 3530,
- "ModStr3g": 3531,
- "ModStr3h": 3532,
- "ModStr3i": 3533,
- "ModStr3j": 3534,
- "ModStr3k": 3535,
- "ModStr3l": 3536,
- "ModStr3m": 3537,
- "ModStr3n": 3538,
- "ModStr3o": 3539,
- "ModStr3p": 3540,
- "ModStr3q": 3541,
- "ModStr3r": 3542,
- "ModStr3u": 3543,
- "ModStr3v": 3544,
- "ModStr3w": 3545,
- "ModStr3x": 3546,
- "ModStr3y": 3547,
- "ModStr3z": 3548,
- "ModStr4a": 3549,
- "ModStr4b": 3550,
- "ModStr4c": 3551,
- "ModStr4d": 3552,
- "ModStr4e": 3553,
- "ModStr4f": 3554,
- "ModStr4g": 3555,
- "ModStr4h": 3556,
- "ModStr4i": 3557,
- "ModStr4j": 3558,
- "ModStr4k": 3559,
- "ModStr4l": 3560,
- "ModStr4m": 3561,
- "ModStr4n": 3562,
- "ModStr4o": 3563,
- "ModStr4p": 3564,
- "ModStr4q": 3565,
- "ModStr4r": 3566,
- "ModStr4s": 3567,
- "ModStr4t": 3568,
- "ModStr4u": 3569,
- "ModStr4v": 3570,
- "ModStr4w": 3571,
- "ModStr4x": 3572,
- "ModStr4y": 3573,
- "ModStr4z": 3574,
- "ModStr5a": 3575,
- "ModStr5b": 3576,
- "ModStr5c": 3577,
- "ModStr5d": 3578,
- "ModStr5e": 3579,
- "ModStr5f": 3580,
- "ModStr5g": 3581,
- "ModStr5h": 3582,
- "ModStr5i": 3583,
- "ModStr5j": 3584,
- "ModStr5k": 3585,
- "ModStr5l": 3586,
- "ModStr5m": 3587,
- "ModStr5n": 3588,
- "ModStr5o": 3589,
- "ModStr5p": 3590,
- "ModStr5q": 3591,
- "ModStr5r": 3592,
- "ModStr5s": 3593,
- "ModStr5t": 3594,
- "ModStr5u": 3595,
- "ModStr5v": 3596,
- "ModStr5w": 3597,
- "ModStr5x": 3598,
- "ModStr5y": 3599,
- "ModStr5z": 3600,
- "ModStr6a": 3601,
- "ModStr6b": 3602,
- "ModStr6c": 3603,
- "ModStr6d": 3604,
- "ModStr6e": 3605,
- "ModStr6f": 3606,
- "ModStr6g": 3607,
- "ModStr6h": 3608,
- "ModStr6i": 3609,
- "strModAllResistances": 3610,
- "strModAllSkillLevels": 3611,
- "strModFireDamage": 3612,
- "strModFireDamageRange": 3613,
- "strModColdDamage": 3614,
- "strModColdDamageRange": 3615,
- "strModLightningDamage": 3616,
- "strModLightningDamageRange": 3617,
- "strModMagicDamage": 3618,
- "strModMagicDamageRange": 3619,
- "strModPoisonDamage": 3620,
- "strModPoisonDamageRange": 3621,
- "strModMinDamage": 3622,
- "strModMinDamageRange": 3623,
- "strModEnhancedDamage": 3624,
- "improved damage": 3625,
- "improved to hit": 3626,
- "improved armor class": 3627,
- "improved durability": 3628,
- "Quick Strike": 3629,
- "strGemPlace1": 3630,
- "strGemPlace2": 3631,
- "gemeffect1": 3632,
- "gemeffect2": 3633,
- "gemeffect3": 3634,
- "gemeffect4": 3635,
- "gemeffect5": 3636,
- "gemeffect6": 3637,
- "gemeffect7": 3638,
- "sysmsg1": 3639,
- "sysmsg2": 3640,
- "sysmsg3": 3641,
- "sysmsg4": 3642,
- "sysmsg3a": 3643,
- "sysmsg4a": 3644,
- "sysmsg5": 3645,
- "sysmsg6": 3646,
- "sysmsg7": 3647,
- "sysmsg8": 3648,
- "sysmsg9": 3649,
- "sysmsg10": 3650,
- "sysmsg11": 3651,
- "sysmsg12": 3652,
- "sysmsgPlayer": 3653,
- "chatmsg1": 3654,
- "chatmsg2": 3655,
- "chatmsg3": 3657,
- "strwhisperworked": 3658,
- "syswork": 3659,
- "ShrId0": 3660,
- "ShrId1": 3661,
- "ShrId2": 3662,
- "ShrId3": 3663,
- "ShrId4": 3664,
- "ShrId5": 3665,
- "ShrId6": 3666,
- "ShrId7": 3667,
- "ShrId8": 3668,
- "ShrId9": 3669,
- "ShrId10": 3670,
- "ShrId11": 3671,
- "ShrId12": 3672,
- "ShrId13": 3673,
- "ShrId14": 3674,
- "ShrId15": 3675,
- "ShrId16": 3676,
- "ShrId17": 3677,
- "ShrId18": 3678,
- "ShrId19": 3679,
- "ShrId20": 3680,
- "ShrId21": 3681,
- "ShrId22": 3682,
- "ShrMsg0": 3683,
- "ShrMsg1": 3684,
- "ShrMsg2": 3685,
- "ShrMsg3": 3686,
- "ShrMsg4": 3687,
- "ShrMsg5": 3688,
- "ShrMsg6": 3689,
- "ShrMsg7": 3690,
- "ShrMsg8": 3691,
- "ShrMsg9": 3692,
- "ShrMsg10": 3693,
- "ShrMsg11": 3694,
- "ShrMsg12": 3695,
- "ShrMsg13": 3696,
- "ShrMsg14": 3697,
- "ShrMsg15": 3698,
- "ShrMsg16": 3699,
- "ShrMsg17": 3700,
- "ShrMsg18": 3701,
- "ShrMsg19": 3702,
- "ShrMsg20": 3703,
- "ShrMsg21": 3704,
- "ShrMsg22": 3705,
- "strqi1": 3706,
- "strqi2": 3707,
- "stsa1q3alert": 3708,
- "stsa1q4alert": 3709,
- "stsa3q1alert": 3710,
- "qstsa1qt": 3711,
- "qstsa1qt0": 3712,
- "qstsa1q0": 3713,
- "qstsa1q1": 3714,
- "qstsa1q2": 3715,
- "qstsa1q3": 3716,
- "qstsa1q4": 3717,
- "qstsa1q5": 3718,
- "qstsa1q6": 3719,
- "strplaylast": 3720,
- "newquestlog": 3721,
- "qsts": 3722,
- "noactivequest": 3723,
- "qstsxxx": 3724,
- "qstsnull": 3725,
- "qstsComplete": 3726,
- "qstsother": 3727,
- "qstsprevious": 3728,
- "qstsThankYouComeAgain": 3729,
- "qstsThankYouComeAgainMulti": 3730,
- "qstsThankYouComeAgainSingle": 3731,
- "Qstsyouarenot8": 3732,
- "qstsa1q3x": 3733,
- "qstsa1q4x": 3734,
- "qstsa1q11": 3735,
- "qstsa1q12": 3736,
- "qstsa1q13": 3737,
- "qstsa1q14": 3738,
- "qstsa1q140": 3739,
- "qstsa1q15": 3740,
- "qstsa1q21": 3741,
- "qstsa1q22": 3742,
- "qstsa1q23": 3743,
- "qstsa1q41": 3744,
- "qstsa1q42": 3745,
- "qstsa1q43": 3746,
- "qstsa1q44": 3747,
- "qstsa1q45": 3748,
- "qstsa1q46": 3749,
- "qstsa1q46b": 3750,
- "qstsa1q51": 3751,
- "qstsa1q51a": 3752,
- "qstsa1q51b": 3753,
- "qstsa1q52": 3754,
- "qstsa1q31": 3755,
- "qstsa1q32": 3756,
- "qstsa1q32b": 3757,
- "qstsa1q61": 3758,
- "qstsa1q62": 3759,
- "qstsa1q62b": 3760,
- "qstsa1q63": 3761,
- "KeyNone": 3762,
- "KeyLButton": 3763,
- "KeyRButton": 3764,
- "KeyCancel": 3765,
- "KeyMButton": 3766,
- "Key4Button": 3767,
- "Key5Button": 3768,
- "KeyWheelUp": 3769,
- "KeyWheelDown": 3770,
- "KeyKana": 3771,
- "KeyJunja": 3772,
- "KeyFinal": 3773,
- "KeyKanji": 3774,
- "KeyEscape": 3775,
- "KeyConvert": 3776,
- "KeyNonConvert": 3777,
- "KeyAccept": 3778,
- "KeyModeChange": 3779,
- "KeyLeft": 3780,
- "KeyUp": 3781,
- "KeyRight": 3782,
- "KeyDown": 3783,
- "KeySelect": 3784,
- "KeyExecute": 3785,
- "KeyLWin": 3786,
- "KeyRWin": 3787,
- "KeyApps": 3788,
- "KeyNumLock": 3789,
- "KeyBack": 3790,
- "KeyTab": 3791,
- "KeyClear": 3792,
- "KeyReturn": 3793,
- "KeyShift": 3794,
- "KeyControl": 3795,
- "KeyMenu": 3796,
- "KeyPause": 3797,
- "KeyCapital": 3798,
- "KeySpace": 3799,
- "KeyPrior": 3800,
- "KeyNext": 3801,
- "KeyEnd": 3802,
- "KeyHome": 3803,
- "KeyPrint": 3804,
- "KeySnapshot": 3805,
- "KeyInsert": 3806,
- "KeyDelete": 3807,
- "KeyHelp": 3808,
- "KeyNumPad0": 3809,
- "KeyNumPad1": 3810,
- "KeyNumPad2": 3811,
- "KeyNumPad3": 3812,
- "KeyNumPad4": 3813,
- "KeyNumPad5": 3814,
- "KeyNumPad6": 3815,
- "KeyNumPad7": 3816,
- "KeyNumPad8": 3817,
- "KeyNumPad9": 3818,
- "KeyMultiply": 3819,
- "KeyAdd": 3820,
- "KeySeparator": 3821,
- "KeySubtract": 3822,
- "KeyDecimal": 3823,
- "KeyDivide": 3824,
- "KeyF1": 3825,
- "KeyF2": 3826,
- "KeyF3": 3827,
- "KeyF4": 3828,
- "KeyF5": 3829,
- "KeyF6": 3830,
- "KeyF7": 3831,
- "KeyF8": 3832,
- "KeyF9": 3833,
- "KeyF10": 3834,
- "KeyF11": 3835,
- "KeyF12": 3836,
- "KeyF13": 3837,
- "KeyF14": 3838,
- "KeyF15": 3839,
- "KeyF16": 3840,
- "KeyF17": 3841,
- "KeyF18": 3842,
- "KeyF19": 3843,
- "KeyF20": 3844,
- "KeyF21": 3845,
- "KeyF22": 3846,
- "KeyF23": 3847,
- "KeyF24": 3848,
- "KeyScroll": 3849,
- "KeySemicolon": 3850,
- "KeyEqual": 3851,
- "KeyComma": 3852,
- "KeyMinus": 3853,
- "KeyPeriod": 3854,
- "KeySlash": 3855,
- "KeyTilde": 3856,
- "KeyLBracket": 3857,
- "KeyBackslash": 3858,
- "KeyRBracket": 3859,
- "KeyApostrophe": 3860,
- "ShorthandKeyMButton": 3861,
- "ShorthandKey4Button": 3862,
- "ShorthandKey5Button": 3863,
- "ShorthandKeyWheelUp": 3864,
- "ShorthandKeyWheelDown": 3865,
- "ShorthandKeyKana": 3866,
- "ShorthandKeyJunja": 3867,
- "ShorthandKeyFinal": 3868,
- "ShorthandKeyKanji": 3869,
- "ShorthandKeyEscape": 3870,
- "ShorthandKeyConvert": 3871,
- "ShorthandKeyNonConvert": 3872,
- "ShorthandKeyAccept": 3873,
- "ShorthandKeyModeChange": 3874,
- "ShorthandKeyLeft": 3875,
- "ShorthandKeyRight": 3876,
- "ShorthandKeyDown": 3877,
- "ShorthandKeySelect": 3878,
- "ShorthandKeyExecute": 3879,
- "ShorthandKeyLeftWindows": 3880,
- "ShorthandKeyRightWindows": 3881,
- "ShorthandKeyApps": 3882,
- "ShorthandKeyNumLock": 3883,
- "ShorthandKeyBackspace": 3884,
- "ShorthandKeyClear": 3885,
- "ShorthandKeyEnter": 3886,
- "ShorthandKeyShift": 3887,
- "ShorthandKeyControl": 3888,
- "ShorthandKeyPause": 3889,
- "ShorthandKeyCapsLock": 3890,
- "ShorthandKeySpace": 3891,
- "ShorthandKeyPageUp": 3892,
- "ShorthandKeyPageDown": 3893,
- "ShorthandKeyHome": 3894,
- "ShorthandKeyPrintScreen": 3895,
- "ShorthandKeyInsert": 3896,
- "ShorthandKeyDelete": 3897,
- "ShorthandKeyHelp": 3898,
- "ShorthandKeyNumPad0": 3899,
- "ShorthandKeyNumPad1": 3900,
- "ShorthandKeyNumPad2": 3901,
- "ShorthandKeyNumPad3": 3902,
- "ShorthandKeyNumPad4": 3903,
- "ShorthandKeyNumPad5": 3904,
- "ShorthandKeyNumPad6": 3905,
- "ShorthandKeyNumPad7": 3906,
- "ShorthandKeyNumPad8": 3907,
- "ShorthandKeyNumPad9": 3908,
- "ShorthandKeyNumPad*\tnp*": 3909,
- "ShorthandKeyNumPad+\tnp+": 3910,
- "ShorthandKeyNumPad-\tnp-": 3911,
- "ShorthandKeyNumPad.\tnp.": 3912,
- "ShorthandKeyNumPad/\tnp/": 3913,
- "ShorthandKeyScroll": 3914,
- "KeyMacOption": 3915,
- "KeyMacCommand": 3916,
- "KeyMacNumPad=\tNum Pad =": 3917,
- "ShorthandKeyMacOption": 3918,
- "ShorthandKeyMacCommand": 3919,
- "ShorthandKeyMacNumPad=\tNP=": 3920,
- "CfgFunction": 3921,
- "CfgPrimaryKey": 3922,
- "CfgSecondaryKey": 3923,
- "CfgCharacter": 3924,
- "CfgInventory": 3925,
- "CfgParty": 3926,
- "CfgMessageLog": 3927,
- "CfgQuestLog": 3928,
- "CfgChat": 3929,
- "CfgAutoMap": 3930,
- "CfgAutoMapCenter": 3931,
- "CfgMiniMap": 3932,
- "CfgHelp": 3933,
- "CfgSkillTree": 3934,
- "CfgSkillPick": 3935,
- "CfgSkill1": 3936,
- "CfgSkill2": 3937,
- "CfgSkill3": 3938,
- "CfgSkill4": 3939,
- "CfgSkill5": 3940,
- "CfgSkill6": 3941,
- "CfgSkill7": 3942,
- "CfgSkill8": 3943,
- "Cfgskillup": 3944,
- "Cfgskilldown": 3945,
- "CfgBeltShow": 3946,
- "CfgBelt1": 3947,
- "CfgBelt2": 3948,
- "CfgBelt3": 3949,
- "CfgBelt4": 3950,
- "CfgBelt5": 3951,
- "CfgBelt6": 3952,
- "CfgBelt7": 3953,
- "CfgBelt8": 3954,
- "CfgBelt9": 3955,
- "CfgBelt10": 3956,
- "CfgBelt11": 3957,
- "CfgBelt12": 3958,
- "CfgSay0": 3959,
- "CfgSay1": 3960,
- "CfgSay2": 3961,
- "CfgSay3": 3962,
- "CfgSay4": 3963,
- "CfgSay5": 3964,
- "CfgSay6": 3965,
- "CfgRun": 3966,
- "CfgRunLock": 3967,
- "CfgStandStill": 3968,
- "CfgShowItems": 3969,
- "CfgClearScreen": 3970,
- "CfgSnapshot": 3971,
- "CfgDefault": 3972,
- "CfgAccept": 3973,
- "CfgCancel": 3974,
- "strNoKeysAssigned": 3975,
- "KeysAssigned": 3976,
- "CantAssignMB": 3977,
- "CantAssignMW": 3978,
- "CantAssignKey": 3979,
- "CfgClearKey": 3980,
- "Cfgcleartextmsg": 3981,
- "CfgTogglePortraits": 3982,
- "CfgAutoMapFade": 3983,
- "CfgAutoMapNames": 3984,
- "CfgAutoMapParty": 3985,
- "strlvlup": 3986,
- "strnewskl": 3987,
- "warpsheader": 3988,
- "nowarps": 3989,
- "waypointsheader": 3990,
- "nowaypoints": 3991,
- "max": 3992,
- "MAX": 3993,
- "colorcode": 3994,
- "space": 3995,
- "dash": 3996,
- "colon": 3997,
- "newline": 3998,
- "pipe": 3999,
- "slash": 4000,
- "percent": 4001,
- "plus": 4002,
- "to": 4003,
- "srostertitle": 4004,
- "dwell": 4005,
- "larva": 4006,
- "Barbarian": 4007,
- "Paladin": 4008,
- "Necromancer": 4009,
- "Sorceress": 4010,
- "Amazon": 4011,
- "druidstr \tDruid": 4012,
- "assassinstr": 4013,
- "Nest": 4014,
- "NoParty": 4015,
- "ItsMyParty": 4016,
- "Upgrade": 4017,
- "upgraderestrict": 4018,
- "Use": 4019,
- "NPCIdentify1": 4020,
- "NPCIdentify2": 4021,
- "strCannotDoThisToUnknown": 4022,
- "Body Looted": 4023,
- "Party1": 4024,
- "Party2": 4025,
- "Party3": 4026,
- "Party4": 4027,
- "Party5": 4028,
- "Party6": 4029,
- "Party7": 4030,
- "Party8": 4031,
- "Party9": 4032,
- "strDropGoldHowMuch": 4033,
- "strDropGoldInfo": 4034,
- "strMsgLog": 4035,
- "strBSArmor": 4036,
- "strBSWeapons": 4037,
- "strBSMagic": 4038,
- "strBSMisc": 4039,
- "strTrade": 4040,
- "strTradeAccept": 4041,
- "strTradeAgreeTo": 4042,
- "strWaitingForOtherPlayer": 4043,
- "strTradeBusy": 4044,
- "strTradeTooFull": 4045,
- "strTradeGoldHowMuch": 4046,
- "strTradeTimeout": 4047,
- "SysmsgPlayer1": 4048,
- "strBankGoldDeposit": 4049,
- "strBankGoldWithdraw": 4050,
- "GoldMax": 4051,
- "StrUI0": 4052,
- "StrUI1": 4053,
- "StrUI2": 4054,
- "StrUI3": 4055,
- "StrUI4": 4056,
- "strchrlvl": 4057,
- "strchrexp": 4058,
- "strchrnxtlvl": 4059,
- "strchrstr": 4060,
- "strchrskm": 4061,
- "strchrdex": 4062,
- "strchratr": 4063,
- "strchrdef": 4064,
- "strchrrat": 4065,
- "strchrvit": 4066,
- "strchrstm": 4067,
- "strchrlif": 4068,
- "strchreng": 4069,
- "strchrman": 4070,
- "strchrfir": 4071,
- "strchrcol": 4072,
- "strchrlit": 4073,
- "strchrpos": 4074,
- "strchrstat": 4075,
- "strchrrema": 4076,
- "WeaponDescMace": 4077,
- "WeaponDescAxe": 4078,
- "WeaponDescSword": 4079,
- "WeaponDescDagger": 4080,
- "WeaponDescThrownPotion": 4081,
- "WeaponDescJavelin": 4082,
- "WeaponDescSpear": 4083,
- "WeaponDescBow": 4084,
- "WeaponDescStaff": 4085,
- "WeaponDescPoleArm": 4086,
- "WeaponDescCrossBow": 4087,
- "WeaponAttackFastest": 4088,
- "WeaponAttackVeryFast": 4089,
- "WeaponAttackFast": 4090,
- "WeaponAttackNormal": 4091,
- "WeaponAttackSlow": 4092,
- "WeaponAttackVerySlow": 4093,
- "WeaponAttackSlowest": 4094,
- "strNecromanerOnly": 4095,
- "strPaladinOnly": 4096,
- "strSorceressOnly": 4097,
- "strMaceSpecialDamage": 4098,
- "strGoldLabel": 4099,
- "strParty1": 4100,
- "strParty2": 4101,
- "strParty3": 4102,
- "strParty4": 4103,
- "strParty5": 4104,
- "strParty6": 4105,
- "strParty7": 4106,
- "strParty8": 4107,
- "strParty9": 4108,
- "strParty10": 4109,
- "strParty11": 4110,
- "strParty12": 4111,
- "strParty13": 4112,
- "strParty14": 4113,
- "strParty15": 4114,
- "strParty16": 4115,
- "strParty17": 4116,
- "strParty18": 4117,
- "strParty19": 4118,
- "strParty22": 4119,
- "strParty24": 4120,
- "strParty25": 4121,
- "StrParty26": 4122,
- "StrParty27": 4123,
- "strGoldWithdraw": 4124,
- "strGoldDrop": 4125,
- "strGoldDeposit": 4126,
- "strGoldTrade": 4127,
- "strGoldInStash": 4128,
- "strGoldTradepup": 4129,
- "strUiMenu1": 4130,
- "strUiBank": 4132,
- "strUnknownTomb": 4133,
- "strTradeOtherBox": 4134,
- "strTradeBox": 4135,
- "strFree": 4136,
- "act1": 4137,
- "act2": 4138,
- "act3": 4139,
- "act4": 4140,
- "level": 4141,
- "lowercasecancel": 4142,
- "close": 4143,
- "strClose": 4144,
- "Lightning Spell": 4145,
- "Fire Spell": 4146,
- "Cold Spell": 4147,
- "Yourparty": 4148,
- "Inparty": 4149,
- "Invite": 4150,
- "Accept": 4151,
- "Leave": 4152,
- "Partyclose": 4153,
- "partycharama": 4154,
- "partycharsor": 4155,
- "partycharbar": 4156,
- "partycharnec": 4157,
- "partycharpal": 4158,
- "charavghit": 4159,
- "charmonster": 4160,
- "charmontohit1": 4161,
- "charmontohit2": 4162,
- "panelexp": 4163,
- "panelstamina": 4164,
- "panelhealth": 4165,
- "panelmana": 4166,
- "panelmini": 4167,
- "panelcmini": 4168,
- "minipanelchar": 4169,
- "minipanelinv": 4170,
- "minipaneltree": 4171,
- "minipanelparty": 4172,
- "minipanelautomap": 4173,
- "minipanelmessage": 4174,
- "minipanelquest": 4175,
- "minipanelmenubtn": 4176,
- "minipanelHelp": 4177,
- "minipanelspecial": 4178,
- "RunOn": 4179,
- "RunOff": 4180,
- "automapgame": 4181,
- "automappw": 4182,
- "automapdif": 4183,
- "scrollbooktext": 4184,
- "skilldesc1": 4185,
- "skilldesc2": 4186,
- "skilldesc3": 4187,
- "skilldesc4": 4188,
- "strpanel1": 4189,
- "strpanel2": 4190,
- "strpanel3": 4191,
- "strpanel4": 4192,
- "strpanel5": 4193,
- "strpanel6": 4194,
- "strpanel7": 4195,
- "strpanel8": 4196,
- "stashfull": 4197,
- "Strhelp1": 4198,
- "StrHelp2": 4199,
- "StrHelp3": 4200,
- "StrHelp4": 4201,
- "StrHelp5": 4202,
- "StrHelp6": 4203,
- "StrHelp7": 4204,
- "StrHelp8": 4205,
- "StrHelp8a": 4206,
- "StrHelp9": 4207,
- "StrHelp10": 4208,
- "StrHelp11": 4209,
- "StrHelp12": 4210,
- "StrHelp13": 4211,
- "StrHelp14": 4212,
- "StrHelp14a": 4213,
- "StrHelp15": 4214,
- "StrHelp16": 4215,
- "StrHelp16a": 4216,
- "StrHelp17": 4217,
- "StrHelp18": 4218,
- "StrHelp19": 4219,
- "StrHelp20": 4220,
- "StrHelp21": 4221,
- "StrHelp22": 4222,
- "strSklTree": 4223,
- "StrSklTreea": 4224,
- "StrSklTreeb": 4225,
- "StrSklTreec": 4226,
- "StrSklTree1": 4227,
- "StrSklTree2": 4228,
- "StrSklTree3": 4229,
- "StrSklTree4": 4230,
- "StrSklTree5": 4231,
- "StrSklTree6": 4232,
- "StrSklTree7": 4233,
- "StrSklTree8": 4234,
- "StrSklTree9": 4235,
- "StrSklTree10": 4236,
- "StrSklTree11": 4237,
- "StrSklTree12": 4238,
- "StrSklTree13": 4239,
- "StrSklTree14": 4240,
- "StrSklTree15": 4241,
- "StrSklTree16": 4242,
- "StrSklTree17": 4243,
- "StrSklTree18": 4244,
- "StrSklTree19": 4245,
- "StrSklTree20": 4246,
- "StrSklTree21": 4247,
- "StrSklTree22": 4248,
- "StrSklTree23": 4249,
- "StrSklTree24": 4250,
- "StrSklTree25": 4251,
- "StrSkill0": 4252,
- "StrSkill1": 4253,
- "StrSkill2": 4254,
- "StrSkill3": 4255,
- "StrSkill4": 4256,
- "StrSkill5": 4257,
- "StrSkill6": 4258,
- "StrSkill7": 4259,
- "StrSkill8": 4260,
- "StrSkill9": 4261,
- "StrSkill10": 4262,
- "StrSkill11": 4263,
- "StrSkill12": 4264,
- "StrSkill13": 4265,
- "StrSkill14": 4266,
- "StrSkill15": 4267,
- "StrSkill16": 4268,
- "StrSkill17": 4269,
- "StrSkill18": 4270,
- "StrSkill19": 4271,
- "StrSkill20": 4272,
- "StrSkill21": 4273,
- "StrSkill22": 4274,
- "StrSkill23": 4275,
- "StrSkill24": 4276,
- "StrSkill25": 4277,
- "StrSkill26": 4278,
- "StrSkill27": 4279,
- "StrSkill28": 4280,
- "StrSkill29": 4281,
- "StrSkill30": 4282,
- "StrSkill31": 4283,
- "StrSkill32": 4284,
- "StrSkill33": 4285,
- "StrSkill34": 4286,
- "StrSkill35": 4287,
- "StrSkill36": 4288,
- "StrSkill37": 4289,
- "StrSkill38": 4290,
- "StrSkill39": 4291,
- "StrSkill40": 4292,
- "StrSkill41": 4293,
- "StrSkill42": 4294,
- "StrSkill43": 4297,
- "StrSkill44": 4298,
- "StrSkill45": 4299,
- "StrSkill46": 4300,
- "StrSkill47": 4301,
- "StrSkill48": 4302,
- "StrSkill49": 4303,
- "StrSkill50": 4304,
- "StrSkill51": 4305,
- "StrSkill52": 4306,
- "StrSkill53": 4307,
- "StrSkill54": 4308,
- "StrSkill55": 4309,
- "StrSkill56": 4310,
- "StrSkill57": 4311,
- "StrSkill58": 4312,
- "StrSkill59": 4313,
- "StrSkill60": 4314,
- "StrSkill61": 4315,
- "StrSkill62": 4316,
- "StrSkill63": 4317,
- "StrSkill64": 4318,
- "StrSkill65": 4319,
- "StrSkill66": 4320,
- "StrSkill67": 4321,
- "StrSkill68": 4322,
- "StrSkill69": 4323,
- "StrSkill70": 4324,
- "StrSkill71": 4325,
- "StrSkill72": 4326,
- "StrSkill73": 4327,
- "StrSkill74": 4328,
- "StrSkill75": 4329,
- "StrSkill76": 4330,
- "StrSkill77": 4331,
- "StrSkill78": 4332,
- "StrSkill79": 4333,
- "StrSkill80": 4334,
- "StrSkill81": 4335,
- "StrSkill82": 4336,
- "StrSkill83": 4337,
- "StrSkill84": 4338,
- "StrSkill85": 4339,
- "StrSkill86": 4340,
- "StrSkill87": 4341,
- "StrSkill88": 4342,
- "StrSkill89": 4343,
- "StrSkill90": 4344,
- "StrSkill91": 4345,
- "StrSkill92": 4346,
- "StrSkill94": 4347,
- "StrSkill95": 4348,
- "StrSkill96": 4349,
- "StrSkill97": 4350,
- "StrSkill98": 4351,
- "StrSkill99": 4352,
- "StrSkill100": 4353,
- "StrSkill101": 4354,
- "StrSkill102": 4355,
- "StrSkill103": 4356,
- "StrSkill104": 4357,
- "StrSkill105": 4358,
- "StrSkill106": 4359,
- "StrSkill107": 4360,
- "StrSkill108": 4361,
- "StrSkill109": 4362,
- "StrSkill110": 4363,
- "StrSkill111": 4364,
- "StrSkill112": 4365,
- "StrSkill113": 4366,
- "StrSkill114": 4367,
- "StrSkill115": 4368,
- "StrSkill116": 4369,
- "StrSkill117": 4370,
- "StrSkill118": 4371,
- "StrSkill119": 4372,
- "skillname0": 4373,
- "skillsd0": 4374,
- "skillld0": 4375,
- "skillan0": 4376,
- "skillname1": 4377,
- "skillsd1": 4378,
- "skillld1": 4379,
- "skillan1": 4380,
- "skillname2": 4381,
- "skillsd2": 4382,
- "skillld2": 4383,
- "skillan2": 4384,
- "skillname3": 4385,
- "skillsd3": 4386,
- "skillld3": 4387,
- "skillan3": 4388,
- "skillname4": 4389,
- "skillsd4": 4390,
- "skillld4": 4391,
- "skillan4": 4392,
- "skillname5": 4393,
- "skillsd5": 4394,
- "skillld5": 4395,
- "skillan5": 4396,
- "skillname6": 4397,
- "skillsd6": 4398,
- "skillld6": 4399,
- "skillan6": 4400,
- "skillname7": 4401,
- "skillsd7": 4402,
- "skillld7": 4403,
- "skillan7": 4404,
- "skillname8": 4405,
- "skillsd8": 4406,
- "skillld8": 4407,
- "skillan8": 4408,
- "skillname9": 4409,
- "skillsd9": 4410,
- "skillld9": 4411,
- "skillan9": 4412,
- "skillname10": 4413,
- "skillsd10": 4414,
- "skillld10": 4415,
- "skillan10": 4416,
- "skillname11": 4417,
- "skillsd11": 4418,
- "skillld11": 4419,
- "skillan11": 4420,
- "skillname12": 4421,
- "skillsd12": 4422,
- "skillld12": 4423,
- "skillan12": 4424,
- "skillname13": 4425,
- "skillsd13": 4426,
- "skillld13": 4427,
- "skillan13": 4428,
- "skillname14": 4429,
- "skillsd14": 4430,
- "skillld14": 4431,
- "skillan14": 4432,
- "skillname15": 4433,
- "skillsd15": 4434,
- "skillld15": 4435,
- "skillan15": 4436,
- "skillname16": 4437,
- "skillsd16": 4438,
- "skillld16": 4439,
- "skillan16": 4440,
- "skillname17": 4441,
- "skillsd17": 4442,
- "skillld17": 4443,
- "skillan17": 4444,
- "skillname18": 4445,
- "skillsd18": 4446,
- "skillld18": 4447,
- "skillan18": 4448,
- "skillname19": 4449,
- "skillsd19": 4450,
- "skillld19": 4451,
- "skillan19": 4452,
- "skillname20": 4453,
- "skillsd20": 4454,
- "skillld20": 4455,
- "skillan20": 4456,
- "skillname21": 4457,
- "skillsd21": 4458,
- "skillld21": 4459,
- "skillan21": 4460,
- "skillname22": 4461,
- "skillsd22": 4462,
- "skillld22": 4463,
- "skillan22": 4464,
- "skillname23": 4465,
- "skillsd23": 4466,
- "skillld23": 4467,
- "skillan23": 4468,
- "skillname24": 4469,
- "skillsd24": 4470,
- "skillld24": 4471,
- "skillan24": 4472,
- "skillname25": 4473,
- "skillsd25": 4474,
- "skillld25": 4475,
- "skillan25": 4476,
- "skillname26": 4477,
- "skillsd26": 4478,
- "skillld26": 4479,
- "skillan26": 4480,
- "skillname27": 4481,
- "skillsd27": 4482,
- "skillld27": 4483,
- "skillan27": 4484,
- "skillname28": 4485,
- "skillsd28": 4486,
- "skillld28": 4487,
- "skillan28": 4488,
- "skillname29": 4489,
- "skillsd29": 4490,
- "skillld29": 4491,
- "skillan29": 4492,
- "skillname30": 4493,
- "skillsd30": 4494,
- "skillld30": 4495,
- "skillan30": 4496,
- "skillname31": 4497,
- "skillsd31": 4498,
- "skillld31": 4499,
- "skillan31": 4500,
- "skillname32": 4501,
- "skillsd32": 4502,
- "skillld32": 4503,
- "skillan32": 4504,
- "skillname33": 4505,
- "skillsd33": 4506,
- "skillld33": 4507,
- "skillan33": 4508,
- "skillname34": 4509,
- "skillsd34": 4510,
- "skillld34": 4511,
- "skillan34": 4512,
- "skillname35": 4513,
- "skillsd35": 4514,
- "skillld35": 4515,
- "skillan35": 4516,
- "skillname36": 4517,
- "skillsd36": 4518,
- "skillld36": 4519,
- "skillan36": 4520,
- "skillname37": 4521,
- "skillsd37": 4522,
- "skillld37": 4523,
- "skillan37": 4524,
- "skillname38": 4525,
- "skillsd38": 4526,
- "skillld38": 4527,
- "skillan38": 4528,
- "skillname39": 4529,
- "skillsd39": 4530,
- "skillld39": 4531,
- "skillan39": 4532,
- "skillname40": 4533,
- "skillsd40": 4534,
- "skillld40": 4535,
- "skillan40": 4536,
- "skillname41": 4537,
- "skillsd41": 4538,
- "skillld41": 4539,
- "skillan41": 4540,
- "skillname42": 4541,
- "skillsd42": 4542,
- "skillld42": 4543,
- "skillan42": 4544,
- "skillname43": 4545,
- "skillsd43": 4546,
- "skillld43": 4547,
- "skillan43": 4548,
- "skillname44": 4549,
- "skillsd44": 4550,
- "skillld44": 4551,
- "skillan44": 4552,
- "skillname45": 4553,
- "skillsd45": 4554,
- "skillld45": 4555,
- "skillan45": 4556,
- "skillname46": 4557,
- "skillsd46": 4558,
- "skillld46": 4559,
- "skillan46": 4560,
- "skillname47": 4561,
- "skillsd47": 4562,
- "skillld47": 4563,
- "skillan47": 4564,
- "skillname48": 4565,
- "skillsd48": 4566,
- "skillld48": 4567,
- "skillan48": 4568,
- "skillname49": 4569,
- "skillsd49": 4570,
- "skillld49": 4571,
- "skillan49": 4572,
- "skillname50": 4573,
- "skillsd50": 4574,
- "skillld50": 4575,
- "skillan50": 4576,
- "skillname51": 4577,
- "skillsd51": 4578,
- "skillld51": 4579,
- "skillan51": 4580,
- "skillname52": 4581,
- "skillsd52": 4582,
- "skillld52": 4583,
- "skillan52": 4584,
- "skillname53": 4585,
- "skillsd53": 4586,
- "skillld53": 4587,
- "skillan53": 4588,
- "skillname54": 4589,
- "skillsd54": 4590,
- "skillld54": 4591,
- "skillan54": 4592,
- "skillname55": 4593,
- "skillsd55": 4594,
- "skillld55": 4595,
- "skillan55": 4596,
- "skillname56": 4597,
- "skillsd56": 4598,
- "skillld56": 4599,
- "skillan56": 4600,
- "skillname57": 4601,
- "skillsd57": 4602,
- "skillld57": 4603,
- "skillan57": 4604,
- "skillname58": 4605,
- "skillsd58": 4606,
- "skillld58": 4607,
- "skillan58": 4608,
- "skillname59": 4609,
- "skillsd59": 4610,
- "skillld59": 4611,
- "skillan59": 4612,
- "skillname60": 4613,
- "skillsd60": 4614,
- "skillld60": 4615,
- "skillan60": 4616,
- "skillsname61": 4617,
- "skillsd61": 4618,
- "skillld61": 4619,
- "skillan61": 4620,
- "skillname62": 4621,
- "skillsd62": 4622,
- "skillld62": 4623,
- "skillan62": 4624,
- "skillname63": 4625,
- "skillsd63": 4626,
- "skillld63": 4627,
- "skillan63": 4628,
- "skillname64": 4629,
- "skillsd64": 4630,
- "skillld64": 4631,
- "skillan64": 4632,
- "skillname65": 4633,
- "skillsd65": 4634,
- "skillld65": 4635,
- "skillan65": 4636,
- "skillname66": 4637,
- "skillsd66": 4638,
- "skillld66": 4639,
- "skillan66": 4640,
- "skillname67": 4641,
- "skillsd67": 4642,
- "skillld67": 4643,
- "skillan67": 4644,
- "skillname68": 4645,
- "skillsd68": 4646,
- "skillld68": 4647,
- "skillan68": 4648,
- "skillname69": 4649,
- "skillsd69": 4650,
- "skillld69": 4651,
- "skillan69": 4652,
- "skillname70": 4653,
- "skillsd70": 4654,
- "skillld70": 4655,
- "skillan70": 4656,
- "skillname71": 4657,
- "skillsd71": 4658,
- "skillld71": 4659,
- "skillan71": 4660,
- "skillname72": 4661,
- "skillsd72": 4662,
- "skillld72": 4663,
- "skillan72": 4664,
- "skillname73": 4665,
- "skillsd73": 4666,
- "skillld73": 4667,
- "skillan73": 4668,
- "skillname74": 4669,
- "skillsd74": 4670,
- "skillld74": 4671,
- "skillan74": 4672,
- "skillname75": 4673,
- "skillsd75": 4674,
- "skillld75": 4675,
- "skillan75": 4676,
- "skillname76": 4677,
- "skillsd76": 4678,
- "skillld76": 4679,
- "skillan76": 4680,
- "skillname77": 4681,
- "skillsd77": 4682,
- "skillld77": 4683,
- "skillan77": 4684,
- "skillname78": 4685,
- "skillsd78": 4686,
- "skillld78": 4687,
- "skillan78": 4688,
- "skillname79": 4689,
- "skillsd79": 4690,
- "skillld79": 4691,
- "skillan79": 4692,
- "skillname80": 4693,
- "skillsd80": 4694,
- "skillld80": 4695,
- "skillan80": 4696,
- "skillname81": 4697,
- "skillsd81": 4698,
- "skillld81": 4699,
- "skillan81": 4700,
- "skillname82": 4701,
- "skillsd82": 4702,
- "skillld82": 4703,
- "skillan82": 4704,
- "skillname83": 4705,
- "skillsd83": 4706,
- "skillld83": 4707,
- "skillan83": 4708,
- "skillname84": 4709,
- "skillsd84": 4710,
- "skillld84": 4711,
- "skillan84": 4712,
- "skillname85": 4713,
- "skillsd85": 4714,
- "skillld85": 4715,
- "skillan85": 4716,
- "skillname86": 4717,
- "skillsd86": 4718,
- "skillld86": 4719,
- "skillan86": 4720,
- "skillname87": 4721,
- "skillsd87": 4722,
- "skillld87": 4723,
- "skillan87": 4724,
- "skillname88": 4725,
- "skillsd88": 4726,
- "skillld88": 4727,
- "skillan88": 4728,
- "skillname89": 4729,
- "skillsd89": 4730,
- "skillld89": 4731,
- "skillan89": 4732,
- "skillname90": 4733,
- "skillsd90": 4734,
- "skillld90": 4735,
- "skillan90": 4736,
- "skillname91": 4737,
- "skillsd91": 4738,
- "skillld91": 4739,
- "skillan91": 4740,
- "skillname92": 4741,
- "skillsd92": 4742,
- "skillld92": 4743,
- "skillan92": 4744,
- "skillname93": 4745,
- "skillsd93": 4746,
- "skillld93": 4747,
- "skillan93": 4748,
- "skillname94": 4749,
- "skillsd94": 4750,
- "skillld94": 4751,
- "skillan94": 4752,
- "skillname95": 4753,
- "skillsd95": 4754,
- "skillld95": 4755,
- "skillan95": 4756,
- "skillname96": 4757,
- "skillsd96": 4758,
- "skillld96": 4759,
- "skillan96": 4760,
- "skillname97": 4761,
- "skillsd97": 4762,
- "skillld97": 4763,
- "skillan97": 4764,
- "skillname98": 4765,
- "skillsd98": 4766,
- "skillld98": 4767,
- "skillan98": 4768,
- "skillname99": 4769,
- "skillsd99": 4770,
- "skillld99": 4771,
- "skillan99": 4772,
- "skillname100": 4773,
- "skillsd100": 4774,
- "skillld100": 4775,
- "skillan100": 4776,
- "skillname101": 4777,
- "skillsd101": 4778,
- "skillld101": 4779,
- "skillan101": 4780,
- "skillname102": 4781,
- "skillsd102": 4782,
- "skillld102": 4783,
- "skillan102": 4784,
- "skillname103": 4785,
- "skillsd103": 4786,
- "skillld103": 4787,
- "skillan103": 4788,
- "skillname104": 4789,
- "skillsd104": 4790,
- "skillld104": 4791,
- "skillan104": 4792,
- "skillname105": 4793,
- "skillsd105": 4794,
- "skillld105": 4795,
- "skillan105": 4796,
- "skillname106": 4797,
- "skillsd106": 4798,
- "skillld106": 4799,
- "skillan106": 4800,
- "skillname107": 4801,
- "skillsd107": 4802,
- "skillld107": 4803,
- "skillan107": 4804,
- "skillname108": 4805,
- "skillsd108": 4806,
- "skillld108": 4807,
- "skillan108": 4808,
- "skillname109": 4809,
- "skillsd109": 4810,
- "skillld109": 4811,
- "skillan109": 4812,
- "skillname110": 4813,
- "skillsd110": 4814,
- "skillld110": 4815,
- "skillan110": 4816,
- "skillname111": 4817,
- "skillsd111": 4818,
- "skillld111": 4819,
- "skillan111": 4820,
- "skillname112": 4821,
- "skillsd112": 4822,
- "skillld112": 4823,
- "skillan112": 4824,
- "skillname113": 4825,
- "skillsd113": 4826,
- "skillld113": 4827,
- "skillan113": 4828,
- "skillname114": 4829,
- "skillsd114": 4830,
- "skillld114": 4831,
- "skillan114": 4832,
- "skillname115": 4833,
- "skillsd115": 4834,
- "skillld115": 4835,
- "skillan115": 4836,
- "skillname116": 4837,
- "skillsd116": 4838,
- "skillld116": 4839,
- "skillan116": 4840,
- "skillname117": 4841,
- "skillsd117": 4842,
- "skillld117": 4843,
- "skillan117": 4844,
- "skillname118": 4845,
- "skillsd118": 4846,
- "skillld118": 4847,
- "skillan118": 4848,
- "skillname119": 4849,
- "skillsd119": 4850,
- "skillld119": 4851,
- "skillan119": 4852,
- "skillname120": 4853,
- "skillsd120": 4854,
- "skillld120": 4855,
- "skillan120": 4856,
- "skillname121": 4857,
- "skillsd121": 4858,
- "skillld121": 4859,
- "skillan121": 4860,
- "skillname122": 4861,
- "skillsd122": 4862,
- "skillld122": 4863,
- "skillan122": 4864,
- "skillname123": 4865,
- "skillsd123": 4866,
- "skillld123": 4867,
- "skillan123": 4868,
- "skillname124": 4869,
- "skillsd124": 4870,
- "skillld124": 4871,
- "skillan124": 4872,
- "skillname125": 4873,
- "skillsd125": 4874,
- "skillld125": 4875,
- "skillan125": 4876,
- "skillname126": 4877,
- "skillsd126": 4878,
- "skillld126": 4879,
- "skillan126": 4880,
- "skillname127": 4881,
- "skillsd127": 4882,
- "skillld127": 4883,
- "skillan127": 4884,
- "skillname128": 4885,
- "skillsd128": 4886,
- "skillld128": 4887,
- "skillan128": 4888,
- "skillname129": 4889,
- "skillsd129": 4890,
- "skillld129": 4891,
- "skillan129": 4892,
- "skillname130": 4893,
- "skillsd130": 4894,
- "skillld130": 4895,
- "skillan130": 4896,
- "skillname131": 4897,
- "skillsd131": 4898,
- "skillld131": 4899,
- "skillan131": 4900,
- "skillname132": 4901,
- "skillsd132": 4902,
- "skillld132": 4903,
- "skillan132": 4904,
- "skillname133": 4905,
- "skillsd133": 4906,
- "skillld133": 4907,
- "skillan133": 4908,
- "skillname134": 4909,
- "skillsd134": 4910,
- "skillld134": 4911,
- "skillan134": 4912,
- "skillname135": 4913,
- "skillsd135": 4914,
- "skillld135": 4915,
- "skillan135": 4916,
- "skillname136": 4917,
- "skillsd136": 4918,
- "skillld136": 4919,
- "skillan136": 4920,
- "skillname137": 4921,
- "skillsd137": 4922,
- "skillld137": 4923,
- "skillan137": 4924,
- "skillname138": 4925,
- "skillsd138": 4926,
- "skillld138": 4927,
- "skillan138": 4928,
- "skillname139": 4929,
- "skillsd139": 4930,
- "skillld139": 4931,
- "skillan139": 4932,
- "skillname140": 4933,
- "skillsd140": 4934,
- "skillld140": 4935,
- "skillan140": 4936,
- "skillname141": 4937,
- "skillsd141": 4938,
- "skillld141": 4939,
- "skillan141": 4940,
- "skillname142": 4941,
- "skillsd142": 4942,
- "skillld142": 4943,
- "skillan142": 4944,
- "skillname143": 4945,
- "skillsd143": 4946,
- "skillld143": 4947,
- "skillan143": 4948,
- "skillname144": 4949,
- "skillsd144": 4950,
- "skillld144": 4951,
- "skillan144": 4952,
- "skillname145": 4953,
- "skillsd145": 4954,
- "skillld145": 4955,
- "skillan145": 4956,
- "skillname146": 4957,
- "skillsd146": 4958,
- "skillld146": 4959,
- "skillan146": 4960,
- "skillname147": 4961,
- "skillsd147": 4962,
- "skillld147": 4963,
- "skillan147": 4964,
- "skillname148": 4965,
- "skillsd148": 4966,
- "skillld148": 4967,
- "skillan148": 4968,
- "skillname149": 4969,
- "skillsd149": 4970,
- "skillld149": 4971,
- "skillan149": 4972,
- "skillname150": 4973,
- "skillsd150": 4974,
- "skillld150": 4975,
- "skillan150": 4976,
- "skillname151": 4977,
- "skillsd151": 4978,
- "skillld151": 4979,
- "skillan151": 4980,
- "skillname152": 4981,
- "skillsd152": 4982,
- "skillld152": 4983,
- "skillan152": 4984,
- "skillname153": 4985,
- "skillsd153": 4986,
- "skillld153": 4987,
- "skillan153": 4988,
- "skillname154": 4989,
- "skillsd154": 4990,
- "skillld154": 4991,
- "skillan154": 4992,
- "skillname155": 4993,
- "skillsd155": 4994,
- "skillld155": 4995,
- "skillan155": 4996,
- "skillname217": 4997,
- "skillsd217": 4998,
- "skillld217": 4999,
- "skillan217": 5000,
- "skillname218": 5001,
- "skillsd218": 5002,
- "skillld218": 5003,
- "skillan218": 5004,
- "skillname219": 5005,
- "skillsd219": 5006,
- "skillld219": 5007,
- "skillan219": 5008,
- "skillname220": 5009,
- "skillsd220": 5010,
- "skillld220": 5011,
- "skillan220": 5012,
- "strMephistoDoorLocked": 5013,
- "strTitleFeminine": 5014,
- "strTitleMasculine": 5015,
- "strChatHardcore": 5016,
- "strChatLevel": 5017,
- "Tristram": 5018,
- "Catacombs Level 4": 5019,
- "Catacombs Level 3": 5020,
- "Catacombs Level 2": 5021,
- "Catacombs Level 1": 5022,
- "Cathedral": 5023,
- "Inner Cloister": 5024,
- "Jail Level 3": 5025,
- "Jail Level 2": 5026,
- "Jail Level 1": 5027,
- "Barracks": 5028,
- "Outer Cloister": 5029,
- "Monastery Gate": 5030,
- "Tower Cellar Level 5": 5031,
- "Tower Cellar Level 4": 5032,
- "Tower Cellar Level 3": 5033,
- "Tower Cellar Level 2": 5034,
- "Tower Cellar Level 1": 5035,
- "Forgotten Tower": 5036,
- "Mausoleum": 5037,
- "Crypt": 5038,
- "Burial Grounds": 5039,
- "Pit Level 2": 5040,
- "Hole Level 2": 5041,
- "Underground Passage Level 2": 5042,
- "Cave Level 2": 5043,
- "Pit Level 1": 5044,
- "Hole Level 1": 5045,
- "Underground Passage Level 1": 5046,
- "Cave Level 1": 5047,
- "Den of Evil": 5048,
- "Tamoe Highland": 5049,
- "Black Marsh": 5050,
- "Dark Wood": 5051,
- "Stony Field": 5052,
- "Cold Plains": 5053,
- "Blood Moor": 5054,
- "Rogue Encampment": 5055,
- "To Tristram": 5056,
- "To The Catacombs Level 4": 5057,
- "To The Catacombs Level 3": 5058,
- "To The Catacombs Level 2": 5059,
- "To The Catacombs Level 1": 5060,
- "To The Cathedral": 5061,
- "To The Inner Cloister": 5062,
- "To The Jail Level 3": 5063,
- "To The Jail Level 2": 5064,
- "To The Jail Level 1": 5065,
- "To The Barracks": 5066,
- "To The Outer Cloister": 5067,
- "To The Monastery Gate": 5068,
- "To The Tower Cellar Level 5": 5069,
- "To The Tower Cellar Level 4": 5070,
- "To The Tower Cellar Level 3": 5071,
- "To The Tower Cellar Level 2": 5072,
- "To The Tower Cellar Level 1": 5073,
- "To The Forgotten Tower": 5074,
- "To The Mausoleum": 5075,
- "To The Crypt": 5076,
- "To The Burial Grounds": 5077,
- "To The Pit Level 2": 5078,
- "To The Hole Level 2": 5079,
- "To Underground Passage Level 2": 5080,
- "To The Cave Level 2": 5081,
- "To The Pit Level 1": 5082,
- "To The Hole Level 1": 5083,
- "To Underground Passage Level 1": 5084,
- "To The Cave Level 1": 5085,
- "To The Den of Evil": 5086,
- "To The Tamoe Highland": 5087,
- "To The Black Marsh": 5088,
- "To The Dark Wood": 5089,
- "To The Stony Field": 5090,
- "To The Cold Plains": 5091,
- "To The Blood Moor": 5092,
- "To The Rogue Encampment": 5093,
- "Deathmessage": 5094,
- "Deathmessnight": 5095,
- "Harddeathmessage": 5096,
- "LordofTerrordied": 5097,
- "Killdiablo1": 5098,
- "KillDiablo2": 5099,
- "KillDiablo3": 5100,
- "x": 22741,
- "X": 22746,
- "Gem Activated": 5334,
- "Gem Deactivated": 5335,
- "Perfect Gem Activated": 5336,
- "dummy": 5382,
- "Dummy": 5383,
- "not used": 5384,
- "unused": 5385,
- "Not used": 5386,
- "convertsto": 5387,
- "strNotInBeta": 5388,
- "strLevelLoadFailed": 5389,
- "Endthispuppy": 5390,
- "A4Q2ExpansionSuccessTyrael": 20000,
- "A4Q2ExpansionSuccessCain": 20001,
- "AncientsAct5IntroGossip1": 20002,
- "CainAct5IntroGossip1": 20003,
- "CainAct5Gossip1": 20004,
- "CainAct5Gossip2": 20005,
- "CainAct5Gossip3": 20006,
- "CainAct5Gossip4": 20007,
- "CainAct5Gossip5": 20008,
- "CainAct5Gossip6": 20009,
- "CainAct5Gossip7": 20010,
- "CainAct5Gossip8": 20011,
- "CainAct5Gossip9": 20012,
- "CainAct5Gossip10": 20013,
- "AnyaAct5IntroGossip1": 20014,
- "AnyaGossip1": 20015,
- "AnyaGossip2": 20016,
- "AnyaGossip3": 20017,
- "AnyaGossip4": 20018,
- "AnyaGossip5": 20019,
- "AnyaGossip6": 20020,
- "AnyaGossip7": 20021,
- "AnyaGossip8": 20022,
- "AnyaGossip9": 20023,
- "AnyaGossip10": 20024,
- "LarzukAct5IntroGossip1": 20025,
- "LarzukAct5IntroAmaGossip1": 20026,
- "LarzukGossip1": 20027,
- "LarzukGossip2": 20028,
- "LarzukGossip3": 20029,
- "LarzukGossip4": 20030,
- "LarzukGossip5": 20031,
- "LarzukGossip6": 20032,
- "LarzukGossip7": 20033,
- "LarzukGossip8": 20034,
- "LarzukGossip9": 20035,
- "LarzukGossip10": 20036,
- "MalahAct5IntroGossip1": 20037,
- "MalahAct5IntroSorGossip1": 20038,
- "MalahAct5IntroBarGossip1": 20039,
- "MalahGossip1": 20040,
- "MalahGossip2": 20041,
- "MalahGossip3": 20042,
- "MalahGossip4": 20043,
- "MalahGossip5": 20044,
- "MalahGossip6": 20045,
- "MalahGossip7": 20046,
- "MalahGossip8": 20047,
- "MalahGossip9": 20048,
- "MalahGossip10": 20049,
- "MalahGossip11": 20050,
- "MalahGossip12": 20051,
- "MalahGossip13": 20052,
- "NihlathakAct5IntroGossip1": 20053,
- "NihlathakAct5IntroAssGossip1": 20054,
- "NihlathakAct5IntroNecGossip1": 20055,
- "NihlathakGossip1": 20056,
- "NihlathakGossip2": 20057,
- "NihlathakGossip3": 20058,
- "NihlathakGossip4": 20059,
- "NihlathakGossip5": 20060,
- "NihlathakGossip6": 20061,
- "NihlathakGossip7": 20062,
- "NihlathakGossip8": 20063,
- "NihlathakGossip9": 20064,
- "QualKehkAct5IntroGossip1": 20065,
- "QualKehkAct5IntroPalGossip1": 20066,
- "QualKehkAct5IntroDruGossip1": 20067,
- "QualKehkGossip1": 20068,
- "QualKehkGossip2": 20069,
- "QualKehkGossip3": 20070,
- "QualKehkGossip4": 20071,
- "QualKehkGossip5": 20072,
- "QualKehkGossip6": 20073,
- "QualKehkGossip7": 20074,
- "QualKehkGossip8": 20075,
- "QualKehkGossip9": 20076,
- "A5Q1InitLarzuk": 20077,
- "A5Q1AfterInitLarzuk": 20078,
- "A5Q1AfterInitCain": 20079,
- "A5Q1AfterInitAnya": 20080,
- "A5Q1AfterInitMalah": 20081,
- "A5Q1AfterInitNihlathak": 20082,
- "A5Q1AfterInitQualKehk": 20083,
- "A5Q1EarlyReturnLarzuk": 20084,
- "A5Q1EarlyReturnCain": 20085,
- "A5Q1EarlyReturnAnya": 20086,
- "A5Q1EarlyReturnMalah": 20087,
- "A5Q1EarlyReturnNihlathak": 20088,
- "A5Q1EarlyReturnQualKehk": 20089,
- "A5Q1SuccessfulLarzuk": 20090,
- "A5Q1SuccessfulCain": 20091,
- "A5Q1SuccessfulAnya": 20092,
- "A5Q1SuccessfulMalah": 20093,
- "A5Q1SuccessfulNihlathak": 20094,
- "A5Q1SuccessfulQualKehk": 20095,
- "A5Q2InitQualKehk": 20096,
- "A5Q2AfterInitQualKehk": 20097,
- "A5Q2AfterInitCain": 20098,
- "A5Q2AfterInitAnya": 20099,
- "A5Q2AfterInitLarzuk": 20100,
- "A5Q2AfterInitMalah": 20101,
- "A5Q2AfterInitNihlathak": 20102,
- "A5Q2EarlyReturnQualKehk": 20103,
- "A5Q2EarlyReturnQualKehkMan": 20104,
- "A5Q2EarlyReturnCain": 20105,
- "A5Q2EarlyReturnAnya": 20106,
- "A5Q2EarlyReturnLarzuk": 20107,
- "A5Q2EarlyReturnMalah": 20108,
- "A5Q2EarlyReturnNihlathak": 20109,
- "A5Q2SuccessfulQualKehk": 20110,
- "A5Q2SuccessfulCain": 20111,
- "A5Q2SuccessfulAnya": 20112,
- "A5Q2SuccessfulLarzuk": 20113,
- "A5Q2SuccessfulMalah": 20114,
- "A5Q2SuccessfulNihlathak": 20115,
- "A5Q3InitMalah": 20116,
- "A5Q3AfterInitMalah": 20117,
- "A5Q3AfterInitCain": 20118,
- "A5Q3AfterInitLarzuk": 20119,
- "A5Q3AfterInitNihlathak": 20120,
- "A5Q3AfterInitQualKehk": 20121,
- "A5Q3EarlyReturnMalah": 20122,
- "A5Q3EarlyReturnCain": 20123,
- "A5Q3EarlyReturnLarzuk": 20124,
- "A5Q3EarlyReturnNihlathak": 20125,
- "A5Q3EarlyReturnQualKehk": 20126,
- "A5Q3FoundAnyaMalah": 20127,
- "A5Q3FoundAnyaCain": 20128,
- "A5Q3FoundAnyaLarzuk": 20129,
- "A5Q3FoundAnyaQualKehk": 20130,
- "A5Q3FoundAnyaAnya": 20131,
- "A5Q3SuccessfulMalah": 20132,
- "A5Q3SuccessfulCain": 20133,
- "A5Q3SuccessfulLarzuk": 20134,
- "A5Q3SuccessfulQualKehk": 20135,
- "A5Q3SuccessfulAnya": 20136,
- "A5Q4InitAnya": 20137,
- "A5Q4AfterInitAnya": 20138,
- "A5Q4AfterInitCain": 20139,
- "A5Q4AfterInitMalah": 20140,
- "A5Q4AfterInitLarzuk": 20141,
- "A5Q4AfterInitQualKehk": 20142,
- "A5Q4EarlyReturnAnya": 20143,
- "A5Q4EarlyReturnCain": 20144,
- "A5Q4EarlyReturnLarzuk": 20145,
- "A5Q4EarlyReturnMalah": 20146,
- "A5Q4EarlyReturnQualKehk": 20147,
- "A5Q4SuccessfulAnya": 20148,
- "A5Q4SuccessfulCain": 20149,
- "A5Q4SuccessfulLarzuk": 20150,
- "A5Q4SuccessfulMalah": 20151,
- "A5Q4SuccessfulQualKehk": 20152,
- "A5Q5InitQualKehk": 20153,
- "A5Q5AfterInitQualKehk": 20154,
- "A5Q5AfterInitCain": 20155,
- "A5Q5AfterInitAnya": 20156,
- "A5Q5AfterInitLarzuk": 20157,
- "A5Q5AfterInitMalah": 20158,
- "A5Q5EarlyReturnQualKehk": 20159,
- "A5Q5EarlyReturnCain": 20160,
- "A5Q5EarlyReturnAnya": 20161,
- "A5Q5EarlyReturnLarzuk": 20162,
- "A5Q5EarlyReturnMalah": 20163,
- "A5Q5SuccessfulQualKehk": 20164,
- "A5Q5SuccessfulCain": 20165,
- "A5Q5SuccessfulAnya": 20166,
- "A5Q5SuccessfulLarzuk": 20167,
- "A5Q5SuccessfulMalah": 20168,
- "A5Q6InitAncients": 20169,
- "A5Q6EarlyReturnCain": 20170,
- "A5Q6EarlyReturnLarzuk": 20171,
- "A5Q6EarlyReturnMalah": 20172,
- "A5Q6EarlyReturnAnya": 20173,
- "A5Q6EarlyReturnQualKehk": 20174,
- "A5Q6SuccessfulTyrael": 20175,
- "A5Q6SuccessfulAnya": 20176,
- "A5Q6SuccessfulCain": 20177,
- "A5Q6SuccessfulLarzuk": 20178,
- "A5Q6SuccessfulMalah": 20179,
- "A5Q6SuccessfulQualKehk": 20180,
- "ktr": 20181,
- "wrb": 20182,
- "ces": 20183,
- "clw": 20184,
- "btl": 20185,
- "skr": 20186,
- "9ar": 20187,
- "9wb": 20188,
- "9xf": 20189,
- "9cs": 20190,
- "9lw": 20191,
- "9tw": 20192,
- "9qr": 20193,
- "7ar": 20194,
- "7wb": 20195,
- "7xf": 20196,
- "7cs": 20197,
- "7lw": 20198,
- "7tw": 20199,
- "7qr": 20200,
- "7ha": 20201,
- "7ax": 20202,
- "72a": 20203,
- "7mp": 20204,
- "7wa": 20205,
- "7la": 20206,
- "7ba": 20207,
- "7bt": 20208,
- "7ga": 20209,
- "7gi": 20210,
- "7wn": 20211,
- "7yw": 20212,
- "7bw": 20213,
- "7gw": 20214,
- "7cl": 20215,
- "7sc": 20216,
- "7qs": 20217,
- "7ws": 20218,
- "7sp": 20219,
- "7ma": 20220,
- "7mt": 20221,
- "7fl": 20222,
- "7wh": 20223,
- "7m7": 20224,
- "7gm": 20225,
- "7ss": 20226,
- "7sm": 20227,
- "7sb": 20228,
- "7fc": 20229,
- "7cr": 20230,
- "7bs": 20231,
- "7ls": 20232,
- "7wd": 20233,
- "72h": 20234,
- "7cm": 20235,
- "7gs": 20236,
- "7b7": 20237,
- "7fb": 20238,
- "7gd": 20239,
- "7dg": 20240,
- "7di": 20241,
- "7kr": 20242,
- "7bl": 20243,
- "7tk": 20244,
- "7ta": 20245,
- "7bk": 20246,
- "7b8": 20247,
- "7ja": 20248,
- "7pi": 20249,
- "7s7": 20250,
- "7gl": 20251,
- "7ts": 20252,
- "7sr": 20253,
- "7tr": 20254,
- "7br": 20255,
- "7st": 20256,
- "7p7": 20257,
- "7o7": 20258,
- "7vo": 20259,
- "7s8": 20260,
- "7pa": 20261,
- "7h7": 20262,
- "7wc": 20263,
- "6ss": 20264,
- "6ls": 20265,
- "6cs": 20266,
- "6bs": 20267,
- "6ws": 20268,
- "6sb": 20269,
- "6hb": 20270,
- "6lb": 20271,
- "6cb": 20272,
- "6s7": 20273,
- "6l7": 20274,
- "6sw": 20275,
- "6lw": 20276,
- "6lx": 20277,
- "6mx": 20278,
- "6hx": 20279,
- "6rx": 20280,
- "am1": 20292,
- "am2": 20293,
- "am3": 20294,
- "am4": 20295,
- "am5": 20296,
- "ob6": 20297,
- "ob7": 20298,
- "ob8": 20299,
- "ob9": 20300,
- "oba": 20301,
- "am6": 20302,
- "am7": 20303,
- "am8": 20304,
- "am9": 20305,
- "ama": 20306,
- "obb": 20307,
- "obc": 20308,
- "obd": 20309,
- "obe": 20310,
- "obf": 20311,
- "amb": 20312,
- "amc": 20313,
- "amd": 20314,
- "ame": 20315,
- "amf": 20316,
- "ba1": 20322,
- "ba2": 20323,
- "ba3": 20324,
- "ba4": 20325,
- "ba5": 20326,
- "pa1": 20327,
- "pa2": 20328,
- "pa3": 20329,
- "pa4": 20330,
- "pa5": 20331,
- "ci0": 20337,
- "ci1": 20338,
- "ci2": 20339,
- "ci3": 20340,
- "uap": 20341,
- "ukp": 20342,
- "ulm": 20343,
- "uhl": 20344,
- "uhm": 20345,
- "urn": 20346,
- "usk": 20347,
- "uui": 20348,
- "uea": 20349,
- "ula": 20350,
- "utu": 20351,
- "ung": 20352,
- "ucl": 20353,
- "uhn": 20354,
- "urs": 20355,
- "upl": 20356,
- "ult": 20357,
- "uld": 20358,
- "uth": 20359,
- "uul": 20360,
- "uar": 20361,
- "utp": 20362,
- "uuc": 20363,
- "uml": 20364,
- "urg": 20365,
- "uit": 20366,
- "uow": 20367,
- "uts": 20368,
- "ulg": 20369,
- "uvg": 20370,
- "umg": 20371,
- "utg": 20372,
- "uhg": 20373,
- "ulb": 20374,
- "uvb": 20375,
- "umb": 20376,
- "utb": 20377,
- "uhb": 20378,
- "ulc": 20379,
- "uvc": 20380,
- "umc": 20381,
- "utc": 20382,
- "uhc": 20383,
- "uh9": 20384,
- "ush": 20385,
- "upk": 20386,
- "dr9": 20387,
- "dr7": 20388,
- "dr8": 20389,
- "dr6": 20390,
- "dra": 20391,
- "ba6": 20392,
- "ba7": 20393,
- "ba8": 20394,
- "ba9": 20395,
- "baa": 20396,
- "pa6": 20397,
- "pa7": 20398,
- "pa8": 20399,
- "pa9": 20400,
- "paa": 20401,
- "ne6": 20402,
- "ne7": 20403,
- "ne8": 20404,
- "ne9": 20405,
- "nea": 20406,
- "dre": 20407,
- "drc": 20408,
- "drd": 20409,
- "drb": 20410,
- "drf": 20411,
- "bab": 20412,
- "bac": 20413,
- "bad": 20414,
- "bae": 20415,
- "baf": 20416,
- "pab": 20417,
- "pac": 20418,
- "pae": 20419,
- "paf": 20420,
- "neb": 20421,
- "nec": 20422,
- "ned": 20423,
- "nee": 20424,
- "nef": 20425,
- "jew": 20433,
- "cm1": 20435,
- "cm2": 20436,
- "cm3": 20437,
- "Charmdes": 20438,
- "ice": 20439,
- "r33": 20440,
- "r32": 20441,
- "r31": 20442,
- "r30": 20443,
- "r29": 20444,
- "r28": 20445,
- "r27": 20446,
- "r26": 20447,
- "r25": 20448,
- "r24": 20449,
- "r23": 20450,
- "r22": 20451,
- "r21": 20452,
- "r20": 20453,
- "r19": 20454,
- "r18": 20455,
- "r17": 20456,
- "r16": 20457,
- "r15": 20458,
- "r14": 20459,
- "r13": 20460,
- "r12": 20461,
- "r11": 20462,
- "r10": 20463,
- "r09": 20464,
- "r08": 20465,
- "r07": 20466,
- "r06": 20467,
- "r05": 20468,
- "r04": 20469,
- "r03": 20470,
- "r02": 20471,
- "r01": 20472,
- "r33L": 20473,
- "r32L": 20474,
- "r31L": 20475,
- "r30L": 20476,
- "r29L": 20477,
- "r28L": 20478,
- "r27L": 20479,
- "r26L": 20480,
- "r25L": 20481,
- "r24L": 20482,
- "r23L": 20483,
- "r22L": 20484,
- "r21L": 20485,
- "r20L": 20486,
- "r19L": 20487,
- "r18L": 20488,
- "r17L": 20489,
- "r16L": 20490,
- "r15L": 20491,
- "r14L": 20492,
- "r13L": 20493,
- "r12L": 20494,
- "r11L": 20495,
- "r10L": 20496,
- "r09L": 20497,
- "r08L": 20498,
- "r07L": 20499,
- "r06L": 20500,
- "r05L": 20501,
- "r04L": 20502,
- "r03L": 20503,
- "r02L": 20504,
- "r01L": 20505,
- "RuneQuote": 20506,
- "Runeword1": 20507,
- "Runeword2": 20508,
- "Runeword3": 20509,
- "Runeword4": 20510,
- "Runeword5": 20511,
- "Runeword6": 20512,
- "Runeword7": 20513,
- "Runeword8": 20514,
- "Runeword9": 20515,
- "Runeword10": 20516,
- "Runeword11": 20517,
- "Runeword12": 20518,
- "Runeword13": 20519,
- "Runeword14": 20520,
- "Runeword15": 20521,
- "Runeword16": 20522,
- "Runeword17": 20523,
- "Runeword18": 20524,
- "Runeword19": 20525,
- "Runeword20": 20526,
- "Runeword21": 20527,
- "Runeword22": 20528,
- "Runeword23": 20529,
- "Runeword24": 20530,
- "Runeword25": 20531,
- "Runeword26": 20532,
- "Runeword27": 20533,
- "Runeword28": 20534,
- "Runeword29": 20535,
- "Runeword30": 20536,
- "Runeword31": 20537,
- "Runeword32": 20538,
- "Runeword33": 20539,
- "Runeword34": 20540,
- "Runeword35": 20541,
- "Runeword36": 20542,
- "Runeword37": 20543,
- "Runeword38": 20544,
- "Runeword39": 20545,
- "Runeword40": 20546,
- "Runeword41": 20547,
- "Runeword42": 20548,
- "Runeword43": 20549,
- "Runeword44": 20550,
- "Runeword45": 20551,
- "Runeword46": 20552,
- "Runeword47": 20553,
- "Runeword48": 20554,
- "Runeword49": 20555,
- "Runeword50": 20556,
- "Runeword51": 20557,
- "Runeword52": 20558,
- "Runeword53": 20559,
- "Runeword54": 20560,
- "Runeword55": 20561,
- "Runeword56": 20562,
- "Runeword57": 20563,
- "Runeword58": 20564,
- "Runeword59": 20565,
- "Runeword60": 20566,
- "Runeword61": 20567,
- "Runeword62": 20568,
- "Runeword63": 20569,
- "Runeword64": 20570,
- "Runeword65": 20571,
- "Runeword66": 20572,
- "Runeword67": 20573,
- "Runeword68": 20574,
- "Runeword69": 20575,
- "Runeword70": 20576,
- "Runeword71": 20577,
- "Runeword72": 20578,
- "Runeword73": 20579,
- "Runeword74": 20580,
- "Runeword75": 20581,
- "Runeword76": 20582,
- "Runeword77": 20583,
- "Runeword78": 20584,
- "Runeword79": 20585,
- "Runeword81": 20586,
- "Runeword82": 20587,
- "Runeword83": 20588,
- "Runeword84": 20589,
- "Runeword85": 20590,
- "Runeword86": 20591,
- "Runeword87": 20592,
- "Runeword88": 20593,
- "Runeword89": 20594,
- "Runeword90": 20595,
- "Runeword91": 20596,
- "Runeword92": 20597,
- "Runeword93": 20598,
- "Runeword94": 20599,
- "Runeword95": 20600,
- "Runeword96": 20601,
- "Runeword97": 20602,
- "Runeword98": 20603,
- "Runeword99": 20604,
- "Runeword100": 20605,
- "Runeword101": 20606,
- "Runeword102": 20607,
- "Runeword103": 20608,
- "Runeword104": 20609,
- "Runeword105": 20610,
- "Runeword106": 20611,
- "Runeword107": 20612,
- "Runeword108": 20613,
- "Runeword109": 20614,
- "Runeword110": 20615,
- "Runeword111": 20616,
- "Runeword112": 20617,
- "Runeword113": 20618,
- "Runeword114": 20619,
- "Runeword115": 20620,
- "Runeword116": 20621,
- "Runeword117": 20622,
- "Runeword118": 20623,
- "Runeword119": 20624,
- "Runeword120": 20625,
- "Runeword121": 20626,
- "Runeword122": 20627,
- "Runeword123": 20628,
- "Runeword124": 20629,
- "Runeword125": 20630,
- "Runeword126": 20631,
- "Runeword127": 20632,
- "Runeword128": 20633,
- "Runeword129": 20634,
- "Runeword130": 20635,
- "Runeword131": 20636,
- "Runeword132": 20637,
- "Runeword133": 20638,
- "Runeword134": 20639,
- "Runeword135": 20640,
- "Runeword136": 20641,
- "Runeword137": 20642,
- "Runeword138": 20643,
- "Runeword139": 20644,
- "Runeword140": 20645,
- "Runeword141": 20646,
- "Runeword142": 20647,
- "Runeword143": 20648,
- "Runeword144": 20649,
- "Runeword145": 20650,
- "Runeword146": 20651,
- "Runeword147": 20652,
- "Runeword148": 20653,
- "Runeword149": 20654,
- "Runeword150": 20655,
- "Runeword151": 20656,
- "Runeword152": 20657,
- "Runeword153": 20658,
- "Runeword154": 20659,
- "Runeword155": 20660,
- "Runeword156": 20661,
- "Runeword157": 20662,
- "Runeword158": 20663,
- "Runeword159": 20664,
- "Runeword160": 20665,
- "Runeword161": 20666,
- "Runeword162": 20667,
- "Runeword163": 20668,
- "Runeword164": 20669,
- "Runeword165": 20670,
- "Runeword166": 20671,
- "Runeword167": 20672,
- "Runeword168": 20673,
- "Runeword169": 20674,
- "Runeword170": 20675,
- "spe": 20676,
- "scz": 20677,
- "sol": 20678,
- "qll": 20679,
- "fng": 20680,
- "flg": 20681,
- "tal": 20682,
- "hrn": 20683,
- "eyz": 20684,
- "jaw": 20685,
- "brz": 20686,
- "hrt": 20687,
- "Stout": 20688,
- "Antimagic": 20689,
- "Null": 20690,
- "Godly": 20691,
- "Ivory": 20692,
- "Eburin": 20693,
- "Blanched": 20694,
- "Stalwart": 20695,
- "Burly": 20696,
- "Dense": 20697,
- "Thin": 20698,
- "Compact": 20699,
- "Witch-hunter's": 20700,
- "Magekiller's": 20701,
- "Hierophant's": 20702,
- "Shaman's": 20703,
- "Pestilent": 20704,
- "Toxic": 20705,
- "Corosive": 20706,
- "Envenomed": 20707,
- "Septic": 20708,
- "Shocking": 20709,
- "Arcing": 20710,
- "Buzzing": 20711,
- "Static": 20712,
- "Scorching": 20713,
- "Flaming": 20714,
- "Smoking": 20715,
- "Smoldering": 20716,
- "Ember": 20717,
- "Hibernal": 20718,
- "Boreal": 20719,
- "Shivering": 20720,
- "Snowflake": 20721,
- "Mnemonic": 20722,
- "Visionary": 20723,
- "Eagleeye": 20724,
- "Hawkeye": 20725,
- "Falconeye": 20726,
- "Sparroweye": 20727,
- "Robineye": 20728,
- "Paradox": 20729,
- "Shouting": 20730,
- "Yelling": 20731,
- "Calling": 20732,
- "Loud": 20733,
- "Trump": 20734,
- "Joker's": 20735,
- "Jester's": 20736,
- "Jack's": 20737,
- "Knave's": 20738,
- "Paleocene": 20739,
- "Eocene": 20740,
- "Oligocene": 20741,
- "Miocene": 20742,
- "Kenshi's": 20743,
- "Sensei's": 20744,
- "Shogukusha's": 20745,
- "Psychic": 20746,
- "Mentalist's": 20747,
- "Cunning": 20748,
- "Trickster's": 20749,
- "Entrapping": 20750,
- "Gaea's": 20751,
- "Terra's": 20752,
- "Nature's": 20753,
- "Communal": 20754,
- "Feral": 20755,
- "Spiritual": 20756,
- "Keeper's": 20757,
- "Caretaker's": 20758,
- "Trainer's": 20759,
- "Veteran's": 20760,
- "Expert's": 20761,
- "Furious": 20762,
- "Raging": 20763,
- "Echoing": 20764,
- "Resonant": 20765,
- "Sounding": 20766,
- "Guardian's": 20767,
- "Warder's": 20768,
- "Preserver's": 20769,
- "Marshal's": 20770,
- "Commander's": 20771,
- "Captain's": 20772,
- "Rose Branded": 20773,
- "Hawk Branded": 20774,
- "Lion Branded": 20775,
- "Golemlord's": 20776,
- "Vodoun": 20777,
- "Graverobber's": 20778,
- "Venomous": 20779,
- "Noxious": 20780,
- "Fungal": 20781,
- "Accursed": 20782,
- "Blighting": 20783,
- "Hexing": 20784,
- "Glacial": 20785,
- "Freezing": 20786,
- "Chilling": 20787,
- "Powered": 20788,
- "Charged": 20789,
- "Sparking": 20790,
- "Volcanic": 20791,
- "Blazing": 20792,
- "Burning": 20793,
- "Lancer's": 20794,
- "Spearmaiden's": 20795,
- "Harpoonist's": 20796,
- "Athlete's": 20797,
- "Gymnast's": 20798,
- "Acrobat's": 20799,
- "Bowyer's": 20800,
- "Diamond": 20801,
- "Celestial": 20802,
- "Elysian": 20803,
- "Astral": 20804,
- "Unearthly": 20805,
- "Arcadian": 20806,
- "Jeweler's": 20807,
- "Artificer's": 20808,
- "Mechanist's": 20809,
- "Aureolin": 20810,
- "Victorious": 20811,
- "Ambergris": 20812,
- "Camphor": 20813,
- "Lapis Lazuli": 20814,
- "Chromatic": 20815,
- "Scintillating": 20816,
- "Turquoise": 20817,
- "Jacinth": 20818,
- "Zircon": 20819,
- "Bahamut's": 20820,
- "Great Wyrm's": 20821,
- "Felicitous": 20822,
- "Lucky": 20823,
- "Wailing": 20824,
- "Screaming": 20825,
- "Grandmaster's": 20826,
- "Master's": 20827,
- "Argent": 20828,
- "Tin": 20829,
- "Nickel": 20830,
- "Maroon": 20831,
- "Chestnut": 20832,
- "Vigorous": 20833,
- "Brown": 20834,
- "Dun": 20835,
- "Realgar": 20836,
- "Rusty": 20837,
- "Cinnabar": 20838,
- "Vermillion": 20839,
- "Carmine": 20840,
- "Carbuncle": 20841,
- "Serrated": 20842,
- "Scarlet": 20843,
- "Bloody": 20844,
- "Sanguinary": 20845,
- "Pearl": 20846,
- "Divine": 20847,
- "Hallowed": 20848,
- "Sacred": 20849,
- "Pure": 20850,
- "Consecrated": 20851,
- "Assamic": 20852,
- "Frantic": 20853,
- "Hellatial": 20854,
- "Quixotic": 20855,
- "Smiting": 20856,
- "Steller": 20857,
- "Stinging": 20858,
- "Singing": 20859,
- "Timeless": 20860,
- "Original": 20861,
- "Corporal": 20862,
- "Lawful": 20863,
- "Chaotic": 20864,
- "Fierce": 20865,
- "Ferocious": 20866,
- "Perpetual": 20867,
- "Continuous": 20868,
- "Laden": 20869,
- "Pernicious": 20870,
- "Harmful": 20871,
- "Evil": 20872,
- "Insidious": 20873,
- "Malicious": 20874,
- "Spiteful": 20875,
- "Precocious": 20876,
- "Majestic": 20877,
- "Sanguine": 20878,
- "Monumental": 20879,
- "Irresistible": 20880,
- "Festering": 20881,
- "Musty": 20882,
- "Dusty": 20883,
- "Decaying": 20884,
- "Rotting": 20885,
- "Infectious": 20886,
- "Foggy": 20887,
- "Cloudy": 20888,
- "Hazy": 20889,
- "Punishing": 20890,
- "Obsidian": 20891,
- "Royal": 20892,
- "Frigid": 20893,
- "Moldy": 20894,
- "Gaudy": 20895,
- "Impecable": 20896,
- "Soulless": 20897,
- "Heated": 20898,
- "Lasting": 20899,
- "Scorched": 20900,
- "Marred": 20901,
- "Lilac": 20902,
- "Rose": 20903,
- "Shimmering": 20904,
- "Wicked": 20906,
- "Strange": 20907,
- "Repulsive": 20908,
- "Reclusive": 20909,
- "Rude": 20911,
- "Hermetic": 20912,
- "Rainbow": 20913,
- "Colorful": 20914,
- "Stinky": 20915,
- "Gritty": 20916,
- "of Warming": 20917,
- "of Stoicism": 20918,
- "of the Dynamo": 20919,
- "of Grounding": 20920,
- "of Insulation": 20921,
- "of Resistance": 20922,
- "of Faith": 20923,
- "of Fire Quenching": 20924,
- "of Amianthus": 20925,
- "of Incombustibility": 20926,
- "of Coolness": 20927,
- "of Anima": 20928,
- "of Life Everlasting": 20929,
- "of Sunlight": 20930,
- "of Frozen Orb": 20931,
- "of Hydra Shield": 20932,
- "of Chilling Armor": 20933,
- "of Blizzard": 20934,
- "of Energy Shield": 20935,
- "of Thunder Storm": 20936,
- "of Meteor": 20937,
- "of Glacial Spike": 20938,
- "of Teleport Shield": 20939,
- "of Chain Lightning": 20940,
- "of Enchant": 20941,
- "of Fire Wall": 20942,
- "of Shiver Armor": 20943,
- "of Nova Shield": 20944,
- "of Nova": 20945,
- "of Fire Ball": 20946,
- "of Blaze": 20947,
- "of Ice Blast": 20948,
- "of Frost Shield": 20949,
- "of Telekinesis": 20950,
- "of Static Field": 20951,
- "of Frozen Armor": 20952,
- "of Icebolt": 20953,
- "of Charged Shield": 20954,
- "of Firebolts": 20955,
- "of the Elements": 20956,
- "of the Cobra": 20957,
- "of the Efreeti": 20958,
- "of the Phoenix": 20959,
- "of the Yeti": 20960,
- "of Grace and Power": 20961,
- "of Grace": 20962,
- "of Power": 20963,
- "of the Elephant": 20964,
- "of Memory": 20965,
- "of the Kraken1": 20966,
- "of Propogation": 20967,
- "of Replenishing": 20968,
- "of Ages": 20969,
- "of Fast Repair": 20970,
- "of Self-Repair": 20971,
- "of Acceleration": 20972,
- "of Traveling": 20973,
- "of Virility": 20974,
- "of Atlus": 20975,
- "of Freedom": 20976,
- "of the Lamprey": 20977,
- "of Hope": 20978,
- "of Spirit": 20979,
- "of Vita": 20980,
- "of Substinence": 20981,
- "of the Whale": 20982,
- "of the Squid": 20983,
- "of the Colossus1": 20984,
- "of Knowledge": 20985,
- "of Enlightenment": 20986,
- "of Prosperity": 20987,
- "of Good Luck": 20988,
- "of Luck": 20989,
- "of Avarice": 20990,
- "of Honor": 20991,
- "of Revivification": 20992,
- "of Truth": 20993,
- "of Daring": 20994,
- "of Nirvana": 20995,
- "of Envy": 20996,
- "of Anthrax": 20997,
- "of Bliss": 20998,
- "of Joy": 20999,
- "of Transcendence": 21000,
- "of Wrath": 21001,
- "of Ire": 21002,
- "of Evisceration": 21003,
- "of Butchery": 21004,
- "of Ennui": 21005,
- "of Storms": 21006,
- "of Passion": 21007,
- "of Incineration": 21008,
- "of Frigidity": 21009,
- "of Winter": 21010,
- "of the Icicle": 21011,
- "of Fervor": 21012,
- "of Malice": 21013,
- "of Swords": 21014,
- "of Razors": 21015,
- "of Desire": 21016,
- "of the Sirocco": 21017,
- "of the Dunes": 21018,
- "of Thawing": 21019,
- "Of the Choir": 21020,
- "Of the Sniper": 21021,
- "Of the Stiletto": 21022,
- "Of Bile": 21023,
- "Of Blitzen": 21024,
- "Of Cremation": 21025,
- "Of Darkness": 21026,
- "Of Disease": 21027,
- "Of Remorse": 21028,
- "Of Terror": 21029,
- "Of the Sky": 21030,
- "Of Valhalla": 21031,
- "Of Waste": 21032,
- "Of Nobility": 21033,
- "Of Karma": 21034,
- "Of Grounding": 21035,
- "Of the River": 21036,
- "Of the Lake": 21037,
- "Of the Ocean": 21038,
- "Of the Bayou": 21039,
- "Of the Stream": 21040,
- "Of the Lady": 21041,
- "Of the Maiden": 21042,
- "Of the Virgin": 21043,
- "Of the Hag": 21044,
- "Of the Witch": 21045,
- "Of Judgement": 21046,
- "Of Illusion": 21047,
- "Of Elusion": 21048,
- "Of Combat": 21049,
- "Of Attrition": 21050,
- "Of Abrasion": 21051,
- "Of Erosion": 21052,
- "Of Searing": 21053,
- "Of Stone": 21054,
- "Of Stature": 21055,
- "Of Fortication": 21056,
- "Of Quickening": 21057,
- "Of Dispatch": 21058,
- "Of Daring": 21059,
- "Of Dread": 21060,
- "Of Suffering": 21061,
- "Of Doom": 21062,
- "Of Vengence": 21063,
- "Of Redemption": 21064,
- "Of Luck": 21065,
- "Of the Avenger": 21066,
- "Of the Specter": 21067,
- "Of the Ghost": 21068,
- "Of the Infantry": 21069,
- "Of the Mosquito": 21070,
- "Of the Gnat": 21071,
- "Of the Fly": 21072,
- "Of the Plague": 21073,
- "Of Twilight": 21074,
- "Of Dusk": 21075,
- "Of Dawn": 21076,
- "Of the Imbecile": 21077,
- "Of the Idiot": 21078,
- "Of the Retard": 21079,
- "Of the Jujube": 21080,
- "Of the Obscenity": 21081,
- "Of Quota": 21082,
- "Of the Maggot": 21083,
- "Of Horror": 21084,
- "Of Baddass": 21085,
- "Of the Beast": 21086,
- "Of Cruelty": 21087,
- "Of Badness": 21088,
- "Of the Horde": 21089,
- "Of the Forest": 21090,
- "Of the Lilly": 21091,
- "Of the Grassy Gnoll": 21092,
- "Of the Stars": 21093,
- "Of the Moon": 21094,
- "Of Love": 21095,
- "Of the Unicorn": 21096,
- "Of the Walrus": 21097,
- "Of the Earth": 21098,
- "Of Vines": 21099,
- "Of Honor": 21100,
- "Of Tribute": 21101,
- "Of Credit": 21102,
- "Of Admiration": 21103,
- "Of Sweetness": 21104,
- "Of Beauty": 21105,
- "Of Pilfering": 21106,
- "of Damage Amplification": 21107,
- "of Hurricane": 21108,
- "of Armageddon": 21109,
- "of Tornado": 21110,
- "of Volcano": 21111,
- "of Twister": 21112,
- "of Cyclone Armor": 21113,
- "of Eruption": 21114,
- "of Molten Boulders": 21115,
- "of Firestorms": 21116,
- "of Battle Command": 21117,
- "of War Cry": 21118,
- "of Grim Ward": 21119,
- "of Battle Orders": 21120,
- "of Battle Cry": 21121,
- "of Concentration": 21122,
- "of Item Finding": 21123,
- "of Stunning": 21124,
- "of Shouting": 21125,
- "of Taunting": 21126,
- "of Potion Finding": 21127,
- "of Howling": 21128,
- "of Fist of the Heavens": 21129,
- "of Holy Shield": 21130,
- "of Conversion": 21131,
- "of Blessed Hammers": 21132,
- "of Vengeance": 21133,
- "of Charging": 21134,
- "of Zeal": 21135,
- "of Holy Bolts": 21136,
- "of Sacrifice": 21137,
- "of Fire Golem Summoning": 21138,
- "of Bone Spirits": 21139,
- "of Poison Novas": 21140,
- "of Lower Resistance": 21141,
- "of Iron Golem Creation": 21142,
- "of Bone Imprisonment": 21143,
- "of Decrepification": 21144,
- "of Attraction": 21145,
- "of Blood Golem Summoning": 21146,
- "of Bone Spears": 21147,
- "of Poison Explosion": 21148,
- "of Life Tap": 21149,
- "of Confusion": 21150,
- "of Raise Skeletal Mages": 21151,
- "of Bone Walls": 21152,
- "of Terror": 21153,
- "of Iron Maiden": 21154,
- "of Clay Golem Summoning": 21155,
- "of Corpse Explosions": 21156,
- "of Poison Dagger": 21157,
- "of Weaken": 21158,
- "of Dim Vision": 21159,
- "of Raise Skeletons": 21160,
- "of Bone Armor": 21161,
- "of Teeth": 21162,
- "of Amplify Damage": 21163,
- "of Frozen Orbs": 21164,
- "of Hydras": 21165,
- "of Blizzards": 21166,
- "of Meteors": 21167,
- "of Glacial Spikes": 21168,
- "of Teleportation": 21169,
- "of Enchantment": 21170,
- "of Fire Walls": 21171,
- "of Novas": 21172,
- "of Fire Balls": 21173,
- "of Blazing": 21174,
- "of Ice Blasts": 21175,
- "of Frost Novas": 21176,
- "of Ice Bolts": 21177,
- "of Charged Bolts": 21178,
- "of Fire Bolts": 21179,
- "of Lightning Fury": 21180,
- "of Lightning Spear": 21181,
- "of Freezing Arrows": 21182,
- "of Fending": 21183,
- "of Immolating Arrows": 21184,
- "of Plague Javelin": 21185,
- "of Charged Spear": 21186,
- "of Guided Arrows": 21187,
- "of Ice Arrows": 21188,
- "of Lightning Javelin": 21189,
- "of Impaling Spear": 21190,
- "of Slow Missiles": 21191,
- "of Exploding Arrows": 21192,
- "of Poison Javelin": 21193,
- "of Power Spear": 21194,
- "of Multiple Shot": 21195,
- "of Cold Arrows": 21196,
- "of Jabbing": 21197,
- "of Inner Sight": 21198,
- "of Fire Arrows": 21199,
- "of Magic Arrows": 21200,
- "Of self-repair": 21201,
- "of Dawn": 21202,
- "of Inertia": 21203,
- "of Joyfulness": 21204,
- "ModStre8a": 21205,
- "ModStre8b": 21206,
- "ModStre8c": 21207,
- "ModStre8d": 21208,
- "ModStre8e": 21209,
- "ModStre8f": 21210,
- "ModStre8g": 21211,
- "ModStre8h": 21212,
- "ModStre8i": 21213,
- "ModStre8j": 21214,
- "ModStre8k": 21215,
- "ModStre8l": 21216,
- "ModStre8m": 21217,
- "ModStre8n": 21218,
- "ModStre8o": 21219,
- "ModStre8p": 21220,
- "ModStre8q": 21221,
- "ModStre8r": 21222,
- "ModStre8s": 21223,
- "ModStre8t": 21224,
- "ModStre8u": 21225,
- "ModStre8v": 21226,
- "ModStre8w": 21227,
- "ModStre8x": 21228,
- "ModStre8y": 21229,
- "ModStre8z": 21230,
- "ModStre9a": 21231,
- "ModStre9b": 21232,
- "ModStre9c": 21233,
- "ModStre9d": 21234,
- "ModStre9e": 21235,
- "ModStre9f": 21236,
- "ModStre9g": 21237,
- "ModStre9h": 21238,
- "ModStre9i": 21239,
- "ModStre9s": 21240,
- "ModStre9t": 21241,
- "ModStre9u": 21242,
- "ModStre9v": 21243,
- "ModStre9w": 21244,
- "ModStre9x": 21245,
- "ModStre9y": 21246,
- "ModStre9z": 21247,
- "ModStre10a": 21248,
- "ModStre10b": 21249,
- "ModStre10c": 21250,
- "ModStre10d": 21251,
- "ModStre10e": 21252,
- "ModStre10f": 21253,
- "ModStre10g": 21254,
- "ModStre10h": 21255,
- "ModStre10i": 21256,
- "ModStre10j": 21257,
- "WeaponDescOrb": 21259,
- "ItemexpED": 21260,
- "StrGemX1": 21261,
- "StrGemX2": 21262,
- "StrGemX3": 21263,
- "StrGemX4": 21264,
- "GemeffectX11": 21265,
- "GemeffectX12": 21266,
- "GemeffectX13": 21267,
- "GemeffectX21": 21268,
- "GemeffectX22": 21269,
- "GemeffectX23": 21270,
- "GemeffectX31": 21271,
- "GemeffectX32": 21272,
- "GemeffectX33": 21273,
- "GemeffectX41": 21274,
- "GemeffectX42": 21275,
- "GemeffectX43": 21276,
- "GemeffectX51": 21277,
- "GemeffectX52": 21278,
- "GemeffectX53": 21279,
- "GemeffectX61": 21280,
- "GemeffectX62": 21281,
- "GemeffectX63": 21282,
- "GemeffectX71": 21283,
- "GemeffectX72": 21284,
- "GemeffectX73": 21285,
- "Coldkill": 21286,
- "Butchers Cleaver": 21287,
- "Butcher's Pupil": 21288,
- "Islestrike": 21289,
- "Pompe's Wrath": 21290,
- "Guardian Naga": 21291,
- "Warlord's Trust": 21292,
- "Spellsteel": 21293,
- "Stormrider": 21294,
- "Boneslayer Blade": 21295,
- "The Minotaur": 21296,
- "Suicide Branch": 21297,
- "Cairn Shard": 21298,
- "Arm of King Leoric": 21299,
- "Blackhand Key": 21300,
- "Dark Clan Crusher": 21301,
- "Drulan's Tongue": 21302,
- "Zakrum's Hand": 21303,
- "The Fetid Sprinkler": 21304,
- "Hand of Blessed Light": 21305,
- "Fleshrender": 21306,
- "Sureshrill Frost": 21307,
- "Moonfall": 21308,
- "Baezils Vortex": 21309,
- "Earthshaker": 21310,
- "Bloodtree Stump": 21311,
- "The Gavel of Pain": 21312,
- "Bloodletter": 21313,
- "Coldsteal Eye": 21314,
- "Hexfire": 21315,
- "Blade of Ali Baba": 21316,
- "Riftslash": 21317,
- "Headstriker": 21318,
- "Plague Bearer": 21319,
- "The Atlantien": 21320,
- "Crainte Vomir": 21321,
- "Bing Sz Wang": 21322,
- "The Vile Husk": 21323,
- "Cloudcrack": 21324,
- "Todesfaelle Flamme": 21325,
- "Swordguard": 21326,
- "Spineripper": 21327,
- "Heart Carver": 21328,
- "Blackbog's Sharp": 21329,
- "Stormspike": 21330,
- "The Impaler": 21331,
- "Kelpie Snare": 21332,
- "Soulfeast Tine": 21333,
- "Hone Sundan": 21334,
- "Spire of Honor": 21335,
- "The Meat Scraper": 21336,
- "Blackleach Blade": 21337,
- "Athena's Wrath": 21338,
- "Pierre Tombale Couant": 21339,
- "Husoldal Evo": 21340,
- "Grim's Burning Dead": 21341,
- "Ribcracker": 21342,
- "Chromatic Ire": 21343,
- "Warpspear": 21344,
- "Skullcollector": 21345,
- "Skystrike": 21346,
- "Kuko Shakaku": 21347,
- "Endlessshail": 21348,
- "Whichwild String": 21349,
- "Godstrike Arch": 21350,
- "Langer Briser": 21351,
- "Pus Spiter": 21352,
- "Buriza-Do Kyanon": 21353,
- "Vampiregaze": 21354,
- "String of Ears": 21355,
- "Gorerider": 21356,
- "Lavagout": 21357,
- "Venom Grip": 21358,
- "Visceratuant": 21359,
- "Guardian Angle": 21360,
- "Shaftstop": 21361,
- "Skin of the Vipermagi": 21362,
- "Blackhorn": 21363,
- "Valkiry Wing": 21364,
- "Peasent Crown": 21365,
- "Demon Machine": 21366,
- "Magewrath": 21367,
- "Cliffkiller": 21368,
- "Riphook": 21369,
- "Razorswitch": 21370,
- "Meatscrape": 21371,
- "Coldsteel Eye": 21372,
- "Pitblood Thirst": 21373,
- "Gaya Wand": 21374,
- "Ondal's Wisdom": 21375,
- "Geronimo's Fury": 21376,
- "Charsi's Favor": 21377,
- "Doppleganger's Shadow": 21378,
- "Deathbit": 21379,
- "Warshrike": 21380,
- "Gutsiphon": 21381,
- "Razoredge": 21382,
- "Stonerattle": 21383,
- "Marrowgrinder": 21384,
- "Gore Ripper": 21385,
- "Bush Wacker": 21386,
- "Demonlimb": 21387,
- "Steelshade": 21388,
- "Tomb Reaver": 21389,
- "Death's Web": 21390,
- "Gaia's Wrath": 21391,
- "Khalim's Vengance": 21392,
- "Angel's Song": 21393,
- "The Reedeemer": 21394,
- "Fleshbone": 21395,
- "Odium": 21396,
- "Blood Comet": 21397,
- "Bonehew": 21398,
- "Steelrend": 21399,
- "Stone Crusher": 21400,
- "Bul-Kathos' Might": 21401,
- "Arioc's Needle": 21402,
- "Shadowdancer": 21403,
- "Indiego's Fancy": 21404,
- "Aladdin's Eviserator": 21405,
- "Tyrael's Mercy": 21406,
- "Souldrain": 21407,
- "Runemaster": 21408,
- "Deathcleaver": 21409,
- "Executioner's Justice": 21410,
- "Wallace's Tear": 21411,
- "Leviathan": 21412,
- "The Wanderer's Blade": 21413,
- "Qual'Kek's Enforcer": 21414,
- "Dawnbringer": 21415,
- "Dragontooth": 21416,
- "Wisp": 21417,
- "Gargoyle's Bite": 21418,
- "Lacerator": 21419,
- "Mang Song's Lesson": 21420,
- "Viperfork": 21421,
- "Blood Chalice": 21422,
- "El Espiritu": 21423,
- "The Long Rod": 21424,
- "Demonhorn's Edge": 21425,
- "The Ensanguinator": 21426,
- "The Reaper's Toll": 21427,
- "Spiritkeeper": 21428,
- "Hellrack": 21429,
- "Alma Negra": 21430,
- "Darkforge Spawn": 21431,
- "Rockhew": 21432,
- "Sankenkur's Resurrection": 21433,
- "Erion's Bonehandle": 21434,
- "The Archon Magus": 21435,
- "Widow maker": 21436,
- "Catgut": 21437,
- "Ghostflame": 21438,
- "Shadowkiller": 21439,
- "Bling Bling": 21440,
- "Nebucaneezer's Storm": 21441,
- "Griffon's Eye": 21442,
- "Eaglewind": 21443,
- "Windhammer": 21444,
- "Thunderstroke": 21445,
- "Giantmaimer": 21446,
- "Demon's Arch": 21447,
- "The Scalper": 21448,
- "Bloodmoon": 21449,
- "Djinnslayer": 21450,
- "Cranebeak": 21451,
- "Iansang's Frenzy": 21452,
- "Warhound": 21453,
- "Gulletwound": 21454,
- "Headhunter's Glory": 21455,
- "Mordoc's marauder": 21456,
- "Talberd's Law": 21457,
- "Amodeus's Manipulator": 21458,
- "Darksoul": 21459,
- "The Black Adder": 21460,
- "Earthshifter": 21461,
- "Nature's Peace": 21462,
- "Horazon's Chalice": 21463,
- "Seraph's Hymn": 21464,
- "Zakarum's Salvation": 21465,
- "Fleshripper": 21466,
- "Stonerage": 21467,
- "Blood Rain": 21468,
- "Horizon's Tornado": 21469,
- "Nord's Tenderizer": 21470,
- "Wrath of Cain": 21471,
- "Siren's call": 21472,
- "Jadetalon": 21473,
- "Wraithfang": 21474,
- "Blademaster": 21475,
- "Cerebus": 21476,
- "Archangel's Deliverance": 21477,
- "Sinblade": 21478,
- "Runeslayer": 21479,
- "Excalibur": 21480,
- "Fuego Del Sol": 21481,
- "Stoneraven": 21482,
- "El Infierno": 21483,
- "Moonrend": 21484,
- "Larzuk's Champion": 21485,
- "Nightsummon": 21486,
- "Bonescapel": 21487,
- "Rabbit Slayer": 21488,
- "Pagan's Athame": 21489,
- "The Swashbuckler": 21490,
- "Kang's Virtue": 21491,
- "Snaketongue": 21492,
- "Lifechoke": 21493,
- "Ethereal edge": 21494,
- "Palo Grande": 21495,
- "Carnageleaver": 21496,
- "Ghostleach": 21497,
- "Soulreaper": 21498,
- "Samual's Caretaker": 21499,
- "Hell's Whisper": 21500,
- "The Harvester": 21501,
- "Raiden's Crutch": 21502,
- "The TreeEnt": 21503,
- "Stormwillow": 21504,
- "Moonshadow": 21505,
- "Strongoak": 21506,
- "Demonweb": 21507,
- "Bloodraven's Charge": 21508,
- "Shadefalcon": 21509,
- "Robin's Yolk": 21510,
- "Glimmershred": 21511,
- "Wraithflight": 21512,
- "Lestron's Mark": 21513,
- "Banshee's Wail": 21514,
- "Windstrike": 21515,
- "Medusa's Gaze": 21516,
- "Titanfist": 21517,
- "Hadeshorn": 21518,
- "Rockstopper": 21519,
- "Stealskull": 21520,
- "Darksight Helm": 21521,
- "Crown of Thieves": 21522,
- "Blackhorn's Face": 21523,
- "The Spirit Shroud": 21524,
- "Skin of the Flayed One": 21525,
- "Ironpelt": 21526,
- "Spiritforge": 21527,
- "Crow Caw": 21528,
- "Duriel's Shell": 21529,
- "Skullder's Ire": 21530,
- "Toothrow": 21531,
- "Atma's Wail": 21532,
- "Black Hades": 21533,
- "Corpsemourn": 21534,
- "Que-hegan's Wisdom": 21535,
- "Moser's Blessed Circle": 21536,
- "Stormchaser": 21537,
- "Tiamat's Rebuke": 21538,
- "Gerke's Sanctuary": 21539,
- "Radimant's Sphere": 21540,
- "Gravepalm": 21541,
- "Ghoulhide": 21542,
- "Hellmouth": 21543,
- "Infernostride": 21544,
- "Waterwalk": 21545,
- "Silkweave": 21546,
- "Wartraveler": 21547,
- "Razortail": 21548,
- "Gloomstrap": 21549,
- "Snowclash": 21550,
- "Thudergod's Vigor": 21551,
- "Lidless Wall": 21552,
- "Lanceguard": 21553,
- "Squire's Cover": 21554,
- "Boneflame": 21555,
- "Steelpillar": 21556,
- "Nightwing's Veil": 21557,
- "Hightower's Watch": 21558,
- "Crown of Ages": 21559,
- "Andariel's Visage": 21560,
- "Darkfear": 21561,
- "Dragonscale": 21562,
- "Steel Carapice": 21563,
- "Ashrera's Wired Frame": 21564,
- "Rainbow Facet": 21565,
- "Ravenlore": 21566,
- "Boneshade": 21567,
- "Nethercrow": 21568,
- "Hellwarden's Husk": 21569,
- "Flamebellow": 21570,
- "Fathom": 21571,
- "Wolfhowl": 21572,
- "Spirit Ward": 21573,
- "Kira's Guardian": 21574,
- "Orumus' Robes": 21575,
- "Gheed's Fortune": 21576,
- "The Vicar": 21577,
- "Stormlash": 21578,
- "Halaberd's Reign": 21579,
- "Parkersor's Calm": 21580,
- "Warriv's Warder": 21581,
- "Spike Thorn": 21582,
- "Dracul's Grasp": 21583,
- "Frostwind": 21584,
- "Templar's Might": 21585,
- "Eschuta's temper": 21620,
- "Firelizard's Talons": 21587,
- "Sandstorm Trek": 21588,
- "Marrowwalk": 21589,
- "Heaven's Light": 21590,
- "Merman's Speed": 21591,
- "Arachnid Mesh": 21592,
- "Nosferatu's Coil": 21593,
- "Metalgird": 21594,
- "Verdugo's Hearty Cord": 21595,
- "Sigurd's Staunch": 21596,
- "Carrion Wind": 21597,
- "Giantskull": 21598,
- "Ironward": 21599,
- "Gillian's Brazier": 21600,
- "Drakeflame": 21601,
- "Dust Storm": 21602,
- "Skulltred": 21603,
- "Alma's Reflection": 21604,
- "Drulan's Tounge": 21605,
- "Sacred Charge": 21606,
- "Bul-Kathos": 21607,
- "Saracen's Chance": 21608,
- "Highlord's Wrath": 21609,
- "Raven Frost": 21610,
- "Dwarf Star": 21611,
- "Atma's Scarab": 21612,
- "Mara's Kaleidoscope": 21613,
- "Crescent Moon": 21614,
- "The Rising Sun": 21615,
- "The Cat's Eye": 21616,
- "Bul Katho's Wedding Band": 21617,
- "Rings": 21618,
- "Metalgrid": 21619,
- "Stormshield": 21621,
- "Blackoak Shield": 21622,
- "Ormus' Robes": 21623,
- "Arkaine's Valor": 21624,
- "The Gladiator's Bane": 21625,
- "Veil of Steel": 21626,
- "Harlequin Crest": 21627,
- "Lance Guard": 21628,
- "Kerke's Sanctuary": 21629,
- "Mosers Blessed Circle": 21630,
- "Que-Hegan's Wisdon": 21631,
- "Guardian Angel": 21632,
- "Skin of the Flayerd One": 21633,
- "Armor": 21634,
- "Windforce": 21635,
- "Eaglehorn": 21636,
- "Gimmershred": 21637,
- "Widowmaker": 21638,
- "Stormspire": 21639,
- "Naj's Puzzler": 21640,
- "Ethereal Edge": 21641,
- "Wizardspike": 21642,
- "The Grandfather": 21643,
- "Doombringer": 21644,
- "Tyrael's Might": 21645,
- "Lightsabre": 21646,
- "The Cranium Basher": 21647,
- "Schaefer's Hammer": 21648,
- "Baranar's Star": 21649,
- "Deaths's Web": 21650,
- "Messerschmidt's Reaver": 21651,
- "Hellslayer": 21652,
- "Endlesshail": 21653,
- "The Atlantian": 21654,
- "Riftlash": 21655,
- "Baezil's Vortex": 21656,
- "Zakarum's Hand": 21657,
- "Carin Shard": 21658,
- "The Minataur": 21659,
- "Trang-Oul's Avatar": 21660,
- "Trang-Oul's Guise": 21661,
- "Trang-Oul's Wing": 21662,
- "Trang-Oul's Mask": 21663,
- "Trang-Oul's Scales": 21664,
- "Trang-Oul's Claws": 21665,
- "Trang-Oul's Girth": 21666,
- "Natalya's Odium": 21667,
- "Natalya's Totem": 21668,
- "Natalya's Mark": 21669,
- "Natalya's Shadow": 21670,
- "Natalya's Soul": 21671,
- "Griswold's Legacy": 21672,
- "Griswolds's Redemption": 21673,
- "Griswold's Honor": 21674,
- "Griswold's Heart": 21675,
- "Griswold's Valor": 21676,
- "Tang's Imperial Robes": 21677,
- "Tang's Fore-Fathers": 21678,
- "Tang's Rule": 21679,
- "Tang's Throne": 21680,
- "Tang's Battle Standard": 21681,
- "Ogun's Fierce Visage": 21682,
- "Ogun's Shadow": 21683,
- "Ogun's Lash": 21684,
- "Ogun's Vengeance": 21685,
- "Bul-Kathos' Warden": 21686,
- "Bul-Kathos' Children": 21687,
- "Bul-Kathos' Sacred Charge": 21688,
- "Bul-Kathos' Tribal Guardian": 21689,
- "Bul-Kathos' Custodian": 21690,
- "Flowkrad's Howl": 21691,
- "Flowkrad's Grin": 21692,
- "Flowkrad's Fur": 21693,
- "Flowkrad's Paws": 21694,
- "Flowkrad's Sinew": 21695,
- "Aldur's Watchtower": 21696,
- "Aldur's Stony Gaze": 21697,
- "Aldur's Deception": 21698,
- "Aldur's Guantlet": 21699,
- "Aldur's Advance": 21700,
- "M'avina's Battle Hymn": 21701,
- "M'avina's True Sight": 21702,
- "M'avina's Embrace": 21703,
- "M'avina's Icy Clutch": 21704,
- "M'avina's Tenet": 21705,
- "M'avina's Caster": 21706,
- "Sazabi's Grand Tribute": 21707,
- "Sazabi's Cobalt Redeemer": 21708,
- "Sazabi's Ghost Liberator": 21709,
- "Sazabi's Mental Sheath": 21710,
- "Hwanin's Majesty": 21711,
- "Hwanin's Justice": 21712,
- "Hwanin's Splendor": 21713,
- "Hwanin's Refuge": 21714,
- "Hwanin's Cordon": 21715,
- "The Disciple": 21716,
- "Telling of Beads": 21717,
- "Laying of Hands": 21718,
- "Rite of Passage": 21719,
- "Spiritual Custodian": 21720,
- "Credendum": 21721,
- "Cow King's Leathers": 21722,
- "Cow King's Horns": 21723,
- "Cow King's Hide": 21724,
- "Cow King's Hoofs": 21725,
- "Aragon's Masterpiece": 21726,
- "Aragon's Sunfire": 21727,
- "Aragon's Icy Stare": 21728,
- "Aragon's Storm Cloud": 21729,
- "Orphan's Call": 21730,
- "Guillaume's Face": 21731,
- "Willhelm's Pride": 21732,
- "Magnus' Skin": 21733,
- "Wihtstan's Guard": 21734,
- "Titan's Revenge": 21735,
- "Shakabra's Crux": 21736,
- "Lycander's Aim": 21737,
- "Shadow's Touch": 21738,
- "The Prowler": 21739,
- "Mortal Crescent": 21740,
- "Cutthroat": 21741,
- "Sarmichian Justice": 21742,
- "Annihilus": 21743,
- "Arreat's Face": 21744,
- "The Harbinger": 21745,
- "Doomseer": 21746,
- "Howling Visage": 21747,
- "Terra": 21748,
- "Syrian": 21749,
- "Jalal's Mane": 21750,
- "Malignant": 21751,
- "Apothecary's Tote": 21752,
- "Apocrypha": 21753,
- "Foci of Visjerei": 21754,
- "Homunculus": 21755,
- "Aurora's Guard": 21756,
- "Crest of Morn": 21757,
- "Herald of Zakarum": 21758,
- "Akarat's Protector": 21759,
- "Ancient Eye": 21760,
- "Globe of Visjerei": 21761,
- "The Oculus": 21762,
- "Phoenix Egg": 21763,
- "Xenos": 21764,
- "Nagas": 21765,
- "Wyvern's Head": 21766,
- "Sightless Veil": 21767,
- "ChampionFormatX": 21768,
- "EskillKickSing": 21769,
- "EskillKickPlur": 21770,
- "EskillPetLife": 21771,
- "EskillWolfDef": 21772,
- "EskillPassiveFeral": 21773,
- "Eskillperhit12": 21774,
- "Eskillincasehit": 21775,
- "Eskillincasemastery": 21776,
- "Eskillincaseraven": 21777,
- "pad": 21779,
- "axf": 21780,
- "Eskillkickdamage": 21781,
- "ModStre10k": 21782,
- "ModStre10L": 21783,
- "Class Specific": 21784,
- "fana": 21785,
- "qsta5q14": 21786,
- "qstsa5q42a": 21787,
- "qstsa5q31a": 21788,
- "qstsa5q21a": 21789,
- "qstsa5q43a": 21790,
- "qstsa5q62a": 21791,
- "qstsa5q61a": 21792,
- "act1X": 21797,
- "act2X": 21798,
- "act3X": 21799,
- "act4X": 21800,
- "strepilogueX": 21801,
- "act5X": 21802,
- "strlastcinematic": 21803,
- "CfgSay7": 21804,
- "0sc": 21805,
- "tr2": 21806,
- "of Lightning Strike": 21807,
- "of Plague Jab": 21808,
- "of Charged Strike": 21809,
- "of Impaling Strike": 21810,
- "of Poison Jab": 21811,
- "of Power Strike": 21812,
- "of the Colossus": 21813,
- "of the Kraken": 21814,
- "Tal Rasha's Wrappings": 21815,
- "Tal Rasha's Fire-Spun Cloth": 21816,
- "Tal Rasha's Adjudication": 21817,
- "Tal Rasha's Howling Wind": 21818,
- "Tal Rasha's Lidless Eye": 21819,
- "Tal Rasha's Horadric Crest": 21820,
- "Hwanin's Seal": 21821,
- "Heaven's Brethren": 21822,
- "Dangoon's Teaching": 21823,
- "Ondal's Almighty": 21824,
- "Heaven's Taebaek": 21825,
- "Haemosu's Adament": 21826,
- "Lycander's Flank": 21827,
- "Constricting Ring": 21828,
- "Ginther's Rift": 21829,
- "Naj's Ancient Set": 21830,
- "Naj's Light Plate": 21831,
- "Naj's Circlet": 21832,
- "Sander's Superstition": 21833,
- "Sander's Taboo": 21834,
- "Sander's Basis": 21835,
- "Sander's Derby": 21836,
- "Sander's Court Jester": 21837,
- "Ghost Liberator": 21838,
- "Wilhelm's Pride": 21839,
- "Immortal King's Stone Crusher": 21840,
- "Immortal King's Pillar": 21841,
- "Immortal King's Forge": 21842,
- "Immortal King's Detail": 21843,
- "Immortal King's Soul Cage \tImmortal King's Soul Cage": 21844,
- "Immortal King's Will": 21845,
- "Immortal King": 21846,
- "Aldur's Gauntlet": 21847,
- "Ancient Statue 3": 21848,
- "Ancient Statue 2": 21849,
- "Ancient Statue 1": 21850,
- "Baal Subject 1": 21851,
- "Baal Subject 2": 21852,
- "Baal Subject 3": 21853,
- "Baal Subject 4": 21854,
- "Baal Subject 5": 21855,
- "Baal Subject 6": 21856,
- "Baal Subject 6a": 21857,
- "Baal Subject 6b": 21858,
- "Baal Crab Clone": 21859,
- "Baal Crab to Stairs": 21860,
- "BaalColdMage": 21861,
- "Baal Subject Mummy": 21862,
- "Baal Tentacle": 21863,
- "Baals Minion": 21864,
- "Hell1": 21865,
- "Hell2": 21866,
- "Hell3": 21867,
- "To Hell1": 21868,
- "To Hell2": 21869,
- "To Hell3": 21870,
- "Lord of Destruction": 21871,
- "EskillPerBlade": 21873,
- "ExInsertSockets": 21874,
- "McAuley's Superstition": 21875,
- "McAuley's Taboo": 21876,
- "McAuley's Riprap": 21877,
- "McAuley's Paragon": 21878,
- "McAuley's Folly": 21879,
- "qstsa5q62b": 21881,
- "of the Plague": 21883,
- "Go South": 21884,
- "ItemExpansiveChancX": 21885,
- "ItemExpansiveChanc1": 21886,
- "ItemExpansiveChanc2": 21887,
- "ItemExpcharmdesc": 21888,
- "StrMercEx12": 21889,
- "StrMercEx14": 21890,
- "StrMercEx15": 21891,
- "Eskillelementaldmg": 21892,
- "Playersubtitles29": 21893,
- "Playersubtitles30": 21894,
- "LeaveCampDru": 21895,
- "LeaveCampAss": 21896,
- "EnterDOEAss": 21897,
- "EnterDOEDru": 21898,
- "EnterBurialAss": 21899,
- "EnterBurialDru": 21900,
- "EnterMonasteryAss": 21901,
- "EnterMonasteryDru": 21902,
- "EnterForgottenTAss": 21903,
- "EnterForgottenTDru": 21904,
- "EnterJailAss": 21905,
- "EnterJailDru": 21906,
- "EnterCatacombsAss": 21907,
- "EnterCatacombsDru": 21908,
- "CompletingDOEAss": 21909,
- "CompletingDOEDru": 21910,
- "CompletingBurialAss": 21911,
- "CompletingBurialDru": 21912,
- "FindingInifusAss": 21913,
- "FindingInifusDru": 21914,
- "FindingCairnAss": 21915,
- "FindingCairnDru": 21916,
- "FindingTristramAss": 21917,
- "FindingTristramDru": 21918,
- "RescueCainAss": 21919,
- "RescueCainDru": 21920,
- "HoradricMalusAss": 21921,
- "HoradricMalusDru": 21922,
- "CompletingAndarielAss": 21925,
- "CompletingAndarielDru": 21926,
- "EnteringRadamentAss": 21927,
- "EnteringRadamentDru": 21928,
- "CompletingRadamentAss": 21929,
- "CompletingRadamentDru": 21930,
- "BeginTaintedSunAss": 21931,
- "BeginTaintedSunDru": 21932,
- "EnteringClawViperAss": 21933,
- "EnteringClawViperDru": 21934,
- "CompletingTaintedSunAss": 21935,
- "CompletingTaintedSunDru": 21936,
- "EnteringArcaneAss": 21937,
- "EnteringArcaneDru": 21938,
- "FindingSummonerAss": 21939,
- "FindingSummonerDru": 21940,
- "CompletingSummonerAss": 21941,
- "CompletingSummonerDru": 21942,
- "FindingdecoyTombAss": 21943,
- "FindingdecoyTombDru": 21944,
- "FindingTrueTombAss": 21945,
- "FindingTrueTombDru": 21946,
- "CompletingTombAss": 21947,
- "CompletingTombDru": 21948,
- "FindingLamEsenAss": 21949,
- "FindingLamEsenDru": 21950,
- "CompletingLamEsenAss": 21952,
- "CompletingLamEsenDru": 21953,
- "FindingBeneathCityAss": 21954,
- "FindingBeneathCityDru": 21955,
- "FindingDrainLeverAss": 21956,
- "FindingDrainLeverDru": 21957,
- "CompletingBeneathCityAss": 21958,
- "CompletingBeneathCityDru": 21959,
- "CompletingBladeAss": 21960,
- "CompletingBladeDru": 21961,
- "FindingJadeFigAss": 21962,
- "FindingJadeFigDru": 21963,
- "FindingTempleAss": 21964,
- "FindingTempleDru": 21965,
- "CompletingTempleAss": 21966,
- "CompletingTempleDru": 21967,
- "FindingGuardianTowerAss": 21968,
- "FindingGuardianTowerDru": 21969,
- "CompletingGuardianTowerAss": 21971,
- "FreezingIzualAss": 21973,
- "FreezingIzualDru": 21974,
- "KillingdDiabloSor": 21975,
- "KillingdDiabloBar": 21976,
- "KillingdDiabloNec": 21977,
- "KillingdDiabloPal": 21978,
- "KillingdDiabloAms": 21979,
- "KillingdDiabloAss": 21980,
- "KillingdDiabloDru": 21981,
- "LeavingTownAct5Sor": 21982,
- "LeavingTownAct5Bar": 21983,
- "LeavingTownAct5Nec": 21984,
- "LeavingTownAct5Pal": 21985,
- "LeavingTownAct5Ams": 21986,
- "LeavingTownAct5Ass": 21987,
- "LeavingTownAct5Dru": 21988,
- "CompletingStopSiegeSor": 21989,
- "CompletingStopSiegeBar": 21990,
- "CompletingStopSiegeNec": 21991,
- "CompletingStopSiegePal": 21992,
- "CompletingStopSiegeAms": 21993,
- "CompletingStopSiegeAss": 21994,
- "CompletingStopSiegeDru": 21995,
- "RescueQual-KehkAct5Sor": 21996,
- "RescueQual-KehkAct5Bar": 21997,
- "RescueQual-KehkAct5Nec": 21998,
- "RescueQual-KehkAct5Pal": 21999,
- "RescueQual-KehkAct5Ams": 22000,
- "RescueQual-KehkAct5Ass": 22001,
- "RescueQual-KehkAct5Dru": 22002,
- "EnteringNihlathakAct5Sor": 22003,
- "EnteringNihlathakAct5Bar": 22004,
- "EnteringNihlathakAct5Nec": 22005,
- "EnteringNihlathakAct5Pal": 22006,
- "EnteringNihlathakAct5Ams": 22007,
- "EnteringNihlathakAct5Ass": 22008,
- "EnteringNihlathakAct5Dru": 22009,
- "CompletingNihlathakAct5Sor": 22010,
- "CompletingNihlathakAct5Bar": 22011,
- "CompletingNihlathakAct5Nec": 22012,
- "CompletingNihlathakAct5Pal": 22013,
- "CompletingNihlathakAct5Ams": 22014,
- "CompletingNihlathakAct5Ass": 22015,
- "CompletingNihlathakAct5Dru": 22016,
- "EnteringTopMountAct5Sor": 22017,
- "EnteringTopMountAct5Bar": 22018,
- "EnteringTopMountAct5Nec": 22019,
- "EnteringTopMountAct5Pal": 22020,
- "EnteringTopMountAct5Ams": 22021,
- "EnteringTopMountAct5Ass": 22022,
- "EnteringTopMountAct5Dru": 22023,
- "EnteringWorldstoneAct5Sor": 22024,
- "EnteringWorldstoneAct5Bar": 22025,
- "EnteringWorldstoneAct5Nec": 22026,
- "EnteringWorldstoneAct5Pal": 22027,
- "EnteringWorldstoneAct5Ams": 22028,
- "EnteringWorldstoneAct5Ass": 22029,
- "EnteringWorldstoneAct5Dru": 22030,
- "CompletingDefeatBaalAct5Sor": 22031,
- "CompletingDefeatBaalAct5Bar": 22032,
- "CompletingDefeatBaalAct5Nec": 22033,
- "CompletingDefeatBaalAct5Pal": 22034,
- "CompletingDefeatBaalAct5Ams": 22035,
- "CompletingDefeatBaalAct5Ass": 22036,
- "CompletingDefeatBaalAct5Dru": 22037,
- "Skillname222": 22038,
- "Skillsd222": 22039,
- "Skillld222": 22040,
- "Skillan222": 22041,
- "Skillname223": 22046,
- "Skillsd223": 22047,
- "Skillld223": 22048,
- "Skillan223": 22049,
- "Skillname225": 22050,
- "Skillsd225": 22051,
- "Skillld225": 22052,
- "Skillan225": 22053,
- "Skillname226": 22054,
- "Skillsd226": 22055,
- "Skillld226": 22056,
- "Skillan226": 22057,
- "Skillname227": 22058,
- "Skillsd227": 22059,
- "Skillld227": 22060,
- "Skillan227": 22061,
- "Skillname228": 22062,
- "Skillsd228": 22063,
- "Skillld228": 22064,
- "Skillan228": 22065,
- "Skillname229": 22066,
- "Skillsd229": 22067,
- "Skillld229": 22068,
- "Skillan229": 22069,
- "Skillname230": 22070,
- "Skillsd230": 22071,
- "Skillld230": 22072,
- "Skillan230": 22073,
- "Skillname231": 22074,
- "Skillsd231": 22075,
- "Skillld231": 22076,
- "Skillan231": 22077,
- "Skillname232": 22078,
- "Skillsd232": 22079,
- "Skillld232": 22080,
- "Skillan232": 22081,
- "Skillname233": 22082,
- "Skillsd233": 22083,
- "Skillld233": 22084,
- "Skillan233": 22085,
- "Skillname234": 22086,
- "Skillsd234": 22087,
- "Skillld234": 22088,
- "Skillan234": 22089,
- "Skillname235": 22090,
- "Skillsd235": 22091,
- "Skillld235": 22092,
- "Skillan235": 22093,
- "Skillname236": 22094,
- "Skillsd236": 22095,
- "Skillld236": 22096,
- "Skillan236": 22097,
- "Skillname237": 22098,
- "Skillsd237": 22099,
- "Skillld237": 22100,
- "Skillan237": 22101,
- "Skillname238": 22102,
- "Skillsd238": 22103,
- "Skillld238": 22104,
- "Skillan238": 22105,
- "Skillname239": 22106,
- "Skillsd239": 22107,
- "Skillld239": 22108,
- "Skillan239": 22109,
- "Skillname240": 22110,
- "Skillsd240": 22111,
- "Skillld240": 22112,
- "Skillan240": 22113,
- "Skillname241": 22114,
- "Skillsd241": 22115,
- "Skillld241": 22116,
- "Skillan241": 22117,
- "Skillname242": 22118,
- "Skillsd242": 22119,
- "Skillld242": 22120,
- "Skillan242": 22121,
- "Skillname243": 22122,
- "Skillsd243": 22123,
- "Skillld243": 22124,
- "Skillan243": 22125,
- "Skillname244": 22126,
- "Skillsd244": 22127,
- "Skillld244": 22128,
- "Skillan244": 22129,
- "Skillname245": 22130,
- "Skillsd245": 22131,
- "Skillld245": 22132,
- "Skillan245": 22133,
- "Skillname246": 22134,
- "Skillsd246": 22135,
- "Skillld246": 22136,
- "Skillan246": 22137,
- "Skillname247": 22138,
- "Skillsd247": 22139,
- "Skillld247": 22140,
- "Skillan247": 22141,
- "Skillname248": 22142,
- "Skillsd248": 22143,
- "Skillld248": 22144,
- "Skillan248": 22145,
- "Skillname249": 22146,
- "Skillsd249": 22147,
- "Skillld249": 22148,
- "Skillan249": 22149,
- "Skillname250": 22150,
- "Skillsd250": 22151,
- "Skillld250": 22152,
- "Skillan250": 22153,
- "Skillname251": 22154,
- "Skillsd251": 22155,
- "Skillld251": 22156,
- "Skillan251": 22157,
- "Skillname252": 22158,
- "Skillsd252": 22159,
- "Skillld252": 22160,
- "Skillan252": 22161,
- "Skillname253": 22162,
- "Skillsd253": 22163,
- "Skillld253": 22164,
- "Skillan253": 22165,
- "Skillname254": 22166,
- "Skillsd254": 22167,
- "Skillld254": 22168,
- "Skillan254": 22169,
- "Skillname255": 22170,
- "Skillsd255": 22171,
- "Skillld255": 22172,
- "Skillan255": 22173,
- "Skillname256": 22174,
- "Skillsd256": 22175,
- "Skillld256": 22176,
- "Skillan256": 22177,
- "Skillname257": 22178,
- "Skillsd257": 22179,
- "Skillld257": 22180,
- "Skillan257": 22181,
- "Skillname258": 22182,
- "Skillsd258": 22183,
- "Skillld258": 22184,
- "Skillan258": 22185,
- "Skillname259": 22186,
- "Skillsd259": 22187,
- "Skillld259": 22188,
- "Skillan259": 22189,
- "Skillname260": 22190,
- "Skillsd260": 22191,
- "Skillld260": 22192,
- "Skillan260": 22193,
- "Skillname261": 22194,
- "Skillsd261": 22195,
- "Skillld261": 22196,
- "Skillan261": 22197,
- "Skillname262": 22198,
- "Skillsd262": 22199,
- "Skillld262": 22200,
- "Skillan262": 22201,
- "Skillname263": 22202,
- "Skillsd263": 22203,
- "Skillld263": 22204,
- "Skillan263": 22205,
- "Skillname264": 22206,
- "Skillsd264": 22207,
- "Skillld264": 22208,
- "Skillan264": 22209,
- "Skillname265": 22210,
- "Skillsd265": 22211,
- "Skillld265": 22212,
- "Skillan265": 22213,
- "Skillname266": 22214,
- "Skillsd266": 22215,
- "Skillld266": 22216,
- "Skillan266": 22217,
- "Skillname267": 22218,
- "Skillsd267": 22219,
- "Skillld267": 22220,
- "Skillan267": 22221,
- "Skillname268": 22222,
- "Skillsd268": 22223,
- "Skillld268": 22224,
- "Skillan268": 22225,
- "Skillname269": 22226,
- "Skillsd269": 22227,
- "Skillld269": 22228,
- "Skillan269": 22229,
- "Skillname270": 22230,
- "Skillsd270": 22231,
- "Skillld270": 22232,
- "Skillan270": 22233,
- "Skillname271": 22234,
- "Skillsd271": 22235,
- "Skillld271": 22236,
- "Skillan271": 22237,
- "Skillname272": 22238,
- "Skillsd272": 22239,
- "Skillld272": 22240,
- "Skillan272": 22241,
- "Skillname273": 22242,
- "Skillsd273": 22243,
- "Skillld273": 22244,
- "Skillan273": 22245,
- "Skillname274": 22246,
- "Skillsd274": 22247,
- "Skillld274": 22248,
- "Skillan274": 22249,
- "Skillname275": 22250,
- "Skillsd275": 22251,
- "Skillld275": 22252,
- "Skillan275": 22253,
- "Skillname276": 22254,
- "Skillsd276": 22255,
- "Skillld276": 22256,
- "Skillan276": 22257,
- "Skillname277": 22258,
- "Skillsd277": 22259,
- "Skillld277": 22260,
- "Skillan277": 22261,
- "Skillname278": 22262,
- "Skillsd278": 22263,
- "Skillld278": 22264,
- "Skillan278": 22265,
- "Skillname279": 22266,
- "Skillsd279": 22267,
- "Skillld279": 22268,
- "Skillan279": 22269,
- "Skillname280": 22270,
- "Skillsd280": 22271,
- "Skillld280": 22272,
- "Skillan280": 22273,
- "Skillname281": 22274,
- "Skillsd281": 22275,
- "Skillld281": 22276,
- "Skillan281": 22277,
- "ESkillPerKick": 22286,
- "EskillLifeSteal": 22287,
- "Eskillchancetostun": 22288,
- "Eskillchancetoafflict": 22289,
- "Eskillpowerup1": 22290,
- "Eskillpowerup2": 22291,
- "Eskillpowerup3": 22292,
- "Eskillpowerupadd": 22293,
- "Eskillsinishup": 22294,
- "Eskillpudlife": 22295,
- "Eskillpudmana": 22296,
- "Eskillpudburning": 22297,
- "Eskillpuddgmper": 22298,
- "Eskilllowerresis": 22299,
- "Eskilltomeleeattacks": 22300,
- "EskillManaSteal": 22301,
- "Eskillferalpets": 22302,
- "Eskillpercentatt": 22303,
- "Eskillpercentlif": 22304,
- "Eskillpercentdmg": 22305,
- "Eskillfinishmove": 22306,
- "Eskillmanarecov": 22307,
- "Eskillphoenix1": 22308,
- "Eskillphoenix2": 22309,
- "Eskillphoenix3": 22310,
- "Eskillthunder1": 22311,
- "Eskillthunder2": 22312,
- "Eskillthunder3": 22313,
- "Eskillfistsoffire1": 22314,
- "Eskillfistsoffire2": 22315,
- "Eskillfistsoffire3": 22316,
- "Eskillbladesofice1": 22317,
- "Eskillbladesofice2": 22318,
- "Eskillbladesofice3": 22319,
- "strUI5": 22320,
- "strUI6": 22321,
- "strUI7": 22322,
- "strUI8": 22323,
- "strUI9": 22324,
- "strUI10": 22325,
- "strUI11": 22326,
- "strUI12": 22327,
- "strUI13": 22328,
- "strUI14": 22329,
- "UIFenirsui": 22330,
- "UiRescuedBarUI": 22331,
- "UiShadowUI": 22332,
- "StrUI18": 22333,
- "Spike Generator": 22334,
- "Charged Bolt Sentry": 22335,
- "Lightning Sentry": 22336,
- "Blade Creeper": 22337,
- "Invis Pet": 22338,
- "Druid Hawk": 22339,
- "Druid Wolf": 22340,
- "Druid Totem": 22341,
- "Druid Fenris": 22342,
- "Druid Spirit Wolf": 22343,
- "Druid Bear": 22344,
- "Druid Plague Poppy": 22345,
- "Druid Cycle of Life": 22346,
- "Vine Creature": 22347,
- "Eagleexp": 22348,
- "Wolf": 22349,
- "Bear": 22350,
- "Siege Door": 22351,
- "Siege Beast": 22358,
- "Hell Temptress": 22389,
- "Blood Temptress": 22390,
- "Blood Witch": 22394,
- "Hell Witch": 22395,
- "CatapultN": 22411,
- "CatapultS": 22412,
- "CatapultE": 22413,
- "CatapultW": 22414,
- "Frozen Horror1": 22415,
- "Frozen Horror2": 22416,
- "Frozen Horror3": 22417,
- "Frozen Horror4": 22418,
- "Frozen Horror5": 22419,
- "Blood Lord1": 22420,
- "Blood Lord2": 22421,
- "Blood Lord3": 22422,
- "Blood Lord4": 22423,
- "Blood Lord5": 22424,
- "Catapult Spotter N": 22425,
- "Catapult Spotter S": 22426,
- "Catapult Spotter E": 22427,
- "Catapult Spotter W": 22428,
- "Catapult Spotter Siege": 22429,
- "CatapultSiege": 22430,
- "Barricade Wall Right": 22431,
- "Barricade Wall Left": 22432,
- "Barricade Door": 22433,
- "Barricade Tower": 22434,
- "Siege Boss": 22435, // shenk the overseer
- "Evil hut": 22436,
- "Death Mauler1": 22437,
- "Death Mauler2": 22438,
- "Death Mauler3": 22439,
- "Death Mauler4": 22440,
- "Death Mauler5": 22441,
- "SnowYeti1": 22442,
- "SnowYeti2": 22443,
- "SnowYeti3": 22444,
- "SnowYeti4": 22445,
- "Baal Throne": 22446,
- "Baal Crab": 22447,
- "Baal Taunt": 22448,
- "Putrid Defiler1": 22449,
- "Putrid Defiler2": 22450,
- "Putrid Defiler3": 22451,
- "Putrid Defiler4": 22452,
- "Putrid Defiler5": 22453,
- "Pain Worm1": 22454,
- "Pain Worm2": 22455,
- "Pain Worm3": 22456,
- "Pain Worm4": 22457,
- "Pain Worm5": 22458,
- "WolfRider5": 22459,
- "WolfRider4": 22460,
- "WolfRider3": 22461,
- "WolfRider2": 22462,
- "WolfRider1": 22463,
- "Oak Sage": 22464,
- "Heart of Wolverine": 22465,
- "Spirit of Barbs": 22466,
- "Shadow Warrior": 22467,
- "Death Sentry": 22468,
- "Inferno Sentry": 22469,
- "Shadow Master": 22470,
- "Wake of Destruction": 22471,
- "Ghostly": 22472,
- "Fanatic": 22473,
- "Possessed": 22474,
- "Berserk": 22475,
- "Larzuk": 22476,
- "Drehya": 22477,
- "Malah": 22478,
- "Nihlathak Town": 22479,
- "Qual-Kehk": 22480,
- "Act 5 Townguard": 22481,
- "Act 5 Combatant": 22482,
- "Nihlathak": 22483,
- "POW": 22484,
- "Moe": 22485,
- "Curly": 22486,
- "Larry": 22487,
- "Ancient Barbarian 3": 22488,
- "Ancient Barbarian 2": 22489,
- "Ancient Barbarian 1": 22490,
- "Blaze Ripper": 22491,
- "Magma Torquer": 22492,
- "Sharp Tooth Sayer": 22493,
- "Vinvear Molech": 22494,
- "Anodized Elite": 22495,
- "Snapchip Shatter": 22496,
- "Pindleskin": 22497,
- "Threash Socket": 22498,
- "Eyeback Unleashed": 22499,
- "Megaflow Rectifier": 22500, // eldritch the rectifier
- "Dac Farren": 22501,
- "Bonesaw Breaker": 22502,
- "Axe Dweller": 22503,
- "Frozenstein": 22504,
- "strDruidOnly": 22505,
- "strAssassinOnly": 22506,
- "strAmazonOnly": 22507,
- "strBarbarianOnly": 22508,
- "StrSklTree26": 22509,
- "StrSklTree27": 22510,
- "StrSklTree28": 22511,
- "StrSklTree29": 22512,
- "StrSklTree30": 22513,
- "StrSklTree31": 22514,
- "StrSklTree32": 22515,
- "StrSklTree33": 22516,
- "StrSklTree34": 22517,
- "chestr": 22520,
- "barrel wilderness": 22521,
- "woodchestL": 22522,
- "burialchestL": 22523,
- "burialchestR": 22524,
- "ChestL": 22527,
- "ChestSL": 22528,
- "ChestSR": 22529,
- "woodchestR": 22530,
- "chestR": 22531,
- "burningbodies": 22532,
- "burningpit": 22533,
- "tribal flag": 22534,
- "flag widlerness": 22535,
- "eflg": 22536,
- "chan": 22537,
- "jar": 22538,
- "jar2": 22539,
- "jar3": 22540,
- "swingingheads": 22541,
- "pole": 22542,
- "animatedskullsandrocks": 22543,
- "hellgate": 22544,
- "gate": 22545,
- "banner1": 22546,
- "banner2": 22547,
- "mrpole": 22548,
- "pene": 22549,
- "debris": 22550,
- "woodchest2R": 22551,
- "woodchest2L": 22552,
- "object1": 22553,
- "magic shrine2": 22554,
- "torch2": 22555,
- "torch1": 22556,
- "tomb3": 22557,
- "tomb2": 22558,
- "tomb1": 22559,
- "ttor": 22560,
- "icecave_torch2": 22561,
- "icecave_torch1": 22562,
- "clientsmoke": 22563,
- "deadbarbarian": 22564,
- "deadbarbarian18": 22565,
- "uncle f#%* comedy central(c)\tMoe": 22566,
- "cagedwussie1": 22567,
- "icecaveshrine2": 22568,
- "icecavejar4": 22569,
- "icecavejar3": 22570,
- "icecavejar2": 22571,
- "icecavejar1": 22572,
- "evilurn": 22573,
- "secret object": 22574,
- "Altar": 22575,
- "Ldeathpole": 22576,
- "deathpole": 22577,
- "explodingchest": 22578,
- "banner 2": 22579,
- "banner 1": 22580,
- "pileofskullsandrocks": 22581,
- "animated skulland rockpile": 22582,
- "jar1": 22583,
- "etorch2": 22584,
- "ettr": 22585,
- "ecfra": 22586,
- "etorch1": 22587,
- "healthshrine": 22588,
- "explodingbarrel": 22589,
- "flag wilderness": 22590,
- "object": 22591,
- "Shrine2wilderness": 22592,
- "Shrine3wilderness": 22593,
- "pyox": 22594,
- "ptox": 22595,
- "Siege Control": 22596,
- "mrjar": 22597,
- "object2": 22598,
- "mrbox": 22599,
- "tomb3L": 22600,
- "tomb2L": 22601,
- "tomb1L": 22602,
- "red light": 22603,
- "groundtombL": 22604,
- "groundtomb": 22605,
- "deadperson": 22606,
- "candles": 22607,
- "sbub": 22608,
- "ubub": 22609,
- "deadperson2": 22610,
- "Prison Door": 22611,
- "ancientsaltar": 22612,
- "hiddenstash": 22613,
- "eweaponrackL": 22614,
- "eweaponrackR": 22615,
- "earmorstandL": 22616,
- "earmorstandR": 22617,
- "qstsa5q1": 22618,
- "qsta5q11": 22619,
- "qsta5q12": 22620,
- "qsta5q13": 22621,
- "qstsa5q2": 22622,
- "qstsa5q21": 22623,
- "qstsa5q22": 22624,
- "qstsa5q23": 22625,
- "qstsa5q24": 22626,
- "qstsa5q3": 22627,
- "qstsa5q31": 22628,
- "qstsa5q32": 22629,
- "qstsa5q33": 22630,
- "qstsa5q34": 22631,
- "qstsa5q35": 22632,
- "qstsa5q4": 22633,
- "qstsa5q41": 22634,
- "qstsa5q42": 22635,
- "qstsa5q43": 22636,
- "qstsa5q5": 22637,
- "qstsa5q51": 22638,
- "qstsa5q52": 22639,
- "qstsa5q53": 22640,
- "qstsa5q6": 22641,
- "qstsa5q61": 22642,
- "qstsa5q62": 22643,
- "qstsa5q63": 22644,
- "qstsa5q64": 22645,
- "Harrogath": 22646,
- "Bloody Foothills": 22647,
- "Rigid Highlands": 22648,
- "Arreat Plateau": 22649,
- "Crystalized Cavern Level 1": 22650,
- "Cellar of Pity": 22651,
- "Crystalized Cavern Level 2": 22652,
- "Echo Chamber": 22653,
- "Tundra Wastelands": 22654,
- "Glacial Caves Level 1": 22655,
- "Glacial Caves Level 2": 22656,
- "Rocky Summit": 22657,
- "Nihlathaks Temple": 22658,
- "Halls of Anguish": 22659,
- "Halls of Death's Calling": 22660,
- "Halls of Tormented Insanity": 22661,
- "Halls of Vaught": 22662,
- "The Worldstone Keep Level 1": 22663,
- "The Worldstone Keep Level 2": 22664,
- "The Worldstone Keep Level 3": 22665,
- "The Worldstone Chamber": 22666,
- "Throne of Destruction": 22667,
- "To Harrogath": 22668,
- "To The Bloody Foothills": 22669,
- "To The Rigid Highlands": 22670,
- "To The Arreat Plateau": 22671,
- "To The Crystalized Cavern Level 1": 22672,
- "To The Cellar of Pity": 22673,
- "To The Crystalized Cavern Level 2": 22674,
- "To The Echo Chamber": 22675,
- "To The Tundra Wastelands": 22676,
- "To The Glacier Caves Level 1": 22677,
- "To The Glacier Caves Level 2": 22678,
- "To The Rocky Summit": 22679,
- "To Nihlathaks Temple": 22680,
- "To The Halls of Anguish": 22681,
- "To The Halls of Death's Calling": 22682,
- "To The Halls of Tormented Insanity": 22683,
- "To The Halls of Vaught": 22684,
- "To The Worldstone Keep Level 1": 22685,
- "To The Worldstone Keep Level 2": 22686,
- "To The Worldstone Keep Level 3": 22687,
- "To The Worldstone Chamber": 22688,
- "To The Throne of Destruction": 22689,
- "hireiconinfo1": 22690,
- "hireiconinfo2": 22691,
- "hiredismiss": 22692,
- "hiredismisshire": 22693,
- "hirerehire": 22694,
- "hireresurrect": 22695,
- "hireresurrect2": 22696,
- "hirechat1": 22697,
- "hirechat2": 22698,
- "hirechat3": 22699,
- "hirepraise1": 22700,
- "hirepraise2": 22701,
- "hiredanger1": 22702,
- "hiredanger2": 22703,
- "hiredanger3": 22704,
- "hiredanger4": 22705,
- "hiredanger5": 22706,
- "hiredanger6": 22707,
- "hirefeelstronger2": 22708,
- "hirehelp1": 22709,
- "hirehelp2": 22710,
- "hirehelp3": 22711,
- "hirehelp4": 22712,
- "hiregreets1": 22713,
- "hiregreets2": 22714,
- "hiregreets3": 22715,
- "hiregreets4": 22716,
- "CfgSkill9": 22717,
- "CfgSkill10": 22718,
- "CfgSkill11": 22719,
- "CfgSkill12": 22720,
- "CfgSkill13": 22721,
- "CfgSkill14": 22722,
- "CfgSkill15": 22723,
- "CfgSkill16": 22724,
- "CfgToggleminimap": 22725,
- "Cfgswapweapons": 22726,
- "Cfghireling": 22727,
- "MiniPanelHireinv": 22728,
- "MiniPanelHire": 22729,
- "Go North": 22737,
- "Travel To Harrogath": 22738,
- "Rename Instruct": 22747,
- "Addsocketsui": 22748,
- "Personalizeui": 22749,
- "Addsocketsui2": 22750,
- "MercX101": 22751,
- "MercX102": 22752,
- "MercX103": 22753,
- "MercX104": 22754,
- "MercX105": 22755,
- "MercX106": 22756,
- "MercX107": 22757,
- "MercX108": 22758,
- "MercX109": 22759,
- "MercX110": 22760,
- "MercX111": 22761,
- "MercX112": 22762,
- "MercX113": 22763,
- "MercX114": 22764,
- "MercX115": 22765,
- "MercX116": 22766,
- "MercX117": 22767,
- "MercX118": 22768,
- "MercX119": 22769,
- "MercX120": 22770,
- "MercX121": 22771,
- "MercX122": 22772,
- "MercX123": 22773,
- "MercX124": 22774,
- "MercX125": 22775,
- "MercX126": 22776,
- "MercX127": 22777,
- "MercX128": 22778,
- "MercX129": 22779,
- "MercX130": 22780,
- "MercX131": 22781,
- "MercX132": 22782,
- "MercX133": 22783,
- "MercX134": 22784,
- "MercX135": 22785,
- "MercX136": 22786,
- "MercX137": 22787,
- "MercX138": 22788,
- "MercX139": 22789,
- "MercX140": 22790,
- "MercX141": 22791,
- "MercX142": 22792,
- "MercX143": 22793,
- "MercX144": 22794,
- "MercX145": 22795,
- "MercX146": 22796,
- "MercX147": 22797,
- "MercX148": 22798,
- "MercX149": 22799,
- "MercX150": 22800,
- "MercX151": 22801,
- "MercX152": 22802,
- "MercX153": 22803,
- "MercX154": 22804,
- "MercX155": 22805,
- "MercX156": 22806,
- "MercX157": 22807,
- "MercX158": 22808,
- "MercX159": 22809,
- "MercX160": 22810,
- "MercX161": 22811,
- "MercX162": 22812,
- "MercX163": 22813,
- "MercX164": 22814,
- "MercX165": 22815,
- "MercX166": 22816,
- "MercX167": 22817
- };
-
- let LocaleStringName = {};
-
- for (let k in LocaleStringID) {
- LocaleStringName[LocaleStringID[k]] = k;
- }
-
- module.exports = {
- LocaleStringName: LocaleStringName,
- LocaleStringID: LocaleStringID
- };
-})(module, require);
diff --git a/libs/SoloPlay/Modules/MercLib.js b/libs/SoloPlay/Modules/MercLib.js
index 4a2ccb60..623ed821 100644
--- a/libs/SoloPlay/Modules/MercLib.js
+++ b/libs/SoloPlay/Modules/MercLib.js
@@ -14,331 +14,332 @@
* + proper modern classes
*/
(function (factory) {
- if (typeof module === "object" && typeof module.exports === "object") {
- let v = factory(require, exports);
- if (v !== undefined) module.exports = v;
- } else if (typeof define === "function" && define.amd) {
- define(["require", "exports", "./bigInt"], factory);
- }
+ if (typeof module === "object" && typeof module.exports === "object") {
+ let v = factory(require, exports);
+ if (v !== undefined) module.exports = v;
+ } else if (typeof define === "function" && define.amd) {
+ define(["require", "exports", "./bigInt"], factory);
+ }
})(function (require, exports) {
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.mercPacket = exports.Merc = exports.Rnd = void 0;
- let bigInt_1 = require("./bigInt");
- /// The actual Merc.js code of dzik
- function leftShift(val, shift) {
- let l;
- while (shift >= bigInt_1.bpe) {
- l = Math.ceil((bigInt_1.bitSize(val) + bigInt_1.bpe - 1) / bigInt_1.bpe) + 1;
- if (val.length < l) {
- val = bigInt_1.expand(val, l);
- }
- bigInt_1.leftShift_(val, bigInt_1.bpe - 1);
- shift -= bigInt_1.bpe - 1;
- }
- if (shift > 0) {
- l = Math.ceil((bigInt_1.bitSize(val) + bigInt_1.bpe - 1) / bigInt_1.bpe) + 1;
- if (val.length < l) {
- val = bigInt_1.expand(val, l);
- }
- bigInt_1.leftShift_(val, shift);
- }
- return val;
- }
- function rightShift(val, shift) {
- while (shift >= bigInt_1.bpe) {
- bigInt_1.rightShift_(val, bigInt_1.bpe - 1);
- shift -= bigInt_1.bpe - 1;
- }
- if (shift > 0) {
- bigInt_1.rightShift_(val, shift);
- }
- }
- let Rnd = /** @class */ (function () {
- function Rnd(seed) {
- let tmp = bigInt_1.int2bigInt(666, 16, 0);
- tmp = leftShift(tmp, 32);
- this.val = bigInt_1.add(tmp, bigInt_1.str2bigInt(seed.toString(16), 16, 33));
- }
- Rnd.prototype.roll = function () {
- let tmp = bigInt_1.modInt(this.val, 0x100000000);
- let tmp2 = bigInt_1.mult(bigInt_1.str2bigInt(tmp.toString(16), 16, 33), bigInt_1.int2bigInt(1791398085, 33, 0));
- rightShift(this.val, 32);
- let res = bigInt_1.add(tmp2, this.val);
- let rescopy = bigInt_1.dup(res);
- rightShift(rescopy, 64);
- res = bigInt_1.sub(res, rescopy);
- this.val = res;
- };
- Rnd.prototype.get = function () {
- return bigInt_1.modInt(this.val, 0x100000000);
- };
- return Rnd;
- }());
- exports.Rnd = Rnd;
- let Merc = /** @class */ (function () {
- function Merc(name, seed) {
- this.id = name;
- this.name = getLocaleString(name);
- const getTable = function () {
- let list = [];
- let level = 0;
- let act = checkActByName(name);
- for (let i = 0; i < MercTable.length; i++) {
- if (MercTable[i][5] == act && MercTable[i][6] == me.diff + 1 && MercTable[i][2] == me.gametype * 100) {
- if (level == 0 || MercTable[i][7] == level) {
- list.push(MercTable[i]);
- level = MercTable[i][7];
- }
- }
- }
- return list;
- };
- const getMercInfo = function (index, infoindex) {
- if (validMercTable[index].hasOwnProperty(infoindex)) {
- return validMercTable[index][infoindex];
- }
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.mercPacket = exports.Merc = exports.Rnd = void 0;
+ let bigInt_1 = require("./bigInt");
+ /// The actual Merc.js code of dzik
+ function leftShift(val, shift) {
+ let l;
+ while (shift >= bigInt_1.bpe) {
+ l = Math.ceil((bigInt_1.bitSize(val) + bigInt_1.bpe - 1) / bigInt_1.bpe) + 1;
+ if (val.length < l) {
+ val = bigInt_1.expand(val, l);
+ }
+ bigInt_1.leftShift_(val, bigInt_1.bpe - 1);
+ shift -= bigInt_1.bpe - 1;
+ }
+ if (shift > 0) {
+ l = Math.ceil((bigInt_1.bitSize(val) + bigInt_1.bpe - 1) / bigInt_1.bpe) + 1;
+ if (val.length < l) {
+ val = bigInt_1.expand(val, l);
+ }
+ bigInt_1.leftShift_(val, shift);
+ }
+ return val;
+ }
+ function rightShift(val, shift) {
+ while (shift >= bigInt_1.bpe) {
+ bigInt_1.rightShift_(val, bigInt_1.bpe - 1);
+ shift -= bigInt_1.bpe - 1;
+ }
+ if (shift > 0) {
+ bigInt_1.rightShift_(val, shift);
+ }
+ }
+ let Rnd = /** @class */ (function () {
+ function Rnd(seed) {
+ let tmp = bigInt_1.int2bigInt(666, 16, 0);
+ tmp = leftShift(tmp, 32);
+ this.val = bigInt_1.add(tmp, bigInt_1.str2bigInt(seed.toString(16), 16, 33));
+ }
+ Rnd.prototype.roll = function () {
+ let tmp = bigInt_1.modInt(this.val, 0x100000000);
+ let tmp2 = bigInt_1.mult(bigInt_1.str2bigInt(tmp.toString(16), 16, 33), bigInt_1.int2bigInt(1791398085, 33, 0));
+ rightShift(this.val, 32);
+ let res = bigInt_1.add(tmp2, this.val);
+ let rescopy = bigInt_1.dup(res);
+ rightShift(rescopy, 64);
+ res = bigInt_1.sub(res, rescopy);
+ this.val = res;
+ };
+ Rnd.prototype.get = function () {
+ return bigInt_1.modInt(this.val, 0x100000000);
+ };
+ return Rnd;
+ }());
+ exports.Rnd = Rnd;
+ let Merc = /** @class */ (function () {
+ function Merc(name, seed) {
+ this.id = name;
+ this.name = getLocaleString(name);
+ const getTable = function () {
+ let list = [];
+ let level = 0;
+ let act = checkActByName(name);
+ for (let i = 0; i < MercTable.length; i++) {
+ if (MercTable[i][5] == act && MercTable[i][6] == me.diff + 1 && MercTable[i][2] == me.gametype * 100) {
+ if (level == 0 || MercTable[i][7] == level) {
+ list.push(MercTable[i]);
+ level = MercTable[i][7];
+ }
+ }
+ }
+ return list;
+ };
+ const getMercInfo = function (index, infoindex) {
+ if (validMercTable[index].hasOwnProperty(infoindex)) {
+ return validMercTable[index][infoindex];
+ }
- return false;
- };
- const checkActByName = function (name) {
- if (name >= 3411 && name <= 3451) {
- return 1;
+ return false;
+ };
+ const checkActByName = function (name) {
+ if (name >= 3411 && name <= 3451) {
+ return 1;
- } else if (name >= 1019 && name <= 1039) {
- return 2;
- } else if (name >= 1040 && name <= 1059) {
- return 3;
- } else if (name >= 10835 && name <= 10901) {
- return 5;
- } else {
- return false;
- }
- };
- const validMercTable = getTable();
- let newseed = new Rnd(seed);
- newseed.roll();
- let index = (newseed.get()) % validMercTable.length;
- newseed.roll();
- this.level = (newseed.get()) % 5 + me.charlvl - 5;
- let lvl = this.level;
- let difference = this.level - getMercInfo(index, 7);
- this.hireling = getMercInfo(index, 0);
- this.typeid = getMercInfo(index, 3);
- this.subtype = getMercInfo(index, 1);
- this.skills = [];
- if (getMercInfo(index, 33) !== undefined) {
- this.skills.push({
- name: getMercInfo(index, 33),
- lvl: Math.floor(lvl * 10 / 32) // experimental
- });
- }
- if (getMercInfo(index, 39) !== undefined) {
- this.skills.push({
- name: getMercInfo(index, 39),
- lvl: Math.floor(lvl * 10 / 32) // experimental
- });
- }
- if (getMercInfo(index, 45) !== undefined) {
- this.skills.push({
- name: getMercInfo(index, 45),
- lvl: Math.floor(lvl * 10 / 32) // experimental
- });
- }
- this.cost = getMercInfo(index, 11) * (15 * difference + 100) / 100;
- if (this.cost < getMercInfo(index, 11)) {
- this.cost = getMercInfo(index, 11);
- }
- this.cost = Math.floor(this.cost);
- this.hp = getMercInfo(index, 13) + getMercInfo(index, 14) * difference;
- if (this.hp < 40) {
- this.hp = 40;
- }
- this.defense = getMercInfo(index, 15) + getMercInfo(index, 16) * difference;
- if (this.defense < 0) {
- this.defense = 0;
- }
- this.strength = getMercInfo(index, 17) + getMercInfo(index, 18) * difference;
- if (this.strength < 10) {
- this.strength = 10;
- }
- this.dexterity = getMercInfo(index, 19) + getMercInfo(index, 20) * difference;
- if (this.dexterity < 10) {
- this.dexterity = 10;
- }
- this.experience = this.level * this.level * (this.level + 1) * getMercInfo(index, 12);
- if (this.experience < 0) {
- this.experience = 0;
- }
- this.attackrating = getMercInfo(index, 21) + getMercInfo(index, 22) * difference;
- if (this.attackrating < 1) {
- this.attackrating = 1;
- }
- this.dmg_min = getMercInfo(index, 24) + getMercInfo(index, 26) * difference;
- if (this.dmg_min < 0) {
- this.dmg_min = 0;
- }
- this.dmg_max = getMercInfo(index, 25) + getMercInfo(index, 26) * difference;
- if (this.dmg_max < 1) {
- this.dmg_max = 1;
- }
- this.resists = getMercInfo(index, 27) + getMercInfo(index, 28) * difference;
- if (this.resists < 1) {
- this.resists = 1;
- }
- }
- Merc.prototype.hire = function () {
- let npc = getInteractedNPC();
- if (!npc) throw new Error("To buy merc you need to interact with npc first");
- if (me.gold < this.cost) throw new Error("Merc is too expensive to buy.");
- let before = me.gold;
- while (before === me.gold) {
- sendPacket(1, sdk.packets.send.HireMerc, 4, npc.gid, 4, this.id);
- delay((me.ping || 1) * 5);
- }
- return true;
- };
- return Merc;
- }());
- exports.Merc = Merc;
- let Mercs = [];
- exports.default = Mercs;
- function mercPacket(pByte) {
- switch (pByte[0]) {
- case 0x4f:
- // Clear mercenary list
- Mercs.splice(0, Mercs.length);
- break;
- case 0x4e:
- let name = ((pByte[1]) | (pByte[2] << 8)), seed = ((pByte[3]) | (pByte[4] << 8) | (pByte[5] << 16) | (pByte[6] << 24)) >>> 0;
- Mercs.push(new Merc(name, seed));
- break;
- }
- }
- exports.mercPacket = mercPacket;
- //addEventListener("gamepacket", mercPacket);
- //removeEventListener("gamepacket", mercPacket);
- const MercTable = [
- // 0.Hireling , 1.SubType , 2.Version , 3.Id , 4.Class , 5.Act , 6.Diff , 7.Level , 8.Seller , 9.NameFirst , 10.NameLast , 11.Gold , 12.Exp/Lvl , 13.HP , 14.HP/Lvl , 15.Def , 16.Def/Lvl , Str , Str/Lvl , Dex , Dex/Lvl , AR , AR/Lvl , Share , Dmg-Min , Dmg-Max , Dmg/Lvl , Resist , Resist/Lvl , WType1 , WType2 , HireDesc , DefaultChance , Skill1 , Mode1 , Chance1 , ChancePerLvl1 , Level1 , LvlPerLvl1 , Skill2 , Mode2 , Chance2 , ChancePerLvl2 , Level2 , LvlPerLvl2 , Skill3 , Mode3 , Chance3 , ChancePerLvl3 , Level3 , LvlPerLvl3 , Skill4 , Mode4 , Chance4 , ChancePerLvl4 , Level4 , LvlPerLvl4 , Skill5 , Mode5 , Chance5 , ChancePerLvl5 , Level5 , LvlPerLvl5 , Skill6 , Mode6 , Chance6 , ChancePerLvl6 , Level6 , LvlPerLvl6 , Head , Torso , Weapon , Shield
- ["Rogue Scout", "Fire - Normal", 0, 0, 271, 1, 1, 3, 150, "merc01", "merc41", 100, 100, 45, 8, 15, 6, 35, 10, 45, 16, 10, 10, , 1, 3, 2, 0, 8, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 1, 10, "Fire Arrow", 4, 25, 8, 1, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Normal", 0, 0, 271, 1, 1, 25, 150, "merc01", "merc41", 100, 100, 221, 13, 147, 12, 63, 10, 89, 16, 230, 20, 1, 7, 9, 4, 44, 7, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 8, 10, "Fire Arrow", 4, 69, 0, 8, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Normal", 0, 0, 271, 1, 1, 49, 150, "merc01", "merc41", 100, 100, 533, 25, 435, 18, 93, 10, 137, 16, 710, 30, 2, 19, 21, 6, 86, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 16, 10, "Fire Arrow", 4, 69, 0, 16, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Normal", 0, 1, 271, 1, 1, 3, 150, "merc01", "merc41", 150, 105, 45, 8, 15, 6, 35, 10, 45, 16, 10, 10, , 1, 3, 2, 0, 8, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 1, 10, "Cold Arrow", 4, 25, 2, 1, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Normal", 0, 1, 271, 1, 1, 25, 150, "merc01", "merc41", 150, 105, 221, 12, 147, 12, 63, 10, 89, 16, 230, 20, 1, 7, 9, 4, 44, 7, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 8, 10, "Cold Arrow", 4, 36, 0, 8, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Normal", 0, 1, 271, 1, 1, 49, 150, "merc01", "merc41", 150, 105, 509, 16, 435, 18, 93, 10, 137, 16, 710, 30, 2, 19, 21, 6, 86, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 16, 10, "Cold Arrow", 4, 36, 0, 16, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Nightmare", 0, 2, 271, 1, 2, 25, 150, "merc01", "merc41", 6000, 110, 199, 12, 132, 12, 60, 10, 84, 16, 207, 20, 1, 6, 8, 4, 41, 7, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 7, 10, "Fire Arrow", 4, 69, 0, 7, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Nightmare", 0, 2, 271, 1, 2, 49, 150, "merc01", "merc41", 6000, 110, 487, 16, 420, 18, 90, 10, 132, 16, 687, 30, 2, 18, 20, 6, 83, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 15, 10, "Fire Arrow", 4, 69, 0, 15, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Nightmare", 0, 3, 271, 1, 2, 25, 150, "merc01", "merc41", 7500, 115, 199, 12, 132, 12, 60, 10, 84, 16, 207, 20, 1, 6, 8, 4, 41, 7, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 7, 10, "Cold Arrow", 4, 69, 0, 7, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Nightmare", 0, 3, 271, 1, 2, 49, 150, "merc01", "merc41", 7500, 115, 487, 16, 420, 18, 90, 10, 132, 16, 687, 30, 2, 18, 20, 6, 83, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 15, 10, "Cold Arrow", 4, 69, 0, 15, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Hell", 0, 4, 271, 1, 3, 49, 150, "merc01", "merc41", 12500, 120, 438, 16, 378, 18, 87, 10, 127, 16, 618, 30, 2, 17, 19, 6, 80, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 14, 10, "Fire Arrow", 4, 69, 0, 14, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Hell", 0, 5, 271, 1, 3, 49, 150, "merc01", "merc41", 14000, 125, 438, 16, 378, 18, 87, 10, 127, 16, 618, 30, 2, 17, 19, 6, 80, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 14, 10, "Cold Arrow", 4, 69, 0, 14, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Normal", 100, 0, 271, 1, 1, 3, 150, "merc01", "merc41", 100, 100, 45, 9, 15, 8, 35, 10, 45, 16, 10, 12, , 1, 3, 2, 0, 8, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 1, 10, "Fire Arrow", 4, 25, 5, 1, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Normal", 100, 0, 271, 1, 1, 36, 150, "merc01", "merc41", 100, 100, 342, 18, 279, 15, 77, 10, 111, 16, 406, 24, 1, 9, 11, 4, 66, 7, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 12, 10, "Fire Arrow", 4, 67, 0, 12, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Normal", 100, 0, 271, 1, 1, 67, 150, "merc01", "merc41", 100, 100, 900, 30, 744, 22, 116, 10, 173, 16, 1150, 36, 2, 25, 27, 6, 121, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 22, 10, "Fire Arrow", 4, 67, 0, 22, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Normal", 100, 1, 271, 1, 1, 3, 150, "merc01", "merc41", 150, 105, 45, 9, 15, 8, 35, 10, 45, 16, 10, 12, , 1, 3, 2, 0, 8, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 1, 10, "Cold Arrow", 4, 25, 5, 1, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Normal", 100, 1, 271, 1, 1, 36, 150, "merc01", "merc41", 150, 105, 342, 18, 279, 15, 77, 10, 111, 16, 406, 24, 1, 9, 11, 4, 66, 7, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 12, 10, "Cold Arrow", 4, 67, 0, 12, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Normal", 100, 1, 271, 1, 1, 67, 150, "merc01", "merc41", 150, 105, 900, 30, 744, 22, 116, 10, 173, 16, 1150, 36, 2, 25, 27, 6, 121, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 22, 10, "Cold Arrow", 4, 67, 0, 22, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Nightmare", 100, 2, 271, 1, 2, 36, 150, "merc01", "merc41", 6000, 110, 308, 18, 251, 15, 74, 10, 106, 16, 365, 24, 1, 8, 10, 4, 63, 7, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 11, 10, "Fire Arrow", 4, 69, 0, 11, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Nightmare", 100, 2, 271, 1, 2, 67, 150, "merc01", "merc41", 6000, 110, 866, 30, 716, 22, 113, 10, 168, 16, 1109, 36, 2, 24, 26, 6, 118, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 21, 10, "Fire Arrow", 4, 69, 0, 21, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Nightmare", 100, 3, 271, 1, 2, 36, 150, "merc01", "merc41", 7500, 115, 308, 18, 251, 15, 74, 10, 106, 16, 365, 24, 1, 8, 10, 4, 63, 7, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 11, 10, "Cold Arrow", 4, 69, 0, 11, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Nightmare", 100, 3, 271, 1, 2, 67, 150, "merc01", "merc41", 7500, 115, 866, 30, 716, 22, 113, 10, 168, 16, 1109, 36, 2, 24, 26, 6, 118, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 21, 10, "Cold Arrow", 4, 69, 0, 21, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Fire - Hell", 100, 4, 271, 1, 3, 67, 150, "merc01", "merc41", 12500, 120, 779, 30, 644, 22, 110, 10, 163, 16, 998, 36, 2, 23, 25, 6, 115, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 20, 10, "Fire Arrow", 4, 69, 0, 20, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Rogue Scout", "Ice - Hell", 100, 5, 271, 1, 3, 67, 150, "merc01", "merc41", 14000, 125, 779, 30, 644, 22, 110, 10, 163, 16, 998, 36, 2, 23, 25, 6, 115, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 20, 10, "Cold Arrow", 4, 69, 0, 20, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
- ["Desert Mercenary", "Comb-Normal", 0, 6, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 10, 45, 9, 57, 14, 40, 12, 20, 10, 1, 7, 14, 4, 18, 8, "pole", "spea", "comb", 30, "Jab", 14, 70, 4, 3, 10, "Prayer", 1, 10, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Normal", 0, 6, 338, 2, 1, 31, 198, "merca201", "merca221", 350, 110, 340, 20, 243, 16, 96, 14, 73, 12, 240, 20, 3, 18, 25, 6, 62, 7, "pole", "spea", "comb", 30, "Jab", 14, 92, 4, 10, 10, "Prayer", 1, 10, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Normal", 0, 6, 338, 2, 1, 55, 198, "merca201", "merca221", 350, 110, 820, 35, 627, 24, 138, 14, 109, 12, 720, 30, 4, 36, 43, 8, 104, 4, "pole", "spea", "comb", 30, "Jab", 14, 116, 4, 18, 10, "Prayer", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Normal", 0, 7, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 10, 45, 9, 57, 14, 40, 12, 20, 10, 1, 7, 14, 4, 18, 8, "pole", "spea", "def", 30, "Jab", 14, 70, 4, 3, 10, "Defiance", 1, 10, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Normal", 0, 7, 338, 2, 1, 31, 198, "merca201", "merca221", 350, 110, 340, 20, 243, 16, 96, 14, 73, 12, 240, 20, 3, 18, 25, 6, 62, 7, "pole", "spea", "def", 30, "Jab", 14, 92, 4, 10, 10, "Defiance", 1, 10, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Normal", 0, 7, 338, 2, 1, 55, 198, "merca201", "merca221", 350, 110, 820, 35, 627, 24, 138, 14, 109, 12, 720, 30, 4, 36, 43, 8, 104, 4, "pole", "spea", "def", 30, "Jab", 14, 116, 4, 18, 0, "Defiance", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Normal", 0, 8, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 10, 45, 9, 57, 14, 40, 12, 20, 10, 1, 7, 14, 4, 18, 8, "pole", "spea", "off", 30, "Jab", 14, 70, 4, 3, 10, "Blessed Aim", 1, 10, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Normal", 0, 8, 338, 2, 1, 31, 198, "merca201", "merca221", 350, 110, 340, 20, 243, 16, 96, 14, 73, 12, 240, 20, 3, 18, 25, 6, 62, 7, "pole", "spea", "off", 30, "Jab", 14, 92, 4, 10, 10, "Blessed Aim", 1, 10, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Normal", 0, 8, 338, 2, 1, 55, 198, "merca201", "merca221", 350, 110, 820, 35, 627, 24, 138, 14, 109, 12, 720, 30, 4, 36, 43, 8, 104, 4, "pole", "spea", "off", 30, "Jab", 14, 116, 4, 18, 0, "Blessed Aim", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Nightmare", 0, 9, 338, 2, 2, 31, 198, "merca201", "merca221", 7900, 120, 306, 20, 219, 16, 92, 14, 69, 12, 216, 20, 3, 16, 23, 6, 59, 7, "pole", "spea", "comb", 30, "Jab", 14, 120, 4, 9, 10, "Thorns", 1, 10, 0, 5, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Nightmare", 0, 9, 338, 2, 2, 55, 198, "merca201", "merca221", 7900, 120, 786, 35, 603, 24, 134, 14, 105, 12, 696, 30, 4, 34, 41, 8, 101, 4, "pole", "spea", "comb", 30, "Jab", 14, 144, 4, 17, 10, "Thorns", 1, 10, 0, 13, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Nightmare", 0, 10, 338, 2, 2, 31, 198, "merca201", "merca221", 7900, 120, 306, 20, 219, 16, 92, 14, 69, 12, 216, 20, 3, 16, 23, 6, 59, 7, "pole", "spea", "def", 30, "Jab", 14, 120, 4, 9, 10, "Holy Freeze", 1, 10, 0, 6, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Nightmare", 0, 10, 338, 2, 2, 55, 198, "merca201", "merca221", 7900, 120, 786, 35, 603, 24, 134, 14, 105, 12, 696, 30, 4, 34, 41, 8, 101, 4, "pole", "spea", "def", 30, "Jab", 14, 144, 4, 17, 0, "Holy Freeze", 1, 10, 0, 14, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Nightmare", 0, 11, 338, 2, 2, 31, 198, "merca201", "merca221", 7900, 120, 306, 20, 219, 16, 92, 14, 69, 12, 216, 20, 3, 16, 23, 6, 59, 7, "pole", "spea", "off", 30, "Jab", 14, 120, 4, 9, 10, "Might", 1, 10, 0, 7, 8, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Nightmare", 0, 11, 338, 2, 2, 55, 198, "merca201", "merca221", 7900, 120, 786, 35, 603, 24, 134, 14, 105, 12, 696, 30, 4, 34, 41, 8, 101, 4, "pole", "spea", "off", 30, "Jab", 14, 144, 4, 17, 0, "Might", 1, 10, 0, 13, 8, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Hell", 0, 12, 338, 2, 3, 55, 198, "merca201", "merca221", 15000, 130, 707, 35, 543, 24, 130, 14, 101, 12, 626, 30, 4, 32, 39, 8, 98, 4, "pole", "spea", "comb", 30, "Jab", 14, 104, 4, 16, 10, "Prayer", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Hell", 0, 13, 338, 2, 3, 55, 198, "merca201", "merca221", 15000, 130, 707, 35, 543, 24, 130, 14, 101, 12, 626, 30, 4, 32, 39, 8, 98, 4, "pole", "spea", "def", 30, "Jab", 14, 104, 4, 16, 0, "Defiance", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Hell", 0, 14, 338, 2, 3, 55, 198, "merca201", "merca221", 15000, 130, 707, 35, 543, 24, 130, 14, 101, 12, 626, 30, 4, 32, 39, 8, 98, 4, "pole", "spea", "off", 30, "Jab", 14, 104, 4, 16, 0, "Blessed Aim", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Normal", 100, 6, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 15, 45, 11, 57, 14, 40, 12, 20, 12, 1, 7, 14, 4, 18, 8, "pole", "spea", "comb", 30, "Jab", 14, 70, 4, 3, 10, "Prayer", 1, 10, 0, 3, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Normal", 100, 6, 338, 2, 1, 43, 198, "merca201", "merca221", 350, 110, 630, 25, 419, 19, 117, 14, 91, 12, 428, 24, 3, 24, 31, 6, 86, 7, "pole", "spea", "comb", 30, "Jab", 14, 104, 4, 14, 10, "Prayer", 1, 10, 0, 11, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Normal", 100, 6, 338, 2, 1, 75, 198, "merca201", "merca221", 350, 110, 1430, 40, 1027, 28, 173, 14, 139, 12, 1196, 36, 4, 48, 55, 8, 142, 4, "pole", "spea", "comb", 30, "Jab", 14, 136, 4, 24, 10, "Prayer", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Normal", 100, 7, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 15, 45, 11, 57, 14, 40, 12, 20, 12, 1, 7, 14, 4, 18, 8, "pole", "spea", "def", 30, "Jab", 14, 70, 4, 3, 10, "Defiance", 1, 10, 0, 3, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Normal", 100, 7, 338, 2, 1, 43, 198, "merca201", "merca221", 350, 110, 630, 25, 419, 19, 117, 14, 91, 12, 428, 24, 3, 24, 31, 6, 86, 7, "pole", "spea", "def", 30, "Jab", 14, 104, 4, 14, 10, "Defiance", 1, 10, 0, 11, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Normal", 100, 7, 338, 2, 1, 75, 198, "merca201", "merca221", 350, 110, 1430, 40, 1027, 28, 173, 14, 139, 12, 1196, 36, 4, 48, 55, 8, 142, 4, "pole", "spea", "def", 30, "Jab", 14, 136, 4, 24, 0, "Defiance", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Normal", 100, 8, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 15, 45, 11, 57, 14, 40, 12, 20, 12, 1, 7, 14, 4, 18, 8, "pole", "spea", "off", 30, "Jab", 14, 70, 4, 3, 10, "Blessed Aim", 1, 10, 0, 3, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Normal", 100, 8, 338, 2, 1, 43, 198, "merca201", "merca221", 350, 110, 630, 25, 419, 19, 117, 14, 91, 12, 428, 24, 3, 24, 31, 6, 86, 7, "pole", "spea", "off", 30, "Jab", 14, 104, 4, 14, 10, "Blessed Aim", 1, 10, 0, 11, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Normal", 100, 8, 338, 2, 1, 75, 198, "merca201", "merca221", 350, 110, 1430, 40, 1027, 28, 173, 14, 139, 12, 1196, 36, 4, 48, 55, 8, 142, 4, "pole", "spea", "off", 30, "Jab", 14, 136, 4, 24, 0, "Blessed Aim", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Nightmare", 100, 9, 338, 2, 2, 43, 198, "merca201", "merca221", 7900, 120, 567, 25, 377, 19, 113, 14, 87, 12, 385, 24, 3, 22, 29, 6, 83, 7, "pole", "spea", "comb", 30, "Jab", 14, 120, 4, 13, 10, "Thorns", 1, 10, 0, 5, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Nightmare", 100, 9, 338, 2, 2, 75, 198, "merca201", "merca221", 7900, 120, 1367, 40, 985, 28, 169, 14, 135, 12, 1153, 36, 4, 46, 53, 8, 139, 4, "pole", "spea", "comb", 30, "Jab", 14, 152, 4, 23, 10, "Thorns", 1, 10, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Nightmare", 100, 10, 338, 2, 2, 43, 198, "merca201", "merca221", 7900, 120, 567, 25, 377, 19, 113, 14, 87, 12, 385, 24, 3, 22, 29, 6, 83, 7, "pole", "spea", "def", 30, "Jab", 14, 120, 4, 13, 10, "Holy Freeze", 1, 10, 0, 6, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Nightmare", 100, 10, 338, 2, 2, 75, 198, "merca201", "merca221", 7900, 120, 1367, 40, 985, 28, 169, 14, 135, 12, 1153, 36, 4, 46, 53, 8, 139, 4, "pole", "spea", "def", 30, "Jab", 14, 152, 4, 23, 0, "Holy Freeze", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Nightmare", 100, 11, 338, 2, 2, 43, 198, "merca201", "merca221", 7900, 120, 567, 25, 377, 19, 113, 14, 87, 12, 385, 24, 3, 22, 29, 6, 83, 7, "pole", "spea", "off", 30, "Jab", 14, 120, 4, 13, 10, "Might", 1, 10, 0, 7, 8, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Nightmare", 100, 11, 338, 2, 2, 75, 198, "merca201", "merca221", 7900, 120, 1367, 40, 985, 28, 169, 14, 135, 12, 1153, 36, 4, 46, 53, 8, 139, 4, "pole", "spea", "off", 30, "Jab", 14, 152, 4, 23, 0, "Might", 1, 10, 0, 15, 8, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Comb-Hell", 100, 12, 338, 2, 3, 75, 198, "merca201", "merca221", 15000, 130, 1230, 40, 887, 28, 165, 14, 131, 12, 1038, 36, 4, 44, 51, 8, 136, 4, "pole", "spea", "comb", 30, "Jab", 14, 104, 4, 22, 10, "Prayer", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Def-Hell", 100, 13, 338, 2, 3, 75, 198, "merca201", "merca221", 15000, 130, 1230, 40, 887, 28, 165, 14, 131, 12, 1038, 36, 4, 44, 51, 8, 136, 4, "pole", "spea", "def", 30, "Jab", 14, 104, 4, 22, 0, "Defiance", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Desert Mercenary", "Off-Hell", 100, 14, 338, 2, 3, 75, 198, "merca201", "merca221", 15000, 130, 1230, 40, 887, 28, 165, 14, 131, 12, 1038, 36, 4, 44, 51, 8, 136, 4, "pole", "spea", "off", 30, "Jab", 14, 104, 4, 22, 0, "Blessed Aim", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
- ["Eastern Sorceror", "Fire-Normal", 0, 15, 359, 3, 1, 15, 252, "merca222", "merca241", 1000, 110, 160, 8, 80, 4, 49, 10, 40, 8, 15, 10, , 1, 7, 4, 25, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 6, 10, "Fire Ball", 7, 30, 0, 4, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Normal", 0, 15, 359, 3, 1, 37, 252, "merca222", "merca241", 1000, 110, 336, 14, 168, 10, 77, 10, 62, 8, 235, 20, 3, 12, 18, 4, 64, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 13, 10, "Fire Ball", 7, 30, 0, 11, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Normal", 0, 15, 359, 3, 1, 61, 252, "merca222", "merca241", 1000, 110, 672, 20, 408, 18, 107, 10, 86, 8, 715, 30, 5, 24, 30, 4, 106, 6, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 21, 10, "Fire Ball", 7, 30, 0, 19, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Normal", 0, 16, 359, 3, 1, 15, 252, "merca222", "merca241", 1500, 120, 160, 8, 80, 4, 49, 10, 40, 8, 15, 10, , 1, 7, 4, 25, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 1, 5, "Frozen Armor", 7, 1000, 0, 2, 10, "Ice Blast", 7, 240, 0, 6, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Normal", 0, 16, 359, 3, 1, 37, 252, "merca222", "merca241", 1500, 120, 336, 14, 168, 10, 77, 10, 62, 8, 235, 20, 3, 12, 18, 4, 64, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 5, 5, "Frozen Armor", 7, 1000, 0, 9, 10, "Ice Blast", 7, 240, 0, 13, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Normal", 0, 16, 359, 3, 1, 61, 252, "merca222", "merca241", 1500, 120, 672, 20, 408, 16, 107, 10, 86, 8, 715, 30, 5, 24, 30, 4, 106, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 9, 5, "Frozen Armor", 7, 1000, 0, 17, 10, "Ice Blast", 7, 240, 0, 21, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Normal", 0, 17, 359, 3, 1, 15, 252, "merca222", "merca241", 1000, 110, 160, 8, 80, 4, 49, 10, 40, 8, 15, 10, , 1, 7, 4, 25, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 4, 5, "Lightning", 7, 30, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Normal", 0, 17, 359, 3, 1, 37, 252, "merca222", "merca241", 1000, 110, 336, 14, 168, 10, 77, 10, 62, 8, 235, 20, 3, 12, 18, 4, 64, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 8, 5, "Lightning", 7, 30, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Normal", 0, 17, 359, 3, 1, 61, 252, "merca222", "merca241", 1000, 110, 672, 20, 408, 16, 107, 10, 86, 8, 715, 30, 5, 24, 30, 4, 106, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 12, 5, "Lightning", 7, 30, 0, 18, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Nightmare", 0, 18, 359, 3, 2, 37, 252, "merca222", "merca241", 9500, 120, 302, 14, 151, 10, 73, 10, 58, 8, 212, 20, 3, 10, 16, 4, 61, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 12, 10, "Fire Ball", 7, 30, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Nightmare", 0, 18, 359, 3, 2, 61, 252, "merca222", "merca241", 9500, 120, 638, 20, 391, 16, 103, 10, 82, 8, 692, 30, 5, 22, 28, 4, 103, 4, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 20, 10, "Fire Ball", 7, 30, 0, 18, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Nightmare", 0, 19, 359, 3, 2, 37, 252, "merca222", "merca241", 12000, 130, 302, 14, 151, 10, 73, 10, 58, 8, 212, 20, 3, 10, 16, 4, 61, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 4, 5, "Frozen Armor", 7, 1000, 0, 8, 10, "Ice Blast", 7, 240, 0, 12, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Nightmare", 0, 19, 359, 3, 2, 61, 252, "merca222", "merca241", 12000, 130, 638, 20, 391, 16, 103, 10, 82, 8, 692, 30, 5, 22, 28, 4, 103, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 8, 5, "Frozen Armor", 7, 1000, 0, 16, 10, "Ice Blast", 7, 240, 0, 20, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Nightmare", 0, 20, 359, 3, 2, 37, 252, "merca222", "merca241", 10000, 120, 302, 14, 151, 10, 73, 10, 58, 8, 212, 20, 3, 10, 16, 4, 61, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 7, 5, "Lightning", 7, 30, 0, 9, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Nightmare", 0, 20, 359, 3, 2, 61, 252, "merca222", "merca241", 10000, 120, 638, 20, 391, 16, 103, 10, 82, 8, 692, 30, 5, 22, 28, 4, 103, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 11, 5, "Lightning", 7, 30, 0, 17, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Hell", 0, 21, 359, 3, 3, 61, 252, "merca222", "merca241", 21000, 130, 574, 20, 352, 16, 99, 10, 78, 8, 623, 30, 5, 20, 26, 4, 100, 4, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 19, 10, "Fire Ball", 7, 30, 0, 17, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Hell", 0, 22, 359, 3, 3, 61, 252, "merca222", "merca241", 27000, 140, 574, 20, 352, 16, 99, 10, 78, 8, 623, 30, 5, 20, 26, 4, 100, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 7, 5, "Frozen Armor", 7, 1000, 0, 15, 10, "Ice Blast", 7, 240, 0, 19, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Hell", 0, 23, 359, 3, 3, 61, 252, "merca222", "merca241", 21000, 130, 574, 20, 352, 16, 99, 10, 78, 8, 623, 30, 5, 20, 27, 4, 100, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 10, 5, "Lightning", 7, 30, 0, 16, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Normal", 100, 15, 359, 3, 1, 15, 252, "merca222", "merca241", 1000, 110, 160, 9, 80, 5, 49, 10, 40, 8, 15, 12, , 1, 7, 4, 25, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 6, 10, "Fire Ball", 7, 30, 0, 4, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Normal", 100, 15, 359, 3, 1, 49, 252, "merca222", "merca241", 1000, 110, 466, 18, 250, 13, 92, 10, 74, 8, 423, 24, 3, 18, 24, 4, 85, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 17, 10, "Fire Ball", 7, 30, 0, 15, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Normal", 100, 15, 359, 3, 1, 79, 252, "merca222", "merca241", 1000, 110, 1006, 27, 640, 25, 130, 10, 104, 8, 1143, 36, 5, 33, 39, 4, 138, 6, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 27, 10, "Fire Ball", 7, 30, 0, 25, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Normal", 100, 16, 359, 3, 1, 15, 252, "merca222", "merca241", 1500, 120, 160, 9, 80, 5, 49, 10, 40, 8, 15, 12, , 1, 7, 4, 25, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 1, 5, "Frozen Armor", 7, 1000, 0, 2, 10, "Ice Blast", 7, 240, 0, 6, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Normal", 100, 16, 359, 3, 1, 49, 252, "merca222", "merca241", 1500, 120, 466, 18, 250, 13, 92, 10, 74, 8, 423, 24, 3, 18, 24, 4, 85, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 7, 5, "Frozen Armor", 7, 1000, 0, 13, 10, "Ice Blast", 7, 240, 0, 17, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Normal", 100, 16, 359, 3, 1, 79, 252, "merca222", "merca241", 1500, 120, 1006, 27, 640, 25, 130, 10, 104, 8, 1143, 36, 5, 33, 39, 4, 138, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 12, 5, "Frozen Armor", 7, 1000, 0, 23, 10, "Ice Blast", 7, 240, 0, 27, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Normal", 100, 17, 359, 3, 1, 15, 252, "merca222", "merca241", 1000, 110, 160, 9, 80, 5, 49, 10, 40, 8, 15, 12, , 1, 7, 4, 25, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 4, 5, "Lightning", 7, 30, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Normal", 100, 17, 359, 3, 1, 49, 252, "merca222", "merca241", 1000, 110, 466, 18, 250, 13, 92, 10, 74, 8, 423, 24, 3, 18, 24, 4, 85, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 10, 5, "Lightning", 7, 30, 0, 14, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Normal", 100, 17, 359, 3, 1, 79, 252, "merca222", "merca241", 1000, 110, 1006, 27, 640, 25, 130, 10, 104, 8, 1143, 36, 5, 33, 39, 4, 138, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 15, 5, "Lightning", 7, 30, 0, 24, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Nightmare", 100, 18, 359, 3, 2, 49, 252, "merca222", "merca241", 9500, 120, 419, 18, 225, 13, 88, 10, 70, 8, 381, 24, 3, 16, 22, 4, 82, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 16, 10, "Fire Ball", 7, 30, 0, 14, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Nightmare", 100, 18, 359, 3, 2, 79, 252, "merca222", "merca241", 9500, 120, 959, 27, 615, 25, 126, 10, 100, 8, 1101, 36, 5, 31, 37, 4, 135, 4, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 26, 10, "Fire Ball", 7, 30, 0, 24, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Nightmare", 100, 19, 359, 3, 2, 49, 252, "merca222", "merca241", 12000, 130, 419, 18, 225, 13, 88, 10, 70, 8, 381, 24, 3, 16, 22, 4, 82, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 6, 5, "Frozen Armor", 7, 1000, 0, 12, 10, "Ice Blast", 7, 240, 0, 16, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Nightmare", 100, 19, 359, 3, 2, 79, 252, "merca222", "merca241", 12000, 130, 959, 27, 615, 25, 126, 10, 100, 8, 1101, 36, 5, 31, 37, 4, 135, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 11, 5, "Frozen Armor", 7, 1000, 0, 22, 10, "Ice Blast", 7, 240, 0, 26, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Nightmare", 100, 20, 359, 3, 2, 49, 252, "merca222", "merca241", 10000, 120, 419, 18, 225, 13, 88, 10, 70, 8, 381, 24, 3, 16, 22, 4, 82, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 9, 5, "Lightning", 7, 30, 0, 13, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Nightmare", 100, 20, 359, 3, 2, 79, 252, "merca222", "merca241", 10000, 120, 959, 27, 615, 25, 126, 10, 100, 8, 1101, 36, 5, 31, 37, 4, 135, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 14, 5, "Lightning", 7, 30, 0, 23, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Fire-Hell", 100, 21, 359, 3, 3, 79, 252, "merca222", "merca241", 21000, 130, 863, 27, 554, 25, 122, 10, 96, 8, 991, 36, 5, 29, 35, 4, 132, 4, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 25, 10, "Fire Ball", 7, 30, 0, 23, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Cold-Hell", 100, 22, 359, 3, 3, 79, 252, "merca222", "merca241", 27000, 140, 863, 27, 554, 25, 122, 10, 96, 8, 991, 36, 5, 29, 35, 4, 132, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 10, 5, "Frozen Armor", 7, 1000, 0, 21, 10, "Ice Blast", 7, 240, 0, 25, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Eastern Sorceror", "Ltng-Hell", 100, 23, 359, 3, 3, 79, 252, "merca222", "merca241", 21000, 130, 863, 27, 554, 25, 122, 10, 96, 8, 991, 36, 5, 29, 36, 4, 132, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 13, 5, "Lightning", 7, 30, 0, 22, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
- ["Barbarian", "1hs-Normal", 0, 24, 561, 5, 1, 28, 515, "mercX101", "mercX167", 9000, 120, 288, 14, 180, 7, 101, 15, 63, 10, 150, 17, 1, 16, 20, 6, 56, 7, "swor", , , 50, "Bash", 5, 15, 0, 4, 10, "Stun", 5, 15, 0, 3, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 0, 24, 561, 5, 1, 42, 515, "mercX101", "mercX167", 9000, 120, 484, 21, 278, 20, 128, 15, 81, 10, 388, 27, 1, 27, 31, 8, 81, 7, "swor", , , 50, "Bash", 5, 50, 0, 9, 10, "Stun", 5, 50, 0, 7, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 0, 24, 561, 5, 1, 75, 515, "mercX101", "mercX167", 9000, 120, 1177, 35, 938, 40, 190, 15, 123, 10, 1279, 37, 1, 60, 64, 8, 139, 4, "swor", , , 50, "Bash", 5, 75, 0, 20, 0, "Stun", 5, 75, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 0, 25, 561, 5, 1, 28, 515, "mercX101", "mercX167", 9000, 120, 288, 14, 180, 7, 101, 15, 63, 10, 150, 17, 1, 16, 20, 6, 56, 7, "swor", , , 50, "Bash", 5, 15, 0, 4, 10, "Stun", 5, 15, 0, 3, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 0, 25, 561, 5, 1, 42, 515, "mercX101", "mercX167", 9000, 120, 484, 21, 278, 20, 128, 15, 81, 10, 388, 27, 1, 27, 31, 8, 81, 7, "swor", , , 50, "Bash", 5, 50, 0, 9, 10, "Stun", 5, 50, 0, 7, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 0, 25, 561, 5, 1, 75, 515, "mercX101", "mercX167", 9000, 120, 1177, 35, 938, 40, 190, 15, 123, 10, 1279, 37, 1, 60, 64, 8, 139, 4, "swor", , , 50, "Bash", 5, 75, 0, 20, 0, "Stun", 5, 75, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Nightmare", 0, 26, 561, 5, 2, 42, 515, "mercX101", "mercX167", 18000, 130, 436, 21, 250, 20, 125, 15, 76, 10, 349, 27, 4, 25, 29, 8, 78, 7, "swor", , , 50, "Bash", 5, 50, 0, 8, 10, "Stun", 5, 50, 0, 6, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Nightmare", 0, 26, 561, 5, 2, 75, 515, "mercX101", "mercX167", 18000, 130, 1129, 35, 910, 40, 187, 15, 118, 10, 1240, 37, 4, 58, 62, 8, 136, 4, "swor", , , 50, "Bash", 5, 75, 0, 19, 0, "Stun", 5, 75, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Nightmare", 0, 27, 561, 5, 2, 42, 515, "mercX101", "mercX167", 18000, 130, 436, 21, 250, 20, 125, 15, 76, 10, 349, 27, 4, 25, 29, 8, 78, 7, "swor", , , 50, "Bash", 5, 50, 0, 8, 10, "Stun", 5, 50, 0, 6, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Nightmare", 0, 27, 561, 5, 2, 75, 515, "mercX101", "mercX167", 18000, 130, 1129, 35, 910, 40, 187, 15, 118, 10, 1240, 37, 4, 58, 62, 8, 136, 4, "swor", , , 50, "Bash", 5, 75, 0, 19, 0, "Stun", 5, 75, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Hell", 0, 28, 561, 5, 3, 75, 515, "mercX101", "mercX167", 32000, 140, 1016, 35, 819, 40, 184, 15, 113, 10, 1116, 37, 6, 56, 60, 8, 133, 4, "swor", , , 50, "Bash", 5, 70, 0, 18, 0, "Stun", 5, 70, 0, 14, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Hell", 0, 29, 561, 5, 3, 75, 515, "mercX101", "mercX167", 32000, 140, 1016, 35, 819, 40, 184, 15, 113, 10, 1116, 37, 6, 56, 60, 8, 133, 4, "swor", , , 50, "Bash", 5, 70, 0, 18, 0, "Stun", 5, 70, 0, 14, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 100, 24, 561, 5, 1, 28, 515, "mercX101", "mercX167", 9000, 120, 288, 18, 180, 10, 101, 15, 63, 10, 150, 20, 1, 16, 20, 6, 56, 7, "swor", , , 50, "Bash", 5, 15, 0, 4, 10, "Stun", 5, 15, 0, 3, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 100, 24, 561, 5, 1, 58, 515, "mercX101", "mercX167", 9000, 120, 828, 27, 480, 35, 158, 15, 101, 10, 750, 35, 1, 39, 43, 8, 109, 7, "swor", , , 50, "Bash", 5, 50, 0, 14, 10, "Stun", 5, 50, 0, 11, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 100, 24, 561, 5, 1, 80, 515, "mercX101", "mercX167", 9000, 120, 1422, 45, 1250, 50, 200, 15, 129, 10, 1520, 45, 1, 61, 65, 8, 148, 4, "swor", , , 50, "Bash", 5, 75, 0, 21, 0, "Stun", 5, 75, 0, 17, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 100, 25, 561, 5, 1, 28, 515, "mercX101", "mercX167", 9000, 120, 288, 18, 180, 10, 101, 15, 63, 10, 150, 20, 1, 16, 20, 6, 56, 7, "swor", , , 50, "Bash", 5, 15, 0, 4, 10, "Stun", 5, 15, 0, 3, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 100, 25, 561, 5, 1, 58, 515, "mercX101", "mercX167", 9000, 120, 828, 27, 480, 35, 158, 15, 101, 10, 750, 35, 1, 39, 43, 8, 109, 7, "swor", , , 50, "Bash", 5, 50, 0, 14, 10, "Stun", 5, 50, 0, 11, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Normal", 100, 25, 561, 5, 1, 80, 515, "mercX101", "mercX167", 9000, 120, 1422, 45, 1250, 50, 200, 15, 129, 10, 1520, 45, 1, 61, 65, 8, 148, 4, "swor", , , 50, "Bash", 5, 75, 0, 21, 0, "Stun", 5, 75, 0, 17, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Nightmare", 100, 26, 561, 5, 2, 58, 515, "mercX101", "mercX167", 18000, 130, 745, 27, 432, 35, 155, 15, 96, 10, 675, 35, 4, 37, 41, 8, 106, 7, "swor", , , 50, "Bash", 5, 50, 0, 13, 10, "Stun", 5, 50, 0, 10, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Nightmare", 100, 26, 561, 5, 2, 80, 515, "mercX101", "mercX167", 18000, 130, 1339, 45, 1202, 50, 197, 15, 124, 10, 1445, 45, 4, 59, 63, 8, 145, 4, "swor", , , 50, "Bash", 5, 75, 0, 20, 0, "Stun", 5, 75, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Nightmare", 100, 27, 561, 5, 2, 58, 515, "mercX101", "mercX167", 18000, 130, 745, 27, 432, 35, 155, 15, 96, 10, 675, 35, 4, 37, 41, 8, 106, 7, "swor", , , 50, "Bash", 5, 50, 0, 13, 10, "Stun", 5, 50, 0, 10, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Nightmare", 100, 27, 561, 5, 2, 80, 515, "mercX101", "mercX167", 18000, 130, 1339, 45, 1202, 50, 197, 15, 124, 10, 1445, 45, 4, 59, 63, 8, 145, 4, "swor", , , 50, "Bash", 5, 75, 0, 20, 0, "Stun", 5, 75, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Hell", 100, 28, 561, 5, 3, 80, 515, "mercX101", "mercX167", 32000, 140, 1205, 45, 1082, 50, 194, 15, 119, 10, 1301, 45, 6, 57, 61, 8, 142, 4, "swor", , , 50, "Bash", 5, 70, 0, 19, 0, "Stun", 5, 70, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
- ["Barbarian", "1hs-Hell", 100, 29, 561, 5, 3, 80, 515, "mercX101", "mercX167", 32000, 140, 1205, 45, 1082, 50, 194, 15, 119, 10, 1301, 45, 6, 57, 61, 8, 142, 4, "swor", , , 50, "Bash", 5, 70, 0, 19, 0, "Stun", 5, 70, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0]
- ];
+ } else if (name >= 1019 && name <= 1039) {
+ return 2;
+ } else if (name >= 1040 && name <= 1059) {
+ return 3;
+ } else if (name >= 10835 && name <= 10901) {
+ return 5;
+ } else {
+ return false;
+ }
+ };
+ const validMercTable = getTable();
+ let newseed = new Rnd(seed);
+ newseed.roll();
+ let index = (newseed.get()) % validMercTable.length;
+ newseed.roll();
+ this.level = (newseed.get()) % 5 + me.charlvl - 5;
+ let lvl = this.level;
+ let difference = this.level - getMercInfo(index, 7);
+ this.hireling = getMercInfo(index, 0);
+ this.typeid = getMercInfo(index, 3);
+ this.subtype = getMercInfo(index, 1);
+ this.skills = [];
+ if (getMercInfo(index, 33) !== undefined) {
+ this.skills.push({
+ name: getMercInfo(index, 33),
+ lvl: Math.floor(lvl * 10 / 32) // experimental
+ });
+ }
+ if (getMercInfo(index, 39) !== undefined) {
+ this.skills.push({
+ name: getMercInfo(index, 39),
+ lvl: Math.floor(lvl * 10 / 32) // experimental
+ });
+ }
+ if (getMercInfo(index, 45) !== undefined) {
+ this.skills.push({
+ name: getMercInfo(index, 45),
+ lvl: Math.floor(lvl * 10 / 32) // experimental
+ });
+ }
+ this.cost = getMercInfo(index, 11) * (15 * difference + 100) / 100;
+ if (this.cost < getMercInfo(index, 11)) {
+ this.cost = getMercInfo(index, 11);
+ }
+ this.cost = Math.floor(this.cost);
+ this.hp = getMercInfo(index, 13) + getMercInfo(index, 14) * difference;
+ if (this.hp < 40) {
+ this.hp = 40;
+ }
+ this.defense = getMercInfo(index, 15) + getMercInfo(index, 16) * difference;
+ if (this.defense < 0) {
+ this.defense = 0;
+ }
+ this.strength = getMercInfo(index, 17) + getMercInfo(index, 18) * difference;
+ if (this.strength < 10) {
+ this.strength = 10;
+ }
+ this.dexterity = getMercInfo(index, 19) + getMercInfo(index, 20) * difference;
+ if (this.dexterity < 10) {
+ this.dexterity = 10;
+ }
+ this.experience = this.level * this.level * (this.level + 1) * getMercInfo(index, 12);
+ if (this.experience < 0) {
+ this.experience = 0;
+ }
+ this.attackrating = getMercInfo(index, 21) + getMercInfo(index, 22) * difference;
+ if (this.attackrating < 1) {
+ this.attackrating = 1;
+ }
+ this.dmg_min = getMercInfo(index, 24) + getMercInfo(index, 26) * difference;
+ if (this.dmg_min < 0) {
+ this.dmg_min = 0;
+ }
+ this.dmg_max = getMercInfo(index, 25) + getMercInfo(index, 26) * difference;
+ if (this.dmg_max < 1) {
+ this.dmg_max = 1;
+ }
+ this.resists = getMercInfo(index, 27) + getMercInfo(index, 28) * difference;
+ if (this.resists < 1) {
+ this.resists = 1;
+ }
+ }
+ Merc.prototype.hire = function () {
+ let npc = getInteractedNPC();
+ if (!npc) throw new Error("To buy merc you need to interact with npc first");
+ if (typeof this.cost !== "number") throw new Error("Invalid cost");
+ if (me.gold < this.cost) throw new Error("Merc is too expensive to buy.");
+ let before = me.gold;
+ while (before === me.gold) {
+ sendPacket(1, sdk.packets.send.HireMerc, 4, npc.gid, 4, this.id);
+ delay((me.ping || 1) * 5);
+ }
+ return true;
+ };
+ return Merc;
+ }());
+ exports.Merc = Merc;
+ let Mercs = [];
+ exports.default = Mercs;
+ function mercPacket(pByte) {
+ switch (pByte[0]) {
+ case 0x4f:
+ // Clear mercenary list
+ Mercs.splice(0, Mercs.length);
+ break;
+ case 0x4e:
+ let name = ((pByte[1]) | (pByte[2] << 8)), seed = ((pByte[3]) | (pByte[4] << 8) | (pByte[5] << 16) | (pByte[6] << 24)) >>> 0;
+ Mercs.push(new Merc(name, seed));
+ break;
+ }
+ }
+ exports.mercPacket = mercPacket;
+ //addEventListener("gamepacket", mercPacket);
+ //removeEventListener("gamepacket", mercPacket);
+ const MercTable = [
+ // 0.Hireling , 1.SubType , 2.Version , 3.Id , 4.Class , 5.Act , 6.Diff , 7.Level , 8.Seller , 9.NameFirst , 10.NameLast , 11.Gold , 12.Exp/Lvl , 13.HP , 14.HP/Lvl , 15.Def , 16.Def/Lvl , Str , Str/Lvl , Dex , Dex/Lvl , AR , AR/Lvl , Share , Dmg-Min , Dmg-Max , Dmg/Lvl , Resist , Resist/Lvl , WType1 , WType2 , HireDesc , DefaultChance , Skill1 , Mode1 , Chance1 , ChancePerLvl1 , Level1 , LvlPerLvl1 , Skill2 , Mode2 , Chance2 , ChancePerLvl2 , Level2 , LvlPerLvl2 , Skill3 , Mode3 , Chance3 , ChancePerLvl3 , Level3 , LvlPerLvl3 , Skill4 , Mode4 , Chance4 , ChancePerLvl4 , Level4 , LvlPerLvl4 , Skill5 , Mode5 , Chance5 , ChancePerLvl5 , Level5 , LvlPerLvl5 , Skill6 , Mode6 , Chance6 , ChancePerLvl6 , Level6 , LvlPerLvl6 , Head , Torso , Weapon , Shield
+ ["Rogue Scout", "Fire - Normal", 0, 0, 271, 1, 1, 3, 150, "merc01", "merc41", 100, 100, 45, 8, 15, 6, 35, 10, 45, 16, 10, 10, , 1, 3, 2, 0, 8, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 1, 10, "Fire Arrow", 4, 25, 8, 1, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Normal", 0, 0, 271, 1, 1, 25, 150, "merc01", "merc41", 100, 100, 221, 13, 147, 12, 63, 10, 89, 16, 230, 20, 1, 7, 9, 4, 44, 7, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 8, 10, "Fire Arrow", 4, 69, 0, 8, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Normal", 0, 0, 271, 1, 1, 49, 150, "merc01", "merc41", 100, 100, 533, 25, 435, 18, 93, 10, 137, 16, 710, 30, 2, 19, 21, 6, 86, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 16, 10, "Fire Arrow", 4, 69, 0, 16, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Normal", 0, 1, 271, 1, 1, 3, 150, "merc01", "merc41", 150, 105, 45, 8, 15, 6, 35, 10, 45, 16, 10, 10, , 1, 3, 2, 0, 8, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 1, 10, "Cold Arrow", 4, 25, 2, 1, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Normal", 0, 1, 271, 1, 1, 25, 150, "merc01", "merc41", 150, 105, 221, 12, 147, 12, 63, 10, 89, 16, 230, 20, 1, 7, 9, 4, 44, 7, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 8, 10, "Cold Arrow", 4, 36, 0, 8, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Normal", 0, 1, 271, 1, 1, 49, 150, "merc01", "merc41", 150, 105, 509, 16, 435, 18, 93, 10, 137, 16, 710, 30, 2, 19, 21, 6, 86, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 16, 10, "Cold Arrow", 4, 36, 0, 16, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Nightmare", 0, 2, 271, 1, 2, 25, 150, "merc01", "merc41", 6000, 110, 199, 12, 132, 12, 60, 10, 84, 16, 207, 20, 1, 6, 8, 4, 41, 7, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 7, 10, "Fire Arrow", 4, 69, 0, 7, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Nightmare", 0, 2, 271, 1, 2, 49, 150, "merc01", "merc41", 6000, 110, 487, 16, 420, 18, 90, 10, 132, 16, 687, 30, 2, 18, 20, 6, 83, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 15, 10, "Fire Arrow", 4, 69, 0, 15, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Nightmare", 0, 3, 271, 1, 2, 25, 150, "merc01", "merc41", 7500, 115, 199, 12, 132, 12, 60, 10, 84, 16, 207, 20, 1, 6, 8, 4, 41, 7, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 7, 10, "Cold Arrow", 4, 69, 0, 7, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Nightmare", 0, 3, 271, 1, 2, 49, 150, "merc01", "merc41", 7500, 115, 487, 16, 420, 18, 90, 10, 132, 16, 687, 30, 2, 18, 20, 6, 83, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 15, 10, "Cold Arrow", 4, 69, 0, 15, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Hell", 0, 4, 271, 1, 3, 49, 150, "merc01", "merc41", 12500, 120, 438, 16, 378, 18, 87, 10, 127, 16, 618, 30, 2, 17, 19, 6, 80, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 14, 10, "Fire Arrow", 4, 69, 0, 14, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Hell", 0, 5, 271, 1, 3, 49, 150, "merc01", "merc41", 14000, 125, 438, 16, 378, 18, 87, 10, 127, 16, 618, 30, 2, 17, 19, 6, 80, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 14, 10, "Cold Arrow", 4, 69, 0, 14, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Normal", 100, 0, 271, 1, 1, 3, 150, "merc01", "merc41", 100, 100, 45, 9, 15, 8, 35, 10, 45, 16, 10, 12, , 1, 3, 2, 0, 8, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 1, 10, "Fire Arrow", 4, 25, 5, 1, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Normal", 100, 0, 271, 1, 1, 36, 150, "merc01", "merc41", 100, 100, 342, 18, 279, 15, 77, 10, 111, 16, 406, 24, 1, 9, 11, 4, 66, 7, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 12, 10, "Fire Arrow", 4, 67, 0, 12, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Normal", 100, 0, 271, 1, 1, 67, 150, "merc01", "merc41", 100, 100, 900, 30, 744, 22, 116, 10, 173, 16, 1150, 36, 2, 25, 27, 6, 121, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 22, 10, "Fire Arrow", 4, 67, 0, 22, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Normal", 100, 1, 271, 1, 1, 3, 150, "merc01", "merc41", 150, 105, 45, 9, 15, 8, 35, 10, 45, 16, 10, 12, , 1, 3, 2, 0, 8, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 1, 10, "Cold Arrow", 4, 25, 5, 1, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Normal", 100, 1, 271, 1, 1, 36, 150, "merc01", "merc41", 150, 105, 342, 18, 279, 15, 77, 10, 111, 16, 406, 24, 1, 9, 11, 4, 66, 7, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 12, 10, "Cold Arrow", 4, 67, 0, 12, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Normal", 100, 1, 271, 1, 1, 67, 150, "merc01", "merc41", 150, 105, 900, 30, 744, 22, 116, 10, 173, 16, 1150, 36, 2, 25, 27, 6, 121, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 22, 10, "Cold Arrow", 4, 67, 0, 22, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Nightmare", 100, 2, 271, 1, 2, 36, 150, "merc01", "merc41", 6000, 110, 308, 18, 251, 15, 74, 10, 106, 16, 365, 24, 1, 8, 10, 4, 63, 7, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 11, 10, "Fire Arrow", 4, 69, 0, 11, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Nightmare", 100, 2, 271, 1, 2, 67, 150, "merc01", "merc41", 6000, 110, 866, 30, 716, 22, 113, 10, 168, 16, 1109, 36, 2, 24, 26, 6, 118, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 21, 10, "Fire Arrow", 4, 69, 0, 21, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Nightmare", 100, 3, 271, 1, 2, 36, 150, "merc01", "merc41", 7500, 115, 308, 18, 251, 15, 74, 10, 106, 16, 365, 24, 1, 8, 10, 4, 63, 7, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 11, 10, "Cold Arrow", 4, 69, 0, 11, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Nightmare", 100, 3, 271, 1, 2, 67, 150, "merc01", "merc41", 7500, 115, 866, 30, 716, 22, 113, 10, 168, 16, 1109, 36, 2, 24, 26, 6, 118, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 21, 10, "Cold Arrow", 4, 69, 0, 21, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Fire - Hell", 100, 4, 271, 1, 3, 67, 150, "merc01", "merc41", 12500, 120, 779, 30, 644, 22, 110, 10, 163, 16, 998, 36, 2, 23, 25, 6, 115, 5, "bow", , "farw", 75, "Inner Sight", 4, 10, 0, 20, 10, "Fire Arrow", 4, 69, 0, 20, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Rogue Scout", "Ice - Hell", 100, 5, 271, 1, 3, 67, 150, "merc01", "merc41", 14000, 125, 779, 30, 644, 22, 110, 10, 163, 16, 998, 36, 2, 23, 25, 6, 115, 5, "bow", , "carw", 75, "Inner Sight", 4, 10, 0, 20, 10, "Cold Arrow", 4, 69, 0, 20, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 1, 2, 0],
+ ["Desert Mercenary", "Comb-Normal", 0, 6, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 10, 45, 9, 57, 14, 40, 12, 20, 10, 1, 7, 14, 4, 18, 8, "pole", "spea", "comb", 30, "Jab", 14, 70, 4, 3, 10, "Prayer", 1, 10, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Normal", 0, 6, 338, 2, 1, 31, 198, "merca201", "merca221", 350, 110, 340, 20, 243, 16, 96, 14, 73, 12, 240, 20, 3, 18, 25, 6, 62, 7, "pole", "spea", "comb", 30, "Jab", 14, 92, 4, 10, 10, "Prayer", 1, 10, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Normal", 0, 6, 338, 2, 1, 55, 198, "merca201", "merca221", 350, 110, 820, 35, 627, 24, 138, 14, 109, 12, 720, 30, 4, 36, 43, 8, 104, 4, "pole", "spea", "comb", 30, "Jab", 14, 116, 4, 18, 10, "Prayer", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Normal", 0, 7, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 10, 45, 9, 57, 14, 40, 12, 20, 10, 1, 7, 14, 4, 18, 8, "pole", "spea", "def", 30, "Jab", 14, 70, 4, 3, 10, "Defiance", 1, 10, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Normal", 0, 7, 338, 2, 1, 31, 198, "merca201", "merca221", 350, 110, 340, 20, 243, 16, 96, 14, 73, 12, 240, 20, 3, 18, 25, 6, 62, 7, "pole", "spea", "def", 30, "Jab", 14, 92, 4, 10, 10, "Defiance", 1, 10, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Normal", 0, 7, 338, 2, 1, 55, 198, "merca201", "merca221", 350, 110, 820, 35, 627, 24, 138, 14, 109, 12, 720, 30, 4, 36, 43, 8, 104, 4, "pole", "spea", "def", 30, "Jab", 14, 116, 4, 18, 0, "Defiance", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Normal", 0, 8, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 10, 45, 9, 57, 14, 40, 12, 20, 10, 1, 7, 14, 4, 18, 8, "pole", "spea", "off", 30, "Jab", 14, 70, 4, 3, 10, "Blessed Aim", 1, 10, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Normal", 0, 8, 338, 2, 1, 31, 198, "merca201", "merca221", 350, 110, 340, 20, 243, 16, 96, 14, 73, 12, 240, 20, 3, 18, 25, 6, 62, 7, "pole", "spea", "off", 30, "Jab", 14, 92, 4, 10, 10, "Blessed Aim", 1, 10, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Normal", 0, 8, 338, 2, 1, 55, 198, "merca201", "merca221", 350, 110, 820, 35, 627, 24, 138, 14, 109, 12, 720, 30, 4, 36, 43, 8, 104, 4, "pole", "spea", "off", 30, "Jab", 14, 116, 4, 18, 0, "Blessed Aim", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Nightmare", 0, 9, 338, 2, 2, 31, 198, "merca201", "merca221", 7900, 120, 306, 20, 219, 16, 92, 14, 69, 12, 216, 20, 3, 16, 23, 6, 59, 7, "pole", "spea", "comb", 30, "Jab", 14, 120, 4, 9, 10, "Thorns", 1, 10, 0, 5, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Nightmare", 0, 9, 338, 2, 2, 55, 198, "merca201", "merca221", 7900, 120, 786, 35, 603, 24, 134, 14, 105, 12, 696, 30, 4, 34, 41, 8, 101, 4, "pole", "spea", "comb", 30, "Jab", 14, 144, 4, 17, 10, "Thorns", 1, 10, 0, 13, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Nightmare", 0, 10, 338, 2, 2, 31, 198, "merca201", "merca221", 7900, 120, 306, 20, 219, 16, 92, 14, 69, 12, 216, 20, 3, 16, 23, 6, 59, 7, "pole", "spea", "def", 30, "Jab", 14, 120, 4, 9, 10, "Holy Freeze", 1, 10, 0, 6, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Nightmare", 0, 10, 338, 2, 2, 55, 198, "merca201", "merca221", 7900, 120, 786, 35, 603, 24, 134, 14, 105, 12, 696, 30, 4, 34, 41, 8, 101, 4, "pole", "spea", "def", 30, "Jab", 14, 144, 4, 17, 0, "Holy Freeze", 1, 10, 0, 14, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Nightmare", 0, 11, 338, 2, 2, 31, 198, "merca201", "merca221", 7900, 120, 306, 20, 219, 16, 92, 14, 69, 12, 216, 20, 3, 16, 23, 6, 59, 7, "pole", "spea", "off", 30, "Jab", 14, 120, 4, 9, 10, "Might", 1, 10, 0, 7, 8, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Nightmare", 0, 11, 338, 2, 2, 55, 198, "merca201", "merca221", 7900, 120, 786, 35, 603, 24, 134, 14, 105, 12, 696, 30, 4, 34, 41, 8, 101, 4, "pole", "spea", "off", 30, "Jab", 14, 144, 4, 17, 0, "Might", 1, 10, 0, 13, 8, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Hell", 0, 12, 338, 2, 3, 55, 198, "merca201", "merca221", 15000, 130, 707, 35, 543, 24, 130, 14, 101, 12, 626, 30, 4, 32, 39, 8, 98, 4, "pole", "spea", "comb", 30, "Jab", 14, 104, 4, 16, 10, "Prayer", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Hell", 0, 13, 338, 2, 3, 55, 198, "merca201", "merca221", 15000, 130, 707, 35, 543, 24, 130, 14, 101, 12, 626, 30, 4, 32, 39, 8, 98, 4, "pole", "spea", "def", 30, "Jab", 14, 104, 4, 16, 0, "Defiance", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Hell", 0, 14, 338, 2, 3, 55, 198, "merca201", "merca221", 15000, 130, 707, 35, 543, 24, 130, 14, 101, 12, 626, 30, 4, 32, 39, 8, 98, 4, "pole", "spea", "off", 30, "Jab", 14, 104, 4, 16, 0, "Blessed Aim", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Normal", 100, 6, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 15, 45, 11, 57, 14, 40, 12, 20, 12, 1, 7, 14, 4, 18, 8, "pole", "spea", "comb", 30, "Jab", 14, 70, 4, 3, 10, "Prayer", 1, 10, 0, 3, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Normal", 100, 6, 338, 2, 1, 43, 198, "merca201", "merca221", 350, 110, 630, 25, 419, 19, 117, 14, 91, 12, 428, 24, 3, 24, 31, 6, 86, 7, "pole", "spea", "comb", 30, "Jab", 14, 104, 4, 14, 10, "Prayer", 1, 10, 0, 11, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Normal", 100, 6, 338, 2, 1, 75, 198, "merca201", "merca221", 350, 110, 1430, 40, 1027, 28, 173, 14, 139, 12, 1196, 36, 4, 48, 55, 8, 142, 4, "pole", "spea", "comb", 30, "Jab", 14, 136, 4, 24, 10, "Prayer", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Normal", 100, 7, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 15, 45, 11, 57, 14, 40, 12, 20, 12, 1, 7, 14, 4, 18, 8, "pole", "spea", "def", 30, "Jab", 14, 70, 4, 3, 10, "Defiance", 1, 10, 0, 3, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Normal", 100, 7, 338, 2, 1, 43, 198, "merca201", "merca221", 350, 110, 630, 25, 419, 19, 117, 14, 91, 12, 428, 24, 3, 24, 31, 6, 86, 7, "pole", "spea", "def", 30, "Jab", 14, 104, 4, 14, 10, "Defiance", 1, 10, 0, 11, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Normal", 100, 7, 338, 2, 1, 75, 198, "merca201", "merca221", 350, 110, 1430, 40, 1027, 28, 173, 14, 139, 12, 1196, 36, 4, 48, 55, 8, 142, 4, "pole", "spea", "def", 30, "Jab", 14, 136, 4, 24, 0, "Defiance", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Normal", 100, 8, 338, 2, 1, 9, 198, "merca201", "merca221", 350, 110, 120, 15, 45, 11, 57, 14, 40, 12, 20, 12, 1, 7, 14, 4, 18, 8, "pole", "spea", "off", 30, "Jab", 14, 70, 4, 3, 10, "Blessed Aim", 1, 10, 0, 3, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Normal", 100, 8, 338, 2, 1, 43, 198, "merca201", "merca221", 350, 110, 630, 25, 419, 19, 117, 14, 91, 12, 428, 24, 3, 24, 31, 6, 86, 7, "pole", "spea", "off", 30, "Jab", 14, 104, 4, 14, 10, "Blessed Aim", 1, 10, 0, 11, 7, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Normal", 100, 8, 338, 2, 1, 75, 198, "merca201", "merca221", 350, 110, 1430, 40, 1027, 28, 173, 14, 139, 12, 1196, 36, 4, 48, 55, 8, 142, 4, "pole", "spea", "off", 30, "Jab", 14, 136, 4, 24, 0, "Blessed Aim", 1, 10, 0, 18, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Nightmare", 100, 9, 338, 2, 2, 43, 198, "merca201", "merca221", 7900, 120, 567, 25, 377, 19, 113, 14, 87, 12, 385, 24, 3, 22, 29, 6, 83, 7, "pole", "spea", "comb", 30, "Jab", 14, 120, 4, 13, 10, "Thorns", 1, 10, 0, 5, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Nightmare", 100, 9, 338, 2, 2, 75, 198, "merca201", "merca221", 7900, 120, 1367, 40, 985, 28, 169, 14, 135, 12, 1153, 36, 4, 46, 53, 8, 139, 4, "pole", "spea", "comb", 30, "Jab", 14, 152, 4, 23, 10, "Thorns", 1, 10, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Nightmare", 100, 10, 338, 2, 2, 43, 198, "merca201", "merca221", 7900, 120, 567, 25, 377, 19, 113, 14, 87, 12, 385, 24, 3, 22, 29, 6, 83, 7, "pole", "spea", "def", 30, "Jab", 14, 120, 4, 13, 10, "Holy Freeze", 1, 10, 0, 6, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Nightmare", 100, 10, 338, 2, 2, 75, 198, "merca201", "merca221", 7900, 120, 1367, 40, 985, 28, 169, 14, 135, 12, 1153, 36, 4, 46, 53, 8, 139, 4, "pole", "spea", "def", 30, "Jab", 14, 152, 4, 23, 0, "Holy Freeze", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Nightmare", 100, 11, 338, 2, 2, 43, 198, "merca201", "merca221", 7900, 120, 567, 25, 377, 19, 113, 14, 87, 12, 385, 24, 3, 22, 29, 6, 83, 7, "pole", "spea", "off", 30, "Jab", 14, 120, 4, 13, 10, "Might", 1, 10, 0, 7, 8, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Nightmare", 100, 11, 338, 2, 2, 75, 198, "merca201", "merca221", 7900, 120, 1367, 40, 985, 28, 169, 14, 135, 12, 1153, 36, 4, 46, 53, 8, 139, 4, "pole", "spea", "off", 30, "Jab", 14, 152, 4, 23, 0, "Might", 1, 10, 0, 15, 8, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Comb-Hell", 100, 12, 338, 2, 3, 75, 198, "merca201", "merca221", 15000, 130, 1230, 40, 887, 28, 165, 14, 131, 12, 1038, 36, 4, 44, 51, 8, 136, 4, "pole", "spea", "comb", 30, "Jab", 14, 104, 4, 22, 10, "Prayer", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Def-Hell", 100, 13, 338, 2, 3, 75, 198, "merca201", "merca221", 15000, 130, 1230, 40, 887, 28, 165, 14, 131, 12, 1038, 36, 4, 44, 51, 8, 136, 4, "pole", "spea", "def", 30, "Jab", 14, 104, 4, 22, 0, "Defiance", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Desert Mercenary", "Off-Hell", 100, 14, 338, 2, 3, 75, 198, "merca201", "merca221", 15000, 130, 1230, 40, 887, 28, 165, 14, 131, 12, 1038, 36, 4, 44, 51, 8, 136, 4, "pole", "spea", "off", 30, "Jab", 14, 104, 4, 22, 0, "Blessed Aim", 1, 10, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 0],
+ ["Eastern Sorceror", "Fire-Normal", 0, 15, 359, 3, 1, 15, 252, "merca222", "merca241", 1000, 110, 160, 8, 80, 4, 49, 10, 40, 8, 15, 10, , 1, 7, 4, 25, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 6, 10, "Fire Ball", 7, 30, 0, 4, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Normal", 0, 15, 359, 3, 1, 37, 252, "merca222", "merca241", 1000, 110, 336, 14, 168, 10, 77, 10, 62, 8, 235, 20, 3, 12, 18, 4, 64, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 13, 10, "Fire Ball", 7, 30, 0, 11, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Normal", 0, 15, 359, 3, 1, 61, 252, "merca222", "merca241", 1000, 110, 672, 20, 408, 18, 107, 10, 86, 8, 715, 30, 5, 24, 30, 4, 106, 6, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 21, 10, "Fire Ball", 7, 30, 0, 19, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Normal", 0, 16, 359, 3, 1, 15, 252, "merca222", "merca241", 1500, 120, 160, 8, 80, 4, 49, 10, 40, 8, 15, 10, , 1, 7, 4, 25, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 1, 5, "Frozen Armor", 7, 1000, 0, 2, 10, "Ice Blast", 7, 240, 0, 6, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Normal", 0, 16, 359, 3, 1, 37, 252, "merca222", "merca241", 1500, 120, 336, 14, 168, 10, 77, 10, 62, 8, 235, 20, 3, 12, 18, 4, 64, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 5, 5, "Frozen Armor", 7, 1000, 0, 9, 10, "Ice Blast", 7, 240, 0, 13, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Normal", 0, 16, 359, 3, 1, 61, 252, "merca222", "merca241", 1500, 120, 672, 20, 408, 16, 107, 10, 86, 8, 715, 30, 5, 24, 30, 4, 106, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 9, 5, "Frozen Armor", 7, 1000, 0, 17, 10, "Ice Blast", 7, 240, 0, 21, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Normal", 0, 17, 359, 3, 1, 15, 252, "merca222", "merca241", 1000, 110, 160, 8, 80, 4, 49, 10, 40, 8, 15, 10, , 1, 7, 4, 25, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 4, 5, "Lightning", 7, 30, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Normal", 0, 17, 359, 3, 1, 37, 252, "merca222", "merca241", 1000, 110, 336, 14, 168, 10, 77, 10, 62, 8, 235, 20, 3, 12, 18, 4, 64, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 8, 5, "Lightning", 7, 30, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Normal", 0, 17, 359, 3, 1, 61, 252, "merca222", "merca241", 1000, 110, 672, 20, 408, 16, 107, 10, 86, 8, 715, 30, 5, 24, 30, 4, 106, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 12, 5, "Lightning", 7, 30, 0, 18, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Nightmare", 0, 18, 359, 3, 2, 37, 252, "merca222", "merca241", 9500, 120, 302, 14, 151, 10, 73, 10, 58, 8, 212, 20, 3, 10, 16, 4, 61, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 12, 10, "Fire Ball", 7, 30, 0, 10, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Nightmare", 0, 18, 359, 3, 2, 61, 252, "merca222", "merca241", 9500, 120, 638, 20, 391, 16, 103, 10, 82, 8, 692, 30, 5, 22, 28, 4, 103, 4, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 20, 10, "Fire Ball", 7, 30, 0, 18, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Nightmare", 0, 19, 359, 3, 2, 37, 252, "merca222", "merca241", 12000, 130, 302, 14, 151, 10, 73, 10, 58, 8, 212, 20, 3, 10, 16, 4, 61, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 4, 5, "Frozen Armor", 7, 1000, 0, 8, 10, "Ice Blast", 7, 240, 0, 12, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Nightmare", 0, 19, 359, 3, 2, 61, 252, "merca222", "merca241", 12000, 130, 638, 20, 391, 16, 103, 10, 82, 8, 692, 30, 5, 22, 28, 4, 103, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 8, 5, "Frozen Armor", 7, 1000, 0, 16, 10, "Ice Blast", 7, 240, 0, 20, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Nightmare", 0, 20, 359, 3, 2, 37, 252, "merca222", "merca241", 10000, 120, 302, 14, 151, 10, 73, 10, 58, 8, 212, 20, 3, 10, 16, 4, 61, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 7, 5, "Lightning", 7, 30, 0, 9, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Nightmare", 0, 20, 359, 3, 2, 61, 252, "merca222", "merca241", 10000, 120, 638, 20, 391, 16, 103, 10, 82, 8, 692, 30, 5, 22, 28, 4, 103, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 11, 5, "Lightning", 7, 30, 0, 17, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Hell", 0, 21, 359, 3, 3, 61, 252, "merca222", "merca241", 21000, 130, 574, 20, 352, 16, 99, 10, 78, 8, 623, 30, 5, 20, 26, 4, 100, 4, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 19, 10, "Fire Ball", 7, 30, 0, 17, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Hell", 0, 22, 359, 3, 3, 61, 252, "merca222", "merca241", 27000, 140, 574, 20, 352, 16, 99, 10, 78, 8, 623, 30, 5, 20, 26, 4, 100, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 7, 5, "Frozen Armor", 7, 1000, 0, 15, 10, "Ice Blast", 7, 240, 0, 19, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Hell", 0, 23, 359, 3, 3, 61, 252, "merca222", "merca241", 21000, 130, 574, 20, 352, 16, 99, 10, 78, 8, 623, 30, 5, 20, 27, 4, 100, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 10, 5, "Lightning", 7, 30, 0, 16, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Normal", 100, 15, 359, 3, 1, 15, 252, "merca222", "merca241", 1000, 110, 160, 9, 80, 5, 49, 10, 40, 8, 15, 12, , 1, 7, 4, 25, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 6, 10, "Fire Ball", 7, 30, 0, 4, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Normal", 100, 15, 359, 3, 1, 49, 252, "merca222", "merca241", 1000, 110, 466, 18, 250, 13, 92, 10, 74, 8, 423, 24, 3, 18, 24, 4, 85, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 17, 10, "Fire Ball", 7, 30, 0, 15, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Normal", 100, 15, 359, 3, 1, 79, 252, "merca222", "merca241", 1000, 110, 1006, 27, 640, 25, 130, 10, 104, 8, 1143, 36, 5, 33, 39, 4, 138, 6, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 27, 10, "Fire Ball", 7, 30, 0, 25, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Normal", 100, 16, 359, 3, 1, 15, 252, "merca222", "merca241", 1500, 120, 160, 9, 80, 5, 49, 10, 40, 8, 15, 12, , 1, 7, 4, 25, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 1, 5, "Frozen Armor", 7, 1000, 0, 2, 10, "Ice Blast", 7, 240, 0, 6, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Normal", 100, 16, 359, 3, 1, 49, 252, "merca222", "merca241", 1500, 120, 466, 18, 250, 13, 92, 10, 74, 8, 423, 24, 3, 18, 24, 4, 85, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 7, 5, "Frozen Armor", 7, 1000, 0, 13, 10, "Ice Blast", 7, 240, 0, 17, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Normal", 100, 16, 359, 3, 1, 79, 252, "merca222", "merca241", 1500, 120, 1006, 27, 640, 25, 130, 10, 104, 8, 1143, 36, 5, 33, 39, 4, 138, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 12, 5, "Frozen Armor", 7, 1000, 0, 23, 10, "Ice Blast", 7, 240, 0, 27, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Normal", 100, 17, 359, 3, 1, 15, 252, "merca222", "merca241", 1000, 110, 160, 9, 80, 5, 49, 10, 40, 8, 15, 12, , 1, 7, 4, 25, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 4, 5, "Lightning", 7, 30, 0, 3, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Normal", 100, 17, 359, 3, 1, 49, 252, "merca222", "merca241", 1000, 110, 466, 18, 250, 13, 92, 10, 74, 8, 423, 24, 3, 18, 24, 4, 85, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 10, 5, "Lightning", 7, 30, 0, 14, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Normal", 100, 17, 359, 3, 1, 79, 252, "merca222", "merca241", 1000, 110, 1006, 27, 640, 25, 130, 10, 104, 8, 1143, 36, 5, 33, 39, 4, 138, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 15, 5, "Lightning", 7, 30, 0, 24, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Nightmare", 100, 18, 359, 3, 2, 49, 252, "merca222", "merca241", 9500, 120, 419, 18, 225, 13, 88, 10, 70, 8, 381, 24, 3, 16, 22, 4, 82, 7, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 16, 10, "Fire Ball", 7, 30, 0, 14, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Nightmare", 100, 18, 359, 3, 2, 79, 252, "merca222", "merca241", 9500, 120, 959, 27, 615, 25, 126, 10, 100, 8, 1101, 36, 5, 31, 37, 4, 135, 4, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 26, 10, "Fire Ball", 7, 30, 0, 24, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Nightmare", 100, 19, 359, 3, 2, 49, 252, "merca222", "merca241", 12000, 130, 419, 18, 225, 13, 88, 10, 70, 8, 381, 24, 3, 16, 22, 4, 82, 7, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 6, 5, "Frozen Armor", 7, 1000, 0, 12, 10, "Ice Blast", 7, 240, 0, 16, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Nightmare", 100, 19, 359, 3, 2, 79, 252, "merca222", "merca241", 12000, 130, 959, 27, 615, 25, 126, 10, 100, 8, 1101, 36, 5, 31, 37, 4, 135, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 11, 5, "Frozen Armor", 7, 1000, 0, 22, 10, "Ice Blast", 7, 240, 0, 26, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Nightmare", 100, 20, 359, 3, 2, 49, 252, "merca222", "merca241", 10000, 120, 419, 18, 225, 13, 88, 10, 70, 8, 381, 24, 3, 16, 22, 4, 82, 7, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 9, 5, "Lightning", 7, 30, 0, 13, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Nightmare", 100, 20, 359, 3, 2, 79, 252, "merca222", "merca241", 10000, 120, 959, 27, 615, 25, 126, 10, 100, 8, 1101, 36, 5, 31, 37, 4, 135, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 14, 5, "Lightning", 7, 30, 0, 23, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Fire-Hell", 100, 21, 359, 3, 3, 79, 252, "merca222", "merca241", 21000, 130, 863, 27, 554, 25, 122, 10, 96, 8, 991, 36, 5, 29, 35, 4, 132, 4, "swor", "shie", "fire", 10, "Inferno", 7, 60, 0, 25, 10, "Fire Ball", 7, 30, 0, 23, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Cold-Hell", 100, 22, 359, 3, 3, 79, 252, "merca222", "merca241", 27000, 140, 863, 27, 554, 25, 122, 10, 96, 8, 991, 36, 5, 29, 35, 4, 132, 4, "swor", "shie", "cold", 10, "Glacial Spike", 7, 60, 0, 10, 5, "Frozen Armor", 7, 1000, 0, 21, 10, "Ice Blast", 7, 240, 0, 25, 10, , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Eastern Sorceror", "Ltng-Hell", 100, 23, 359, 3, 3, 79, 252, "merca222", "merca241", 21000, 130, 863, 27, 554, 25, 122, 10, 96, 8, 991, 36, 5, 29, 36, 4, 132, 4, "swor", "shie", "ltng", 10, "Charged Bolt", 7, 60, 0, 13, 5, "Lightning", 7, 30, 0, 22, 10, , , , , , , , , , , , , , , , , , , , , , , , , 2, 2, 2, 1],
+ ["Barbarian", "1hs-Normal", 0, 24, 561, 5, 1, 28, 515, "mercX101", "mercX167", 9000, 120, 288, 14, 180, 7, 101, 15, 63, 10, 150, 17, 1, 16, 20, 6, 56, 7, "swor", , , 50, "Bash", 5, 15, 0, 4, 10, "Stun", 5, 15, 0, 3, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 0, 24, 561, 5, 1, 42, 515, "mercX101", "mercX167", 9000, 120, 484, 21, 278, 20, 128, 15, 81, 10, 388, 27, 1, 27, 31, 8, 81, 7, "swor", , , 50, "Bash", 5, 50, 0, 9, 10, "Stun", 5, 50, 0, 7, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 0, 24, 561, 5, 1, 75, 515, "mercX101", "mercX167", 9000, 120, 1177, 35, 938, 40, 190, 15, 123, 10, 1279, 37, 1, 60, 64, 8, 139, 4, "swor", , , 50, "Bash", 5, 75, 0, 20, 0, "Stun", 5, 75, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 0, 25, 561, 5, 1, 28, 515, "mercX101", "mercX167", 9000, 120, 288, 14, 180, 7, 101, 15, 63, 10, 150, 17, 1, 16, 20, 6, 56, 7, "swor", , , 50, "Bash", 5, 15, 0, 4, 10, "Stun", 5, 15, 0, 3, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 0, 25, 561, 5, 1, 42, 515, "mercX101", "mercX167", 9000, 120, 484, 21, 278, 20, 128, 15, 81, 10, 388, 27, 1, 27, 31, 8, 81, 7, "swor", , , 50, "Bash", 5, 50, 0, 9, 10, "Stun", 5, 50, 0, 7, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 0, 25, 561, 5, 1, 75, 515, "mercX101", "mercX167", 9000, 120, 1177, 35, 938, 40, 190, 15, 123, 10, 1279, 37, 1, 60, 64, 8, 139, 4, "swor", , , 50, "Bash", 5, 75, 0, 20, 0, "Stun", 5, 75, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Nightmare", 0, 26, 561, 5, 2, 42, 515, "mercX101", "mercX167", 18000, 130, 436, 21, 250, 20, 125, 15, 76, 10, 349, 27, 4, 25, 29, 8, 78, 7, "swor", , , 50, "Bash", 5, 50, 0, 8, 10, "Stun", 5, 50, 0, 6, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Nightmare", 0, 26, 561, 5, 2, 75, 515, "mercX101", "mercX167", 18000, 130, 1129, 35, 910, 40, 187, 15, 118, 10, 1240, 37, 4, 58, 62, 8, 136, 4, "swor", , , 50, "Bash", 5, 75, 0, 19, 0, "Stun", 5, 75, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Nightmare", 0, 27, 561, 5, 2, 42, 515, "mercX101", "mercX167", 18000, 130, 436, 21, 250, 20, 125, 15, 76, 10, 349, 27, 4, 25, 29, 8, 78, 7, "swor", , , 50, "Bash", 5, 50, 0, 8, 10, "Stun", 5, 50, 0, 6, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Nightmare", 0, 27, 561, 5, 2, 75, 515, "mercX101", "mercX167", 18000, 130, 1129, 35, 910, 40, 187, 15, 118, 10, 1240, 37, 4, 58, 62, 8, 136, 4, "swor", , , 50, "Bash", 5, 75, 0, 19, 0, "Stun", 5, 75, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Hell", 0, 28, 561, 5, 3, 75, 515, "mercX101", "mercX167", 32000, 140, 1016, 35, 819, 40, 184, 15, 113, 10, 1116, 37, 6, 56, 60, 8, 133, 4, "swor", , , 50, "Bash", 5, 70, 0, 18, 0, "Stun", 5, 70, 0, 14, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Hell", 0, 29, 561, 5, 3, 75, 515, "mercX101", "mercX167", 32000, 140, 1016, 35, 819, 40, 184, 15, 113, 10, 1116, 37, 6, 56, 60, 8, 133, 4, "swor", , , 50, "Bash", 5, 70, 0, 18, 0, "Stun", 5, 70, 0, 14, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 100, 24, 561, 5, 1, 28, 515, "mercX101", "mercX167", 9000, 120, 288, 18, 180, 10, 101, 15, 63, 10, 150, 20, 1, 16, 20, 6, 56, 7, "swor", , , 50, "Bash", 5, 15, 0, 4, 10, "Stun", 5, 15, 0, 3, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 100, 24, 561, 5, 1, 58, 515, "mercX101", "mercX167", 9000, 120, 828, 27, 480, 35, 158, 15, 101, 10, 750, 35, 1, 39, 43, 8, 109, 7, "swor", , , 50, "Bash", 5, 50, 0, 14, 10, "Stun", 5, 50, 0, 11, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 100, 24, 561, 5, 1, 80, 515, "mercX101", "mercX167", 9000, 120, 1422, 45, 1250, 50, 200, 15, 129, 10, 1520, 45, 1, 61, 65, 8, 148, 4, "swor", , , 50, "Bash", 5, 75, 0, 21, 0, "Stun", 5, 75, 0, 17, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 100, 25, 561, 5, 1, 28, 515, "mercX101", "mercX167", 9000, 120, 288, 18, 180, 10, 101, 15, 63, 10, 150, 20, 1, 16, 20, 6, 56, 7, "swor", , , 50, "Bash", 5, 15, 0, 4, 10, "Stun", 5, 15, 0, 3, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 100, 25, 561, 5, 1, 58, 515, "mercX101", "mercX167", 9000, 120, 828, 27, 480, 35, 158, 15, 101, 10, 750, 35, 1, 39, 43, 8, 109, 7, "swor", , , 50, "Bash", 5, 50, 0, 14, 10, "Stun", 5, 50, 0, 11, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Normal", 100, 25, 561, 5, 1, 80, 515, "mercX101", "mercX167", 9000, 120, 1422, 45, 1250, 50, 200, 15, 129, 10, 1520, 45, 1, 61, 65, 8, 148, 4, "swor", , , 50, "Bash", 5, 75, 0, 21, 0, "Stun", 5, 75, 0, 17, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Nightmare", 100, 26, 561, 5, 2, 58, 515, "mercX101", "mercX167", 18000, 130, 745, 27, 432, 35, 155, 15, 96, 10, 675, 35, 4, 37, 41, 8, 106, 7, "swor", , , 50, "Bash", 5, 50, 0, 13, 10, "Stun", 5, 50, 0, 10, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Nightmare", 100, 26, 561, 5, 2, 80, 515, "mercX101", "mercX167", 18000, 130, 1339, 45, 1202, 50, 197, 15, 124, 10, 1445, 45, 4, 59, 63, 8, 145, 4, "swor", , , 50, "Bash", 5, 75, 0, 20, 0, "Stun", 5, 75, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Nightmare", 100, 27, 561, 5, 2, 58, 515, "mercX101", "mercX167", 18000, 130, 745, 27, 432, 35, 155, 15, 96, 10, 675, 35, 4, 37, 41, 8, 106, 7, "swor", , , 50, "Bash", 5, 50, 0, 13, 10, "Stun", 5, 50, 0, 10, 8, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Nightmare", 100, 27, 561, 5, 2, 80, 515, "mercX101", "mercX167", 18000, 130, 1339, 45, 1202, 50, 197, 15, 124, 10, 1445, 45, 4, 59, 63, 8, 145, 4, "swor", , , 50, "Bash", 5, 75, 0, 20, 0, "Stun", 5, 75, 0, 16, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Hell", 100, 28, 561, 5, 3, 80, 515, "mercX101", "mercX167", 32000, 140, 1205, 45, 1082, 50, 194, 15, 119, 10, 1301, 45, 6, 57, 61, 8, 142, 4, "swor", , , 50, "Bash", 5, 70, 0, 19, 0, "Stun", 5, 70, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0],
+ ["Barbarian", "1hs-Hell", 100, 29, 561, 5, 3, 80, 515, "mercX101", "mercX167", 32000, 140, 1205, 45, 1082, 50, 194, 15, 119, 10, 1301, 45, 6, 57, 61, 8, 142, 4, "swor", , , 50, "Bash", 5, 70, 0, 19, 0, "Stun", 5, 70, 0, 15, 0, , , , , , , , , , , , , , , , , , , , , , , , , 0, 0, 0, 0]
+ ];
});
diff --git a/libs/SoloPlay/Modules/MissileData.js b/libs/SoloPlay/Modules/MissileData.js
deleted file mode 100644
index be798b02..00000000
--- a/libs/SoloPlay/Modules/MissileData.js
+++ /dev/null
@@ -1,28 +0,0 @@
-(function (module, require) {
- /**
- * MissilesData
- */
- const MISSILES_COUNT = 385;
- const MissilesData = Array(MISSILES_COUNT);
-
- for (let i = 0; i < MissilesData.length; i++) {
- let index = i;
- MissilesData[i] = ({
- index: index,
- classID: index,
- internalName: getBaseStat("missiles", index, "Missile"),
- velocity: getBaseStat("missiles", index, "Vel"),
- velocityMax: getBaseStat("missiles", index, "MaxVel"),
- acceleration: getBaseStat("missiles", index, "Accel"),
- range: getBaseStat("missiles", index, "Range"),
- size: getBaseStat("missiles", index, "Size"),
- minDamage: getBaseStat("missiles", index, "MinDamage"),
- maxDamage: getBaseStat("missiles", index, "MaxDamage"),
- eType: getBaseStat("missiles", index, "EType"),
- eMin: getBaseStat("missiles", index, "EMin"),
- eMax: getBaseStat("missiles", index, "EMax"),
- cltSubMissiles: [getBaseStat("missiles", index, "CltSubMissile1"), getBaseStat("missiles", index, "CltSubMissile2"), getBaseStat("missiles", index, "CltSubMissile3")],
- });
- }
- module.exports = MissilesData;
-})(module, require);
diff --git a/libs/SoloPlay/Modules/Mock.js b/libs/SoloPlay/Modules/Mock.js
index d8276f0a..caeebb0a 100644
--- a/libs/SoloPlay/Modules/Mock.js
+++ b/libs/SoloPlay/Modules/Mock.js
@@ -1,194 +1,196 @@
+/* eslint-disable no-var */
var __extends = (this && this.__extends) || (function () {
- var extendStatics = function (d, b) {
- extendStatics = Object.setPrototypeOf ||
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
- return extendStatics(d, b);
- };
- return function (d, b) {
- if (typeof b !== "function" && b !== null)
- throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
- extendStatics(d, b);
- function __() { this.constructor = d; }
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
- };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
})();
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
- for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
- to[j] = from[i];
- return to;
+ for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) {
+ to[j] = from[i];
+ }
+ return to;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
+ return (mod && mod.__esModule) ? mod : { "default": mod };
};
(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", "./utilities", "../../modules/sdk"], 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", "./utilities", "../../modules/sdk"], factory);
+ }
})(function (require, exports) {
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.MockMonster = exports.MockPlayer = exports.MockItem = exports.Mockable = void 0;
- /**
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.MockMonster = exports.MockPlayer = exports.MockItem = exports.Mockable = void 0;
+ /**
* @description An savable/transferable item you can test with as if it where real
* @author Jaenster
*
*/
- var utilities_1 = require("./utilities");
- var sdk_1 = __importDefault(require("../../modules/sdk"));
- var Mockable = /** @class */ (function () {
- function Mockable(settings) {
- if (settings === void 0) { settings = {}; }
- this.overrides = { stats: [], skills: [], flags: 0, items: [], states: {} };
- this.settingKeys = [];
- this.settingKeys = Object.keys(settings);
- Object.assign(this, settings);
- }
- Mockable.prototype.internalGetStat = function (major, minor) {
- var _a;
- var stat = ((_a = this.overrides.stats) !== null && _a !== void 0 ? _a : []).find(function (_a) {
- var main = _a[0], sub = _a[1];
- return main === major && sub === (minor | 0);
- });
- return (stat === null || stat === void 0 ? void 0 : stat[2]) || 0;
- };
- Mockable.prototype.getStat = function (major, minor, extra) {
- var selfValue = this.internalGetStat(major, minor);
- var inventory = (this.getItems() || undefined);
- // Level requirement is the max of all items (so including sockets)
- if (major === sdk.stats.Levelreq) {
- return Math.max.apply(Math, __spreadArray([selfValue], inventory.map(function (el) { return el.getStat(sdk.stats.Levelreq); })));
- }
- var socketedStats = inventory.reduce(function (a, c) { return a + c.getStat.call(c, major, minor, extra); }, 0);
- return selfValue + socketedStats;
- };
- Mockable.prototype.getItems = function () {
- return this.overrides.items || [];
- };
- ;
- Mockable.prototype.toJSON = function () {
- var _this = this;
- var obj = {};
- this.settingKeys.forEach(function (key) {
- obj[key] = _this[key];
- });
- return JSON.stringify(obj);
- };
- Mockable.prototype.getState = function (id) {
- var _a;
- return ((_a = this.overrides.states) === null || _a === void 0 ? void 0 : _a[id]) || 0;
- };
- Mockable.prototype.getFlag = function (flags) {
- var _a;
- return !!(((_a = this.overrides.flags) !== null && _a !== void 0 ? _a : 0) & (flags | 0));
- };
- return Mockable;
- }());
- exports.Mockable = Mockable;
- // Put entire prototype of Unit in Mockable
- utilities_1.mixinFunctions(Mockable, Unit);
- var MockItem = /** @class */ (function (_super) {
- __extends(MockItem, _super);
- function MockItem() {
- return _super !== null && _super.apply(this, arguments) || this;
- }
- MockItem.getAllItemStats = function (item) {
- var stats = [];
- if (!item.getFlag(sdk.items.flags.Runeword)) {
- // since getStat(-1) is a perfect copy from item.getStat(major, minor), loop over it and get the real value
- // example, item.getStat(7, 0) != item.getStat(-1).find(([major])=> major === 7)[2]
- // its shifted with 8 bytes
- return (item.getStat(-1) || [])
- .map(function (_a) {
- var major = _a[0], minor = _a[1], value = _a[2];
- return [major, minor, item.getStat(major, minor)];
- });
- }
- for (var x = 0; x < 358; x++) {
- var zero = item.getStatEx(x, 0);
- zero && stats.push([x, 0, zero]);
- for (var y = 1; y < 281; y++) {
- var second = item.getStatEx(x, y);
- second && second !== zero && stats.push([x, y, second]);
- }
- }
- return stats;
- };
- MockItem.settingsGenerator = function (item, settings) {
- if (settings === void 0) { settings = {}; }
- // Add to settings
- var initializer = Object.keys(item)
- .filter(function (key) { return typeof item[key] !== 'function'; })
- .reduce(function (acc, key) {
- acc[key] = item[key];
- return acc;
- }, {});
- var stats = MockItem.getAllItemStats(item);
- initializer.overrides = {
- stats: stats.reduce(function (accumulator, _a) {
- var major = _a[0], minor = _a[1], value = _a[2];
- var socketable = item.getItemsEx().map(function (item) { return item.getStat(major, minor); }).reduce(function (a, c) { return a + c; }, 0) || 0;
- var realValue = value;
- if (major !== sdk.stats.Levelreq) {
- realValue = value - socketable;
- }
- if (realValue > 0) { // Only if this stat isn't given by a socketable
- accumulator.push([major, minor, value]);
- }
- return accumulator;
- }, []),
- flags: item.getFlags(),
- };
- return initializer;
- };
- MockItem.fromItem = function (item, settings) {
- if (settings === void 0) { settings = {}; }
- var initializer = this.settingsGenerator(item, settings);
- initializer.overrides.items = item.getItemsEx().map(function (item) { return MockItem.fromItem(item); });
- return new MockItem(initializer);
- };
- MockItem.fromPlayer = function (from) {
- if (from === void 0) { from = me; }
- return from.getItemsEx()
- .filter(function (item) { return item.location === sdk_1.default.storage.Equipment
- || (item.location === sdk_1.default.storage.Inventory && [603, 604, 605].indexOf(item.classid) > -1); })
- .map(function (x) { return MockItem.fromItem(x); });
- };
- return MockItem;
- }(Mockable));
- exports.MockItem = MockItem;
- var MockPlayer = /** @class */ (function (_super) {
- __extends(MockPlayer, _super);
- function MockPlayer() {
- return _super !== null && _super.apply(this, arguments) || this;
- }
- Object.defineProperty(MockPlayer.prototype, "maxhp", {
- get: function () {
- return this.getStat(sdk_1.default.stats.Maxhp) * (1 + (this.getStat(sdk_1.default.stats.MaxhpPercent) / 100));
- },
- enumerable: false,
- configurable: true
- });
- Object.defineProperty(MockPlayer.prototype, "maxmp", {
- get: function () {
- return this.getStat(sdk_1.default.stats.Maxhp) * (1 + (this.getStat(sdk_1.default.stats.MaxmanaPercent) / 100));
- },
- enumerable: false,
- configurable: true
- });
- return MockPlayer;
- }(Mockable));
- exports.MockPlayer = MockPlayer;
- var MockMonster = /** @class */ (function (_super) {
- __extends(MockMonster, _super);
- function MockMonster() {
- return _super !== null && _super.apply(this, arguments) || this;
- }
- return MockMonster;
- }(Mockable));
- exports.MockMonster = MockMonster;
+ var utilities_1 = require("./utilities");
+ var sdk_1 = __importDefault(require("../../modules/sdk"));
+ var Mockable = /** @class */ (function () {
+ function Mockable(settings) {
+ if (settings === void 0) { settings = {}; }
+ this.overrides = { stats: [], skills: [], flags: 0, items: [], states: {} };
+ this.settingKeys = [];
+ this.settingKeys = Object.keys(settings);
+ Object.assign(this, settings);
+ }
+ Mockable.prototype.internalGetStat = function (major, minor) {
+ var _a;
+ var stat = ((_a = this.overrides.stats) !== null && _a !== void 0 ? _a : []).find(function (_a) {
+ var main = _a[0], sub = _a[1];
+ return main === major && sub === (minor | 0);
+ });
+ return (stat === null || stat === void 0 ? void 0 : stat[2]) || 0;
+ };
+ Mockable.prototype.getStat = function (major, minor, extra) {
+ var selfValue = this.internalGetStat(major, minor);
+ var inventory = (this.getItems() || undefined);
+ // Level requirement is the max of all items (so including sockets)
+ if (major === sdk.stats.Levelreq) {
+ return Math.max.apply(Math, __spreadArray([selfValue], inventory.map(function (el) { return el.getStat(sdk.stats.Levelreq); })));
+ }
+ var socketedStats = inventory.reduce(function (a, c) { return a + c.getStat.call(c, major, minor, extra); }, 0);
+ return selfValue + socketedStats;
+ };
+ Mockable.prototype.getItems = function () {
+ return this.overrides.items || [];
+ };
+ Mockable.prototype.toJSON = function () {
+ var _this = this;
+ var obj = {};
+ this.settingKeys.forEach(function (key) {
+ obj[key] = _this[key];
+ });
+ return JSON.stringify(obj);
+ };
+ Mockable.prototype.getState = function (id) {
+ var _a;
+ return ((_a = this.overrides.states) === null || _a === void 0 ? void 0 : _a[id]) || 0;
+ };
+ Mockable.prototype.getFlag = function (flags) {
+ var _a;
+ return !!(((_a = this.overrides.flags) !== null && _a !== void 0 ? _a : 0) & (flags | 0));
+ };
+ return Mockable;
+ }());
+ exports.Mockable = Mockable;
+ // Put entire prototype of Unit in Mockable
+ utilities_1.mixinFunctions(Mockable, Unit);
+ var MockItem = /** @class */ (function (_super) {
+ __extends(MockItem, _super);
+ function MockItem() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ MockItem.getAllItemStats = function (item) {
+ var stats = [];
+ if (!item.getFlag(sdk.items.flags.Runeword)) {
+ // since getStat(-1) is a perfect copy from item.getStat(major, minor), loop over it and get the real value
+ // example, item.getStat(7, 0) != item.getStat(-1).find(([major])=> major === 7)[2]
+ // its shifted with 8 bytes
+ return (item.getStat(-1) || [])
+ .map(function (_a) {
+ // eslint-disable-next-line no-unused-vars
+ var major = _a[0], minor = _a[1], value = _a[2];
+ return [major, minor, item.getStat(major, minor)];
+ });
+ }
+ for (var x = 0; x < 358; x++) {
+ var zero = item.getStatEx(x, 0);
+ zero && stats.push([x, 0, zero]);
+ for (var y = 1; y < 281; y++) {
+ var second = item.getStatEx(x, y);
+ second && second !== zero && stats.push([x, y, second]);
+ }
+ }
+ return stats;
+ };
+ MockItem.settingsGenerator = function (item, settings) {
+ if (settings === void 0) { settings = {}; }
+ // Add to settings
+ var initializer = Object.keys(item)
+ .filter(function (key) { return typeof item[key] !== "function"; })
+ .reduce(function (acc, key) {
+ acc[key] = item[key];
+ return acc;
+ }, {});
+ var stats = MockItem.getAllItemStats(item);
+ initializer.overrides = {
+ stats: stats.reduce(function (accumulator, _a) {
+ var major = _a[0], minor = _a[1], value = _a[2];
+ var socketable = item.getItemsEx().map(function (item) { return item.getStat(major, minor); }).reduce(function (a, c) { return a + c; }, 0) || 0;
+ var realValue = value;
+ if (major !== sdk.stats.Levelreq) {
+ realValue = value - socketable;
+ }
+ if (realValue > 0) { // Only if this stat isn't given by a socketable
+ accumulator.push([major, minor, value]);
+ }
+ return accumulator;
+ }, []),
+ flags: item.getFlags(),
+ };
+ return initializer;
+ };
+ MockItem.fromItem = function (item, settings) {
+ if (settings === void 0) { settings = {}; }
+ var initializer = this.settingsGenerator(item, settings);
+ initializer.overrides.items = item.getItemsEx().map(function (item) { return MockItem.fromItem(item); });
+ return new MockItem(initializer);
+ };
+ MockItem.fromPlayer = function (from) {
+ if (from === void 0) { from = me; }
+ return from.getItemsEx()
+ .filter(function (item) {
+ return item.location === sdk_1.default.storage.Equipment
+ || (item.location === sdk_1.default.storage.Inventory && [603, 604, 605].indexOf(item.classid) > -1);
+ })
+ .map(function (x) { return MockItem.fromItem(x); });
+ };
+ return MockItem;
+ }(Mockable));
+ exports.MockItem = MockItem;
+ var MockPlayer = /** @class */ (function (_super) {
+ __extends(MockPlayer, _super);
+ function MockPlayer() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ Object.defineProperty(MockPlayer.prototype, "maxhp", {
+ get: function () {
+ return this.getStat(sdk_1.default.stats.Maxhp) * (1 + (this.getStat(sdk_1.default.stats.MaxhpPercent) / 100));
+ },
+ enumerable: false,
+ configurable: true
+ });
+ Object.defineProperty(MockPlayer.prototype, "maxmp", {
+ get: function () {
+ return this.getStat(sdk_1.default.stats.Maxhp) * (1 + (this.getStat(sdk_1.default.stats.MaxmanaPercent) / 100));
+ },
+ enumerable: false,
+ configurable: true
+ });
+ return MockPlayer;
+ }(Mockable));
+ exports.MockPlayer = MockPlayer;
+ var MockMonster = /** @class */ (function (_super) {
+ __extends(MockMonster, _super);
+ function MockMonster() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ return MockMonster;
+ }(Mockable));
+ exports.MockMonster = MockMonster;
});
diff --git a/libs/SoloPlay/Modules/MockItem.js b/libs/SoloPlay/Modules/MockItem.js
index 10f19026..b0cb8ede 100644
--- a/libs/SoloPlay/Modules/MockItem.js
+++ b/libs/SoloPlay/Modules/MockItem.js
@@ -7,203 +7,203 @@
(function (module, require) {
- var defaultSettings = {
- base: 0, // Can be an item it extends
- type: 4,
- classid: 0,
- mode: 0,
- name: 0,
- act: 0,
- gid: 0,
- x: 0,
- y: 0,
- targetx: 0,
- targety: 0,
- area: 0,
- hp: 0,
- hpmax: 0,
- mp: 0,
- mpmax: 0,
- stamina: 0,
- staminamax: 0,
- charlvl: 0,
- itemcount: 0,
- owner: 0,
- ownertype: 0,
- spectype: 0,
- direction: 0,
- uniqueid: 0,
- code: 0,
- prefix: 0,
- suffix: 0,
- prefixes: 0,
- suffixes: 0,
- prefixnum: 0,
- suffixnum: 0,
- prefixnums: 0,
- suffixnums: 0,
- fname: 0,
- quality: 0,
- node: 0,
- location: 0,
- sizex: 0,
- sizey: 0,
- itemType: 0,
- description: 0,
- bodylocation: 0,
- ilvl: 0,
- lvlreq: 0,
- gfx: 0,
- runwalk: 0,
- weaponswitch: 0,
- objtype: 0,
- islocked: 0,
- getColor: 0,
- socketedWith: [],
-
- overrides: {stats: {}},
- };
-
- /**
- * @static fromItem
- * @static fromGear
- *
- * @param settings
- * @constructor
- */
- function MockItem(settings = {}) {
- if (typeof settings !== 'object' && settings) { settings = {}; }
- this.overrides = { stats: [], skills: [], flags: 0, items: [], states: {} };
- this.settingKeys = [];
- this.settingKeys = Object.keys(settings);
- Object.assign(this, settings);
-
- Object.keys(Unit.prototype).forEach(k => {
- typeof Unit.prototype === 'function' && (this[k] = (...args) => {
- Unit.prototype.apply(this, args);
- });
- });
-
- this.internalGetStat = function (major, minor) {
- var _a;
- var stat = ((_a = this.overrides.stats) !== null && _a !== void 0 ? _a : []).find(function (_a) {
- var main = _a[0], sub = _a[1];
- return main === major && sub === (minor | 0);
- });
- return (stat === null || stat === void 0 ? void 0 : stat[2]) || 0;
- };
-
- this.getStat = function (major, minor, extra) {
- var selfValue = this.internalGetStat(major, minor);
- var inventory = (this.getItems() || undefined);
- // Level requirement is the max of all items (so including sockets)
- let original = typeof this.base === 'object' && this.base.hasOwnProperty('getStat') && this.base.getStat.apply(this.base, [major, minor]) || 0;
- if (major === sdk.stats.Levelreq) {
- return Math.max.apply(Math, __spreadArray([selfValue], inventory.map(function (el) { return el.getStat(sdk.stats.Levelreq); })));
- }
- var socketedStats = inventory.reduce(function (a, c) { return a + c.getStat.call(c, major, minor, extra); }, 0);
- return original + selfValue + socketedStats;
- };
-
- this.getItems = function () {
- return this.overrides.items || [];
- };
-
- this.toJSON = function () {
- var _this = this;
- var obj = {};
- this.settingKeys.forEach(function (key) {
- obj[key] = _this[key];
- });
- return JSON.stringify(obj);
- };
- this.getState = function (id) {
- var _a;
- return ((_a = this.overrides.states) === null || _a === void 0 ? void 0 : _a[id]) || 0;
- };
- this.getFlag = function (flags) {
- var _a;
- return !!(((_a = this.overrides.flags) !== null && _a !== void 0 ? _a : 0) & (flags | 0));
- };
-
- this.getItemsEx = function () {
- return this.socketedWith;
- };
-
- // make it work with pickit lines
- this.getStatEx = function (major, minor) {
- return Unit.prototype.getStatEx.apply(this, [major, minor]);
- };
-
- this.store = () => JSON.stringify(Object.keys(settings).reduce((a, key) => a[key] = this[key], {}));
-
- Object.keys(Unit.prototype)
- .filter(key => typeof this[key] === 'undefined')
- .forEach(key => this[key] = (...args) => Unit.prototype[key].apply(this, args));
- }
-
- MockItem.getAllItemStats = function (item) {
- var stats = [];
- if (!item.getFlag(sdk.items.flags.Runeword)) {
- // since getStat(-1) is a perfect copy from item.getStat(major, minor), loop over it and get the real value
- // example, item.getStat(7, 0) != item.getStat(-1).find(([major])=> major === 7)[2]
- // its shifted with 8 bytes
- return (item.getStat(-1) || [])
- .map(function (_a) {
- var major = _a[0], minor = _a[1], value = _a[2];
- return [major, minor, item.getStat(major, minor)];
- });
- }
- for (var x = 0; x < 358; x++) {
- var zero = item.getStatEx(x, 0);
- zero && stats.push([x, 0, zero]);
- for (var y = 1; y < 281; y++) {
- var second = item.getStatEx(x, y);
- second && second !== zero && stats.push([x, y, second]);
- }
- }
- return stats;
- };
-
- MockItem.fromItem = function (item, settings) {
- if (settings === void 0) { settings = {}; }
- console.log(JSON.stringify(settings));
- Object.keys(item).forEach(key => settings[key] = item[key]);
- console.log(JSON.stringify(settings));
- settings.socketedWith = item.getItemsEx().map(item => MockItem.fromItem(item)) || []; // Mock its sockets too
- var initializer = Object.keys(item)
- .filter(function (key) { return typeof item[key] !== 'function'; })
- .reduce(function (acc, key) {
- acc[key] = item[key];
- return acc;
- }, {});
- var stats = MockItem.getAllItemStats(item);
- initializer.overrides = {
- stats: stats.reduce(function (accumulator, _a) {
- var major = _a[0], minor = _a[1], value = _a[2];
- var socketable = item.getItemsEx().map(item => item.getStat(major, minor)).reduce((a, c) => a + c, 0) || 0;
- var realValue = value;
- if (major !== sdk.stats.Levelreq) {
- realValue = value - socketable;
- }
- if (realValue > 0) { // Only if this stat isn't given by a socketable
- accumulator.push([major, minor, value]);
- }
- return accumulator;
- }, []),
- flags: item.getFlag(),
- };
- initializer.overrides.items = item.getItemsEx().map(function (item) { return MockItem.fromItem(item); });
- return new MockItem(initializer);
- };
-
- MockItem.fromGear = function () {
- return me.getItemsEx()
- .filter(item => item.location === sdk.storage.Equipped
- || (item.location === sdk.storage.Inventory && [603, 604, 605].indexOf(item.classid) > -1))
- .map(x => MockItem.fromItem(x));
- };
-
- module.exports = MockItem;
+ var defaultSettings = {
+ base: 0, // Can be an item it extends
+ type: 4,
+ classid: 0,
+ mode: 0,
+ name: 0,
+ act: 0,
+ gid: 0,
+ x: 0,
+ y: 0,
+ targetx: 0,
+ targety: 0,
+ area: 0,
+ hp: 0,
+ hpmax: 0,
+ mp: 0,
+ mpmax: 0,
+ stamina: 0,
+ staminamax: 0,
+ charlvl: 0,
+ itemcount: 0,
+ owner: 0,
+ ownertype: 0,
+ spectype: 0,
+ direction: 0,
+ uniqueid: 0,
+ code: 0,
+ prefix: 0,
+ suffix: 0,
+ prefixes: 0,
+ suffixes: 0,
+ prefixnum: 0,
+ suffixnum: 0,
+ prefixnums: 0,
+ suffixnums: 0,
+ fname: 0,
+ quality: 0,
+ node: 0,
+ location: 0,
+ sizex: 0,
+ sizey: 0,
+ itemType: 0,
+ description: 0,
+ bodylocation: 0,
+ ilvl: 0,
+ lvlreq: 0,
+ gfx: 0,
+ runwalk: 0,
+ weaponswitch: 0,
+ objtype: 0,
+ islocked: 0,
+ getColor: 0,
+ socketedWith: [],
+
+ overrides: {stats: {}},
+ };
+
+ /**
+ * @static fromItem
+ * @static fromGear
+ *
+ * @param settings
+ * @constructor
+ */
+ function MockItem(settings = {}) {
+ if (typeof settings !== 'object' && settings) { settings = {}; }
+ this.overrides = { stats: [], skills: [], flags: 0, items: [], states: {} };
+ this.settingKeys = [];
+ this.settingKeys = Object.keys(settings);
+ Object.assign(this, settings);
+
+ Object.keys(Unit.prototype).forEach(k => {
+ typeof Unit.prototype === 'function' && (this[k] = (...args) => {
+ Unit.prototype.apply(this, args);
+ });
+ });
+
+ this.internalGetStat = function (major, minor) {
+ var _a;
+ var stat = ((_a = this.overrides.stats) !== null && _a !== void 0 ? _a : []).find(function (_a) {
+ var main = _a[0], sub = _a[1];
+ return main === major && sub === (minor | 0);
+ });
+ return (stat === null || stat === void 0 ? void 0 : stat[2]) || 0;
+ };
+
+ this.getStat = function (major, minor, extra) {
+ var selfValue = this.internalGetStat(major, minor);
+ var inventory = (this.getItems() || undefined);
+ // Level requirement is the max of all items (so including sockets)
+ let original = typeof this.base === 'object' && this.base.hasOwnProperty('getStat') && this.base.getStat.apply(this.base, [major, minor]) || 0;
+ if (major === sdk.stats.Levelreq) {
+ return Math.max.apply(Math, __spreadArray([selfValue], inventory.map(function (el) { return el.getStat(sdk.stats.Levelreq); })));
+ }
+ var socketedStats = inventory.reduce(function (a, c) { return a + c.getStat.call(c, major, minor, extra); }, 0);
+ return original + selfValue + socketedStats;
+ };
+
+ this.getItems = function () {
+ return this.overrides.items || [];
+ };
+
+ this.toJSON = function () {
+ var _this = this;
+ var obj = {};
+ this.settingKeys.forEach(function (key) {
+ obj[key] = _this[key];
+ });
+ return JSON.stringify(obj);
+ };
+ this.getState = function (id) {
+ var _a;
+ return ((_a = this.overrides.states) === null || _a === void 0 ? void 0 : _a[id]) || 0;
+ };
+ this.getFlag = function (flags) {
+ var _a;
+ return !!(((_a = this.overrides.flags) !== null && _a !== void 0 ? _a : 0) & (flags | 0));
+ };
+
+ this.getItemsEx = function () {
+ return this.socketedWith;
+ };
+
+ // make it work with pickit lines
+ this.getStatEx = function (major, minor) {
+ return Unit.prototype.getStatEx.apply(this, [major, minor]);
+ };
+
+ this.store = () => JSON.stringify(Object.keys(settings).reduce((a, key) => a[key] = this[key], {}));
+
+ Object.keys(Unit.prototype)
+ .filter(key => typeof this[key] === 'undefined')
+ .forEach(key => this[key] = (...args) => Unit.prototype[key].apply(this, args));
+ }
+
+ MockItem.getAllItemStats = function (item) {
+ var stats = [];
+ if (!item.getFlag(sdk.items.flags.Runeword)) {
+ // since getStat(-1) is a perfect copy from item.getStat(major, minor), loop over it and get the real value
+ // example, item.getStat(7, 0) != item.getStat(-1).find(([major])=> major === 7)[2]
+ // its shifted with 8 bytes
+ return (item.getStat(-1) || [])
+ .map(function (_a) {
+ var major = _a[0], minor = _a[1], value = _a[2];
+ return [major, minor, item.getStat(major, minor)];
+ });
+ }
+ for (var x = 0; x < 358; x++) {
+ var zero = item.getStatEx(x, 0);
+ zero && stats.push([x, 0, zero]);
+ for (var y = 1; y < 281; y++) {
+ var second = item.getStatEx(x, y);
+ second && second !== zero && stats.push([x, y, second]);
+ }
+ }
+ return stats;
+ };
+
+ MockItem.fromItem = function (item, settings) {
+ if (settings === void 0) { settings = {}; }
+ console.log(JSON.stringify(settings));
+ Object.keys(item).forEach(key => settings[key] = item[key]);
+ console.log(JSON.stringify(settings));
+ settings.socketedWith = item.getItemsEx().map(item => MockItem.fromItem(item)) || []; // Mock its sockets too
+ var initializer = Object.keys(item)
+ .filter(function (key) { return typeof item[key] !== 'function'; })
+ .reduce(function (acc, key) {
+ acc[key] = item[key];
+ return acc;
+ }, {});
+ var stats = MockItem.getAllItemStats(item);
+ initializer.overrides = {
+ stats: stats.reduce(function (accumulator, _a) {
+ var major = _a[0], minor = _a[1], value = _a[2];
+ var socketable = item.getItemsEx().map(item => item.getStat(major, minor)).reduce((a, c) => a + c, 0) || 0;
+ var realValue = value;
+ if (major !== sdk.stats.Levelreq) {
+ realValue = value - socketable;
+ }
+ if (realValue > 0) { // Only if this stat isn't given by a socketable
+ accumulator.push([major, minor, value]);
+ }
+ return accumulator;
+ }, []),
+ flags: item.getFlag(),
+ };
+ initializer.overrides.items = item.getItemsEx().map(function (item) { return MockItem.fromItem(item); });
+ return new MockItem(initializer);
+ };
+
+ MockItem.fromGear = function () {
+ return me.getItemsEx()
+ .filter(item => item.location === sdk.storage.Equipped
+ || (item.location === sdk.storage.Inventory && [603, 604, 605].indexOf(item.classid) > -1))
+ .map(x => MockItem.fromItem(x));
+ };
+
+ module.exports = MockItem;
}).call(null, module, require);
diff --git a/libs/SoloPlay/Modules/MonsterData.js b/libs/SoloPlay/Modules/MonsterData.js
deleted file mode 100644
index 322b7977..00000000
--- a/libs/SoloPlay/Modules/MonsterData.js
+++ /dev/null
@@ -1,109 +0,0 @@
-(function (module, require) {
- const LocaleStringName = require("./LocaleStringID").LocaleStringName;
- const MONSTER_INDEX_COUNT = 770;
- /**
- * @typedef MonsterDataObj
- * @type {object}
- * @property {number} Index = Index of this monster
- * @property {number} ClassID = classid of this monster
- * @property {number} Type = Type of monster
- * @property {number} Level = Level of this monster in normal (use GameData.monsterLevel to find monster levels)
- * @property {boolean} Ranged = if monster is ranged
- * @property {number} Rarity = weight of this monster in level generation
- * @property {number} Threat = threat level used by mercs
- * @property {number} Align = alignment of unit (determines what it will attack)
- * @property {boolean} Melee = if monster is melee
- * @property {boolean} NPC = if unit is NPC
- * @property {boolean} Demon = if monster is demon
- * @property {boolean} Flying = if monster is flying
- * @property {boolean} Boss = if monster is a boss
- * @property {boolean} ActBoss = if monster is act boss
- * @property {boolean} Killable = if monster can be killed
- * @property {boolean} Convertable = if monster is affected by convert or mind blast
- * @property {boolean} NeverCount = if not counted as a minion
- * @property {number} DeathDamage = explodes on death
- * @property {number} Regeneration = hp regeneration
- * @property {number} LocaleString = locale string index for getLocaleString
- * @property {number} ExperienceModifier = percent of base monster exp this unit rewards when killed
- * @property {number} Undead = 2 if greater undead, 1 if lesser undead, 0 if neither
- * @property {number} Drain = drain effectiveness percent
- * @property {number} Block = block percent
- * @property {number} Physical = physical resist
- * @property {number} Magic = magic resist
- * @property {number} Fire = fire resist
- * @property {number} Lightning = lightning resist
- * @property {number} Poison = poison resist
- * @property {number[]} Minions = array of minions that can spawn with this unit
- * @property {number} MinionCount.Min = minimum number of minions that can spawn with this unit
- * @property {number} MinionCount.Max = maximum number of minions that can spawn with this unit
- */
-
- /** @type {MonsterDataObj[]} */
- const MonsterData = Array(MONSTER_INDEX_COUNT);
-
- for (let i = 0; i < MonsterData.length; i++) {
- let index = i;
-
- MonsterData[i] = ({
- Index: index,
- ClassID: index,
- Type: getBaseStat("monstats", index, "MonType"),
- Level: getBaseStat("monstats", index, "Level"), // normal only, nm/hell are determined by area's LevelEx
- Ranged: getBaseStat("monstats", index, "RangedType"),
- Rarity: getBaseStat("monstats", index, "Rarity"),
- Threat: getBaseStat("monstats", index, "threat"),
- PetIgnore: getBaseStat("monstats", index, "petignore"),
- Align: getBaseStat("monstats", index, "Align"),
- Melee: getBaseStat("monstats", index, "isMelee"),
- NPC: getBaseStat("monstats", index, "npc"),
- Demon: getBaseStat("monstats", index, "demon"),
- Flying: getBaseStat("monstats", index, "flying"),
- Boss: getBaseStat("monstats", index, "boss"),
- ActBoss: getBaseStat("monstats", index, "primeevil"),
- Killable: getBaseStat("monstats", index, "killable"),
- Convertable: getBaseStat("monstats", index, "switchai"),
- NeverCount: getBaseStat("monstats", index, "neverCount"),
- DeathDamage: getBaseStat("monstats", index, "deathDmg"),
- Regeneration: getBaseStat("monstats", index, "DamageRegen"),
- LocaleString: getLocaleString(getBaseStat("monstats", index, "NameStr")),
- InternalName: LocaleStringName[getBaseStat("monstats", index, "NameStr")],
- ExperienceModifier: getBaseStat("monstats", index, ["Exp", "Exp(N)", "Exp(H)"][me.diff]),
- Undead: (getBaseStat("monstats", index, "hUndead") && 2) | (getBaseStat("monstats", index, "lUndead") && 1),
- Drain: getBaseStat("monstats", index, ["Drain", "Drain(N)", "Drain(H)"][me.diff]),
- Block: getBaseStat("monstats", index, ["ToBlock", "ToBlock(N)", "ToBlock(H)"][me.diff]),
- Physical: getBaseStat("monstats", index, ["ResDm", "ResDm(N)", "ResDm(H)"][me.diff]),
- Magic: getBaseStat("monstats", index, ["ResMa", "ResMa(N)", "ResMa(H)"][me.diff]),
- Fire: getBaseStat("monstats", index, ["ResFi", "ResFi(N)", "ResFi(H)"][me.diff]),
- Lightning: getBaseStat("monstats", index, ["ResLi", "ResLi(N)", "ResLi(H)"][me.diff]),
- Cold: getBaseStat("monstats", index, ["ResCo", "ResCo(N)", "ResCo(H)"][me.diff]),
- Poison: getBaseStat("monstats", index, ["ResPo", "ResPo(N)", "ResPo(H)"][me.diff]),
- Minions: ([getBaseStat("monstats", index, "minion1"), getBaseStat("monstats", index, "minion2")].filter(mon => mon !== 65535)),
- GroupCount: ({
- Min: getBaseStat("monstats", index, "MinGrp"),
- Max: getBaseStat("monstats", index, "MaxGrp")
- }),
- MinionCount: ({
- Min: getBaseStat("monstats", index, "PartyMin"),
- Max: getBaseStat("monstats", index, "PartyMax")
- }),
- Velocity: getBaseStat("monstats", index, "Velocity"),
- Run: getBaseStat("monstats", index, "Run"),
- SizeX: getBaseStat("monstats", index, "SizeX"),
- SizeY: getBaseStat("monstats", index, "SizeY"),
- Attack1MinDmg: getBaseStat("monstats", index, ["A1MinD", "A1MinD(N)", "A1MinD(H)"][me.diff]),
- Attack1MaxDmg: getBaseStat("monstats", index, ["A1MaxD", "A1MaxD(N)", "A1MaxD(H)"][me.diff]),
- Attack2MinDmg: getBaseStat("monstats", index, ["A2MinD", "A2MinD(N)", "A2MinD(H)"][me.diff]),
- Attack2MaxDmg: getBaseStat("monstats", index, ["A2MaxD", "A2MaxD(N)", "A2MaxD(H)"][me.diff]),
- Skill1MinDmg: getBaseStat("monstats", index, ["S1MinD", "S1MinD(N)", "S1MinD(H)"][me.diff]),
- Skill1MaxDmg: getBaseStat("monstats", index, ["S1MaxD", "S1MaxD(N)", "S1MaxD(H)"][me.diff]),
- });
- }
-
- MonsterData.findByName = function (whatToFind) {
- let matches = MonsterData.map(mon => [Math.min(whatToFind.diffCount(mon.LocaleString), whatToFind.diffCount(mon.InternalName)), mon]).sort((a, b) => a[0] - b[0]);
-
- return matches[0][1];
- };
-
- module.exports = MonsterData;
-})(module, require);
diff --git a/libs/SoloPlay/Modules/MoveTo.js b/libs/SoloPlay/Modules/MoveTo.js
index bc3754de..abb563bd 100644
--- a/libs/SoloPlay/Modules/MoveTo.js
+++ b/libs/SoloPlay/Modules/MoveTo.js
@@ -1,300 +1,300 @@
// eslint-disable-next-line no-var
var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
+ return (mod && mod.__esModule) ? mod : { "default": mod };
};
(function (factory) {
- if (typeof module === "object" && typeof module.exports === "object") {
- let v = factory(require, exports);
- if (v !== undefined) module.exports = v;
- } else if (typeof define === "function" && define.amd) {
- define(["require", "exports", "./Clear", "../../modules/sdk"], factory);
- }
+ if (typeof module === "object" && typeof module.exports === "object") {
+ let v = factory(require, exports);
+ if (v !== undefined) module.exports = v;
+ } else if (typeof define === "function" && define.amd) {
+ define(["require", "exports", "./Clear", "../../modules/sdk"], factory);
+ }
})(function (require, exports) {
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.currentWalkingPath = exports.getWalkDistance = void 0;
- const clear_1 = __importDefault(require("./Clear"));
- const sdk_1 = __importDefault(require("../../modules/sdk"));
- const getWalkDistance = function (x, y, area, xx, yy) {
- if (area === void 0) { area = me.area; }
- if (xx === void 0) { xx = me.x; }
- if (yy === void 0) { yy = me.y; }
- // distance between node x and x-1
- let path = getPath(area, x, y, xx, yy, 2, 5);
- return path && path.map(function (e, i, s) { return i && getDistance(s[i - 1], e) || 0; })
- .reduce(function (acc, cur) { return acc + cur; }, 0) || Infinity;
- };
- exports.getWalkDistance = getWalkDistance;
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.currentWalkingPath = exports.getWalkDistance = void 0;
+ const clear_1 = __importDefault(require("./Clear"));
+ const sdk_1 = __importDefault(require("../../modules/sdk"));
+ const getWalkDistance = function (x, y, area, xx, yy) {
+ if (area === void 0) { area = me.area; }
+ if (xx === void 0) { xx = me.x; }
+ if (yy === void 0) { yy = me.y; }
+ // distance between node x and x-1
+ let path = getPath(area, x, y, xx, yy, 2, 5);
+ return path && path.map(function (e, i, s) { return i && getDistance(s[i - 1], e) || 0; })
+ .reduce(function (acc, cur) { return acc + cur; }, 0) || Infinity;
+ };
+ exports.getWalkDistance = getWalkDistance;
- let skipShrine = [];
- exports.currentWalkingPath = [];
- function moveTo(target, givenSettings) {
- let _a;
- let settings = Object.assign({}, {
- allowTeleport: true,
- startIndex: 0,
- rangeOverride: null,
- callback: undefined,
- allowClearing: true,
- clearFilter: function (m, n) { return getDistance(m, n) <= 14; },
- }, givenSettings);
- let stateForShrine = function (id) {
- if (id >= sdk_1.default.shrines.Armor && id <= sdk_1.default.shrines.Experience) {
- return id + 122;
- }
- return 0;
- };
- let searchShrine = function () {
- return getUnits(2, "shrine")
- .filter(function (el) { return el.mode === sdk.objects.mode.Inactive && Config.ScanShrines.includes(el.objtype); })
- .filter(function (el) {
- // Dont do anything with shrines we already found
- if (skipShrine.includes(el.gid)) return false;
- let currentIndex = Config.ScanShrines.findIndex((s) => me.getState(stateForShrine(s)));
- let index = Config.ScanShrines.indexOf(el.objtype);
- if (currentIndex === -1 || index <= currentIndex || stateForShrine(el.objtype) === 0) {
- if (el.objtype !== sdk_1.default.shrines.Mana || me.mpPercent >= 50) {
- return true;
- } else {
- return getDistance(me, el) <= 10;
- }
- }
- return false;
- })
- .filter(function (el) { return Pather.getWalkDistance(el.x, el.y, el.area, me.x, me.y, 0, 5) <= 40; })
- .sort(function (a, b) { return (Config.ScanShrines.indexOf(a.objtype) - Config.ScanShrines.indexOf(b.objtype)) || a.distance - b.distance; })
- .first();
- };
- // convert presetunit to x,y target
- if (target instanceof PresetUnit) {
- target = { x: target.roomx * 5 + target.x, y: target.roomy * 5 + target.y };
- }
+ let skipShrine = [];
+ exports.currentWalkingPath = [];
+ function moveTo(target, givenSettings) {
+ let _a;
+ let settings = Object.assign({}, {
+ allowTeleport: true,
+ startIndex: 0,
+ rangeOverride: null,
+ callback: undefined,
+ allowClearing: true,
+ clearFilter: function (m, n) { return getDistance(m, n) <= 14; },
+ }, givenSettings);
+ let stateForShrine = function (id) {
+ if (id >= sdk_1.default.shrines.Armor && id <= sdk_1.default.shrines.Experience) {
+ return id + 122;
+ }
+ return 0;
+ };
+ let searchShrine = function () {
+ return getUnits(2, "shrine")
+ .filter(function (el) { return el.mode === sdk.objects.mode.Inactive && Config.ScanShrines.includes(el.objtype); })
+ .filter(function (el) {
+ // Dont do anything with shrines we already found
+ if (skipShrine.includes(el.gid)) return false;
+ let currentIndex = Config.ScanShrines.findIndex((s) => me.getState(stateForShrine(s)));
+ let index = Config.ScanShrines.indexOf(el.objtype);
+ if (currentIndex === -1 || index <= currentIndex || stateForShrine(el.objtype) === 0) {
+ if (el.objtype !== sdk_1.default.shrines.Mana || me.mpPercent >= 50) {
+ return true;
+ } else {
+ return getDistance(me, el) <= 10;
+ }
+ }
+ return false;
+ })
+ .filter(function (el) { return Pather.getWalkDistance(el.x, el.y, el.area, me.x, me.y, 0, 5) <= 40; })
+ .sort(function (a, b) { return (Config.ScanShrines.indexOf(a.objtype) - Config.ScanShrines.indexOf(b.objtype)) || a.distance - b.distance; })
+ .first();
+ };
+ // convert presetunit to x,y target
+ if (target instanceof PresetUnit) {
+ target = { x: target.roomx * 5 + target.x, y: target.roomy * 5 + target.y };
+ }
- let canTeleport = settings.allowTeleport && Pather.useTeleport();
- let clearPercentage = 100, didSkipTown = false;
- // To fix recursion issues
- let _prevpath = exports.currentWalkingPath;
- try {
- if (!Array.isArray(target)) {
- target = [target];
- }
+ let canTeleport = settings.allowTeleport && Pather.useTeleport();
+ let clearPercentage = 100, didSkipTown = false;
+ // To fix recursion issues
+ let _prevpath = exports.currentWalkingPath;
+ try {
+ if (!Array.isArray(target)) {
+ target = [target];
+ }
- let path_1 = target.map(function (target, index, self) {
- // The next node starts with the last node
- let fromx = !index ? me.x : self[index - 1].x, fromy = !index ? me.y : self[index - 1].y;
- // avoid d2bs issues
- if (typeof target.hook === "undefined") {
- target.hook = undefined;
- }
- let path = (getPath(me.area, target.x, target.y, fromx, fromy, 2, 4) || []);
- // sometimes the reduction path messes us that we dont have any path left to take (bugs in arcane)
- if (!path.length) {
- path = (getPath(me.area, target.x, target.y, fromx, fromy, 0, 4) || []);
- }
- return path.map(function (el, idx) {
- // last index of the path gets the hook. Since path is in reverse order, last node is idx 0
- if (idx === 0 && target.hook) {
- console.log("Assign the current hook -> ", target.hook);
- return { x: el.x, y: el.y, index: index, hook: target.hook };
- }
- // normal ones dont -> hook: undefined to avoid d2bs issues
- return { x: el.x, y: el.y, index: index, hook: undefined };
- });
- }).reduce(function (cur, acc) {
- // push each node to the list
- cur.forEach(function (el) { return acc.push(el); });
- return acc;
- }, []);
- if (!path_1) {
- throw new Error("failed to generate path");
- }
+ let path_1 = target.map(function (target, index, self) {
+ // The next node starts with the last node
+ let fromx = !index ? me.x : self[index - 1].x, fromy = !index ? me.y : self[index - 1].y;
+ // avoid d2bs issues
+ if (typeof target.hook === "undefined") {
+ target.hook = undefined;
+ }
+ let path = (getPath(me.area, target.x, target.y, fromx, fromy, 2, 4) || []);
+ // sometimes the reduction path messes us that we dont have any path left to take (bugs in arcane)
+ if (!path.length) {
+ path = (getPath(me.area, target.x, target.y, fromx, fromy, 0, 4) || []);
+ }
+ return path.map(function (el, idx) {
+ // last index of the path gets the hook. Since path is in reverse order, last node is idx 0
+ if (idx === 0 && target.hook) {
+ console.log("Assign the current hook -> ", target.hook);
+ return { x: el.x, y: el.y, index: index, hook: target.hook };
+ }
+ // normal ones dont -> hook: undefined to avoid d2bs issues
+ return { x: el.x, y: el.y, index: index, hook: undefined };
+ });
+ }).reduce(function (cur, acc) {
+ // push each node to the list
+ cur.forEach(function (el) { return acc.push(el); });
+ return acc;
+ }, []);
+ if (!path_1) {
+ throw new Error("failed to generate path");
+ }
- path_1.reverse();
- let lines = path_1.map(function (node, i, self) { return i /*skip first*/ && new Line(self[i - 1].x, self[i - 1].y, node.x, node.y, 0x33, true); });
- path_1.forEach(function (el, idx) {
- if (el.hook && idx) {
- console.log("path ", idx, "has a hook");
- }
- });
- exports.currentWalkingPath = path_1;
- let pathCopy = path_1.slice();
- // find where to start (usefull to render a long path with nodes to walk back
- let startIndex = path_1.findIndex(function (path) { return path.index === settings.startIndex; });
- if (startIndex > -1) {
- console.log("start idnex");
- }
+ path_1.reverse();
+ let lines = path_1.map(function (node, i, self) { return i /*skip first*/ && new Line(self[i - 1].x, self[i - 1].y, node.x, node.y, 0x33, true); });
+ path_1.forEach(function (el, idx) {
+ if (el.hook && idx) {
+ console.log("path ", idx, "has a hook");
+ }
+ });
+ exports.currentWalkingPath = path_1;
+ let pathCopy = path_1.slice();
+ // find where to start (usefull to render a long path with nodes to walk back
+ let startIndex = path_1.findIndex(function (path) { return path.index === settings.startIndex; });
+ if (startIndex > -1) {
+ console.log("start idnex");
+ }
- let loops = 0, shrine_1;
- let _loop_1 = function (i, node, l) {
- if (settings.allowClearing && settings.clearFilter && canTeleport) {
- j = i + 1;
- let monsters = getUnits(sdk_1.default.unittype.Monster)
- .filter(function (m) { return m.attackable && settings.clearFilter(m, path_1[j]); });
- while (j < path_1.length && !path_1[j].hook && monsters.length === 0 && exports.getWalkDistance(path_1[j].x, path_1[j].y) < 100 - 14 && settings.allowClearing) {
- j += 1;
- monsters = getUnits(sdk_1.default.unittype.Monster)
- .filter(function (m) { return m.attackable && settings.clearFilter(m, path_1[j]); });
- }
- i = Math.min(path_1.length - 1, j - 1);
- }
+ let loops = 0, shrine_1;
+ let _loop_1 = function (i, node, l) {
+ if (settings.allowClearing && settings.clearFilter && canTeleport) {
+ j = i + 1;
+ let monsters = getUnits(sdk_1.default.unittype.Monster)
+ .filter(function (m) { return m.attackable && settings.clearFilter(m, path_1[j]); });
+ while (j < path_1.length && !path_1[j].hook && monsters.length === 0 && exports.getWalkDistance(path_1[j].x, path_1[j].y) < 100 - 14 && settings.allowClearing) {
+ j += 1;
+ monsters = getUnits(sdk_1.default.unittype.Monster)
+ .filter(function (m) { return m.attackable && settings.clearFilter(m, path_1[j]); });
+ }
+ i = Math.min(path_1.length - 1, j - 1);
+ }
- node = path_1[i];
- path_1.index = i;
- lines.forEach(function (line, i) { return line.color = i < path_1.index ? 0x99 : 0x7A; });
- if (me.inTown && !didSkipTown) {
- didSkipTown = true;
- console.log("Total nodes -> " + path_1.length);
- let area = void 0, exits = [];
- (area = getArea(me.area)) && (exits = area.exits);
- let target_1 = exits.find(function (exit) {
- let closeExitNode = path_1.findIndex(function (node) { return getDistance(node, exit) < 10; });
- if (closeExitNode > -1) {
- // i = Math.min(closeExitNode-3 , 1);
- i = closeExitNode;
- return true;
- }
- return false;
- });
- if (!target_1) {
- console.log("Walking in town, but cant find any exit to walk to. So, simply walk normally");
- }
- }
+ node = path_1[i];
+ path_1.index = i;
+ lines.forEach(function (line, i) { return line.color = i < path_1.index ? 0x99 : 0x7A; });
+ if (me.inTown && !didSkipTown) {
+ didSkipTown = true;
+ console.log("Total nodes -> " + path_1.length);
+ let area = void 0, exits = [];
+ (area = getArea(me.area)) && (exits = area.exits);
+ let target_1 = exits.find(function (exit) {
+ let closeExitNode = path_1.findIndex(function (node) { return getDistance(node, exit) < 10; });
+ if (closeExitNode > -1) {
+ // i = Math.min(closeExitNode-3 , 1);
+ i = closeExitNode;
+ return true;
+ }
+ return false;
+ });
+ if (!target_1) {
+ console.log("Walking in town, but cant find any exit to walk to. So, simply walk normally");
+ }
+ }
- let hookEvent = node.hook;
- me.overhead("Moving to node (" + i + "/" + l + ") -- " + Math.round(node.distance * 100) / 100);
- if (node.distance < 5) {
- i++;
- // console.log('Skipping node as its too nearby -> Hook? ', hookEvent);
- hookEvent && hookEvent();
- return out_i_1 = i, out_node_1 = node, "continue";
- }
+ let hookEvent = node.hook;
+ me.overhead("Moving to node (" + i + "/" + l + ") -- " + Math.round(node.distance * 100) / 100);
+ if (node.distance < 5) {
+ i++;
+ // console.log('Skipping node as its too nearby -> Hook? ', hookEvent);
+ hookEvent && hookEvent();
+ return out_i_1 = i, out_node_1 = node, "continue";
+ }
- //ToDo; teleport a part if we have enough mana and it saves us a bunch of nodes
- // Like if we can skip by 35 of distance, yet remove a walk path of 60, we rather use a single teleport
- // The path generated is long, we want sub nodes
- // fixme: this will never be true, because we get a path from target by chunks of distance 4, see line 89-91
- // so the distance to next node is always 4
- if (node.distance > 30) {
- let d = exports.getWalkDistance(node.x, node.y);
- // If walking to the node is twice as far as teleporting, we teleport
- if (canTeleport && d * 2 > node.distance) {
- if (node.distance > 35) {
- Pather.moveToOverride(node.x, node.y, 4, settings.allowClearing);
- } else {
- Pather.teleportTo(node.x, node.y);
- }
- } else {
- console.debug("DONT USE RECURSION HERE WTF?");
- Pather.moveToOverride(node.x, node.y);
- }
- }
+ //ToDo; teleport a part if we have enough mana and it saves us a bunch of nodes
+ // Like if we can skip by 35 of distance, yet remove a walk path of 60, we rather use a single teleport
+ // The path generated is long, we want sub nodes
+ // fixme: this will never be true, because we get a path from target by chunks of distance 4, see line 89-91
+ // so the distance to next node is always 4
+ if (node.distance > 30) {
+ let d = exports.getWalkDistance(node.x, node.y);
+ // If walking to the node is twice as far as teleporting, we teleport
+ if (canTeleport && d * 2 > node.distance) {
+ if (node.distance > 35) {
+ Pather.moveToOverride(node.x, node.y, 4, settings.allowClearing);
+ } else {
+ Pather.teleportTo(node.x, node.y);
+ }
+ } else {
+ console.debug("DONT USE RECURSION HERE WTF?");
+ Pather.moveToOverride(node.x, node.y);
+ }
+ }
- // decent fix for this
- me.cancel(0) && me.cancel(0) && me.cancel(0) && me.cancel(0);
+ // decent fix for this
+ me.cancel(0) && me.cancel(0) && me.cancel(0) && me.cancel(0);
- if (node.distance > 2) {
- if (exports.getWalkDistance(node.x, node.y) * 0.9 > node.distance) {
- Pather.moveToOverride(node.x, node.y);
- } else {
- Pather.walkTo(node.x, node.y);
- }
- }
+ if (node.distance > 2) {
+ if (exports.getWalkDistance(node.x, node.y) * 0.9 > node.distance) {
+ Pather.moveToOverride(node.x, node.y);
+ } else {
+ Pather.walkTo(node.x, node.y);
+ }
+ }
- if (settings.callback && settings.callback()) return { value: void 0 };
- // ToDo; only if clearing makes sense in this area due to effort
- let range = 14 / 100 * clearPercentage;
- if (settings.allowClearing) {
- clear_1.default({ nodes: path_1, range: settings.rangeOverride || Math.max(4, range), callback: settings.callback });
- }
- // console.log('after clear');
- // console.log('before pick');
- //Pickit.pickOnPath(path_1);
- Misc.openChests(8);
- // console.log('after pick');
- // if shrine found, click on it
- if ((shrine_1 = searchShrine())) {
- skipShrine.push(shrine_1.gid);
- let nearestShrine_1 = path_1.slice().sort(function (a, b) { return getDistance(shrine_1, a) - getDistance(shrine_1, b); }).first();
- if (nearestShrine_1) {
- (function (originalHook, shrineId) {
- // First run original hook on this spot, if it had any
- originalHook && originalHook();
- // once we are near
- nearestShrine_1.hook = function () {
- console.log("Should take shrine");
- let shrine = getUnits(2, "shrine").filter(function (el) { return el.gid === shrineId; }).first();
- if (shrine) {
- // ToDo; use walk near / tk if we got it
- moveTo([{
- x: shrine.x,
- y: shrine.y,
- hook: function () {
- Misc.getShrine(shrine);
- }
- }]);
- }
- };
- })(typeof nearestShrine_1.hook !== "undefined" ? nearestShrine_1.hook : undefined, shrine_1.gid);
- }
- }
+ if (settings.callback && settings.callback()) return { value: void 0 };
+ // ToDo; only if clearing makes sense in this area due to effort
+ let range = 14 / 100 * clearPercentage;
+ if (settings.allowClearing) {
+ clear_1.default({ nodes: path_1, range: settings.rangeOverride || Math.max(4, range), callback: settings.callback });
+ }
+ // console.log('after clear');
+ // console.log('before pick');
+ //Pickit.pickOnPath(path_1);
+ Misc.openChests(8);
+ // console.log('after pick');
+ // if shrine found, click on it
+ if ((shrine_1 = searchShrine())) {
+ skipShrine.push(shrine_1.gid);
+ let nearestShrine_1 = path_1.slice().sort(function (a, b) { return getDistance(shrine_1, a) - getDistance(shrine_1, b); }).first();
+ if (nearestShrine_1) {
+ (function (originalHook, shrineId) {
+ // First run original hook on this spot, if it had any
+ originalHook && originalHook();
+ // once we are near
+ nearestShrine_1.hook = function () {
+ console.log("Should take shrine");
+ let shrine = getUnits(2, "shrine").filter(function (el) { return el.gid === shrineId; }).first();
+ if (shrine) {
+ // ToDo; use walk near / tk if we got it
+ moveTo([{
+ x: shrine.x,
+ y: shrine.y,
+ hook: function () {
+ Misc.getShrine(shrine);
+ }
+ }]);
+ }
+ };
+ })(typeof nearestShrine_1.hook !== "undefined" ? nearestShrine_1.hook : undefined, shrine_1.gid);
+ }
+ }
- // if this wasnt our last node
- if (l - 1 !== i) {
- if (path_1.index < i) {
- console.debug("Walked back?");
- // let nearestNode = pathCopy.filter(el => el.index === node.index).sort((a, b) => a.distance - b.distance).first();
- i = path_1.index;
- hookEvent && hookEvent();
- return out_i_1 = i, out_node_1 = node, "continue";
- } else {
- // Sometimes we go way out track due to clearing,
- // lets find the nearest node on the path and go from there
- // but not of the next node path
- let nearestNode_1 = pathCopy.filter(function (el) { return el.index === node.index; }).sort(function (a, b) { return a.distance - b.distance; }).first();
- // let nearestNode = path.slice(Math.min(path.index-10,0), path.index + 30).sort((a, b) => a.distance - b.distance).first();
- // if the nearest node is still in 95% of our current node, we dont need to reset
- if (nearestNode_1.distance > 5 && node.distance > 5 && 100 / node.distance * nearestNode_1.distance < 95) {
- console.debug("reseting path to other node");
- // reset i to the nearest node
- let newIndex = path_1.findIndex(function (node) { return nearestNode_1.x === node.x && nearestNode_1.y === node.y; });
- // Move forward
- if (newIndex > i) {
- // Hook all skipped nodes
- for (let j_1 = i; j_1 < newIndex; j_1++) {
- let hookEvent_1 = (_a = path_1[i + j_1]) === null || _a === void 0 ? void 0 : _a.hook;
- hookEvent_1 && hookEvent_1();
- }
- i = newIndex;
- }
- hookEvent && hookEvent();
- return out_i_1 = i, out_node_1 = node, "continue";
- }
- }
- hookEvent && hookEvent();
- i++;
- }
+ // if this wasnt our last node
+ if (l - 1 !== i) {
+ if (path_1.index < i) {
+ console.debug("Walked back?");
+ // let nearestNode = pathCopy.filter(el => el.index === node.index).sort((a, b) => a.distance - b.distance).first();
+ i = path_1.index;
+ hookEvent && hookEvent();
+ return out_i_1 = i, out_node_1 = node, "continue";
+ } else {
+ // Sometimes we go way out track due to clearing,
+ // lets find the nearest node on the path and go from there
+ // but not of the next node path
+ let nearestNode_1 = pathCopy.filter(function (el) { return el.index === node.index; }).sort(function (a, b) { return a.distance - b.distance; }).first();
+ // let nearestNode = path.slice(Math.min(path.index-10,0), path.index + 30).sort((a, b) => a.distance - b.distance).first();
+ // if the nearest node is still in 95% of our current node, we dont need to reset
+ if (nearestNode_1.distance > 5 && node.distance > 5 && 100 / node.distance * nearestNode_1.distance < 95) {
+ console.debug("reseting path to other node");
+ // reset i to the nearest node
+ let newIndex = path_1.findIndex(function (node) { return nearestNode_1.x === node.x && nearestNode_1.y === node.y; });
+ // Move forward
+ if (newIndex > i) {
+ // Hook all skipped nodes
+ for (let j_1 = i; j_1 < newIndex; j_1++) {
+ let hookEvent_1 = (_a = path_1[i + j_1]) === null || _a === void 0 ? void 0 : _a.hook;
+ hookEvent_1 && hookEvent_1();
+ }
+ i = newIndex;
+ }
+ hookEvent && hookEvent();
+ return out_i_1 = i, out_node_1 = node, "continue";
+ }
+ }
+ hookEvent && hookEvent();
+ i++;
+ }
- out_i_1 = i;
- out_node_1 = node;
- };
+ out_i_1 = i;
+ out_node_1 = node;
+ };
- let j, out_i_1, out_node_1;
- for (let i = startIndex > 1 ? startIndex : 0, node = void 0, l = path_1.length; i < l; loops++) {
- let state_1 = _loop_1(i, node, l);
- i = out_i_1;
- node = out_node_1;
- if (typeof state_1 === "object") {
- return state_1.value;
- }
- }
- } finally {
- // reset current path
- exports.currentWalkingPath = _prevpath;
- recursiveMoveTo--;
- }
- }
+ let j, out_i_1, out_node_1;
+ for (let i = startIndex > 1 ? startIndex : 0, node = void 0, l = path_1.length; i < l; loops++) {
+ let state_1 = _loop_1(i, node, l);
+ i = out_i_1;
+ node = out_node_1;
+ if (typeof state_1 === "object") {
+ return state_1.value;
+ }
+ }
+ } finally {
+ // reset current path
+ exports.currentWalkingPath = _prevpath;
+ recursiveMoveTo--;
+ }
+ }
- exports.default = moveTo;
- // eslint-disable-next-line no-var, no-unused-vars
- var recursiveMoveTo = 0;
+ exports.default = moveTo;
+ // eslint-disable-next-line no-var, no-unused-vars
+ var recursiveMoveTo = 0;
});
diff --git a/libs/SoloPlay/Modules/PotData.js b/libs/SoloPlay/Modules/PotData.js
deleted file mode 100644
index f4707827..00000000
--- a/libs/SoloPlay/Modules/PotData.js
+++ /dev/null
@@ -1,114 +0,0 @@
-/**
- * @description Data about pots
- * @author ryancrunchi
- */
-
-// eslint-disable-next-line no-unused-vars
-(function (module, require) {
- const PotData = {
- pots: [],
- getMpPots: function () {
- return this.pots.filter((el) => el.type === "mp");
- },
- getHpPots: function () {
- return this.pots.filter((el) => el.type === "hp");
- },
- };
-
- PotData.pots[sdk.items.MinorHealingPotion] = {
- type: "hp",
- effect: [45, 30, 30, 45, 60, 30, 45],
- cost: 30,
- duration: 7.68
- };
- PotData.pots[sdk.items.LightHealingPotion] = {
- type: "hp",
- effect: [90, 60, 60, 90, 120, 60, 90],
- cost: 67,
- duration: 6.4
- };
- PotData.pots[sdk.items.HealingPotion] = {
- type: "hp",
- effect: [150, 100, 100, 150, 200, 100, 150],
- cost: 112,
- duration: 6.84
- };
- PotData.pots[sdk.items.GreaterHealingPotion] = {
- type: "hp",
- effect: [270, 180, 180, 270, 360, 180, 270],
- cost: 225,
- duration: 7.68
- };
- PotData.pots[sdk.items.SuperHealingPotion] = {
- type: "hp",
- effect: [480, 320, 320, 480, 640, 320, 480],
- cost: undefined,
- duration: 10.24
- };
- PotData.pots[sdk.items.MinorManaPotion] = {
- type: "mp",
- effect: [30, 40, 40, 30, 20, 40, 30],
- cost: 60,
- duration: 5.12
- };
- PotData.pots[sdk.items.LightManaPotion] = {
- type: "mp",
- effect: [60, 80, 80, 60, 40, 80, 60],
- cost: 135,
- duration: 5.12
- };
- PotData.pots[sdk.items.ManaPotion] = {
- type: "mp",
- effect: [120, 160, 160, 120, 80, 160, 120],
- cost: 270,
- duration: 5.12
- };
- PotData.pots[sdk.items.GreaterManaPotion] = {
- type: "mp",
- effect: [225, 300, 300, 225, 150, 300, 225],
- cost: 450,
- duration: 5.12
- };
- PotData.pots[sdk.items.SuperManaPotion] = {
- type: "mp",
- effect: [375, 500, 500, 375, 250, 500, 375],
- cost: undefined,
- duration: 5.12
- };
- PotData.pots[sdk.items.RejuvenationPotion] = {
- type: "rv",
- effect: [35, 35, 35, 35, 35, 35, 35],
- cost: undefined,
- duration: 0.04,
- recipe: [
- [
- sdk.items.HealingPotion, sdk.items.HealingPotion, sdk.items.HealingPotion,
- sdk.items.ManaPotion, sdk.items.ManaPotion, sdk.items.ManaPotion,
- function (item) {
- return item.itemType === sdk.items.type.ChippedGem;
- }
- ]
- ]
- };
- PotData.pots[sdk.items.FullRejuvenationPotion] = {
- type: "rv",
- effect: [100, 100, 100, 100, 100, 100, 100],
- cost: undefined,
- duration: 0.04,
- recipe: [
- // Recipe is either an classid, or an function that returns true on the correct item
- [
- sdk.items.RejuvenationPotion, sdk.items.RejuvenationPotion, sdk.items.RejuvenationPotion // 3 normal rv's
- ],
- [
- sdk.items.HealingPotion, sdk.items.HealingPotion, sdk.items.HealingPotion,
- sdk.items.ManaPotion, sdk.items.ManaPotion, sdk.items.ManaPotion,
- function (item) {
- return item.itemType === sdk.items.type.Gem;
- }
- ],
- ]
- };
-
- module.exports = PotData;
-})(module, require);
diff --git a/libs/SoloPlay/Modules/TownGuard.js b/libs/SoloPlay/Modules/TownGuard.js
deleted file mode 100644
index 5fb7b3a1..00000000
--- a/libs/SoloPlay/Modules/TownGuard.js
+++ /dev/null
@@ -1,107 +0,0 @@
-(function (module, require, thread) {
- const Messaging = require("../../modules/Messaging");
- const Worker = require("../../modules/Worker");
- const sdk = require("../../modules/sdk");
-
- switch (thread) {
- case "thread": {
- Worker.runInBackground.stackTrace = (new function () {
- let self = this;
- let stack;
-
- let myStack = "";
-
- // recv stack
- Messaging.on("TownGuard", (data => typeof data === "object" && data && data.hasOwnProperty("stack") && (myStack = data.stack)));
-
- /**
- * @constructor
- * @param {function():string} callback
- */
- function UpdateableText(callback) {
- let element = new Text(callback(), self.x + 15, self.y + (7 * self.hooks.length), 0, 12, 0);
- self.hooks.push(element);
- this.update = () => {
- element.text = callback();
- element.visible = element.visible = [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 = 150;
- 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 < 20; i++) {
- (i => this.hooks.push(new UpdateableText(() => stack && stack.length > i && stack[i] || "")))(i);
- }
-
- this.update = () => {
- stack = myStack.match(/[^\r\n]+/g);
- stack = stack && stack.slice(6/*skip path to here*/).map(el => {
- let line = el.substr(el.lastIndexOf(":") + 1);
- let functionName = el.substr(0, el.indexOf("@"));
- let filename = el.substr(el.lastIndexOf("\\") + 1);
-
- filename = filename.substr(0, filename.indexOf("."));
-
- return filename + "ÿc::ÿc0" + line + "ÿc:@ÿc0" + functionName;
- }).reverse();
- this.hooks.filter(hook => hook.hasOwnProperty("update") && typeof hook.update === "function" && hook.update());
- return true;
- };
-
- }).update;
-
- Worker.runInBackground.ping = (new function () {
- // recv heartbeat
- Messaging.on("TownGuard", (data => typeof data === "object" && data && data.hasOwnProperty("heartbeat")));
-
- this.update = function () {
- // Only deal with this shit if default is paused - townchicken
- const script = getScript("default.dbj");
- if (script && script.running) {
- return true;
- }
- return true;
- };
- }).update;
-
- let quiting = false;
- addEventListener("scriptmsg", data => data === "quit" && (quiting = true));
-
- while (!quiting) delay(1000);
- break;
- }
- case "started": {
- let sendStack = getTickCount();
- Worker.push(function highPrio() {
- Worker.push(highPrio);
- if ((getTickCount() - sendStack) < 200 || (sendStack = getTickCount()) && false) return true;
- Messaging.send({TownGuard: {stack: (new Error).stack}});
- return true;
- });
-
- let timer = getTickCount();
- Worker.runInBackground.heartbeatForTownGuard = function () {
- if ((getTickCount() - timer) < 1000 || (timer = getTickCount()) && false) return true;
-
- // Every second or so, we send a heartbeat tick
- Messaging.send({TownGuard: {heartbeat: getTickCount()}});
-
- return true;
- };
- break;
- }
- case "loaded": {
- break;
- }
- }
-
-}).call(null, typeof module === "object" && module || {}, typeof require === "undefined" && (include("require.js") && require) || require, getScript.startAsThread());
diff --git a/libs/SoloPlay/Modules/Vector.js b/libs/SoloPlay/Modules/Vector.js
new file mode 100644
index 00000000..cd88f6d5
--- /dev/null
+++ b/libs/SoloPlay/Modules/Vector.js
@@ -0,0 +1,191 @@
+/**
+ * @filename Vector.js
+ * @author theBGuy
+ * @desc Vector class
+ *
+ */
+
+(function (module) {
+ /**
+ * @constructor
+ * @param {number} x
+ * @param {number} y
+ */
+ function Vector (x, y) {
+ this.x = Math.trunc(x) || 0;
+ this.y = Math.trunc(y) || 0;
+ }
+
+ Vector.prototype = {
+ /**
+ * Set the magnitude of the vector
+ * @param {number} magnitude
+ */
+ setMagnitude: function (magnitude) {
+ let angle = this.getAngle();
+ this.x = Math.cos(angle) * magnitude;
+ this.y = Math.sin(angle) * magnitude;
+ },
+
+ /**
+ * Get the magnitude of the vector
+ * @returns {number}
+ */
+ getMagnitude: function () {
+ return Math.sqrt((this.x * this.x) + (this.y * this.y));
+ },
+
+ /**
+ * Calculate the distance to another point
+ * @param {Vector} point
+ * @returns {number}
+ */
+ getMagnitudeTo: function (point) {
+ let dx = this.x - point.x;
+ let dy = this.y - point.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+
+ /**
+ * Set the angle of the vector
+ * @param {number} angle
+ */
+ setAngle: function (angle) {
+ let magnitude = this.getMagnitude();
+ this.x = Math.cos(angle) * magnitude;
+ this.y = Math.sin(angle) * magnitude;
+ },
+
+ /**
+ * Get the angle of the vector
+ * @returns {number}
+ */
+ getAngle: function () {
+ return Math.atan2(this.y, this.x);
+ },
+
+ /**
+ * Add a vector to this vector
+ * @param {Vector} vector
+ */
+ add: function (vector) {
+ this.x += vector.x;
+ this.y += vector.y;
+ return this;
+ },
+
+ /**
+ * Subtract a vector from this vector
+ * @param {Vector} vector
+ */
+ subtract: function (vector) {
+ this.x -= vector.x;
+ this.y -= vector.y;
+ return this;
+ },
+
+ /**
+ * Multiply this vector by a scalar value
+ * @param {number} scalar
+ */
+ multiply: function (scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ return this;
+ },
+
+ /**
+ * Divide this vector by a scalar value
+ * @param {number} scalar
+ */
+ divide: function (scalar) {
+ this.x /= scalar;
+ this.y /= scalar;
+ return this;
+ },
+
+ /**
+ * Normalize this vector (make its magnitude 1)
+ */
+ normalize: function () {
+ let magnitude = this.getMagnitude();
+ if (magnitude !== 0) {
+ this.x /= magnitude;
+ this.y /= magnitude;
+ }
+ return this;
+ }
+ };
+
+ /**
+ * Generate a list of points along the line from pointA to pointB
+ * @param {Vector} pointA
+ * @param {Vector} pointB
+ * @param {number} numberOfPoints
+ * @returns {Vector[]}
+ * @todo Should this accept a unit as well and create a vector for it?
+ */
+ Vector.path = function (pointA, pointB, numberOfPoints) {
+ if (!(pointA instanceof Vector)) {
+ pointA = new Vector(pointA.x, pointA.y);
+ }
+ if (!(pointB instanceof Vector)) {
+ pointB = new Vector(pointB.x, pointB.y);
+ }
+ if (!numberOfPoints) {
+ numberOfPoints = Math.floor(getDistance(pointA.x, pointA.y, pointB.x, pointB.y));
+ }
+ // Calculate the vector from point A to point B
+ let trajectory = new Vector(pointB.x - pointA.x, pointB.y - pointA.y);
+
+ // Normalize the trajectory vector
+ trajectory.normalize();
+
+ // Calculate the distance between point A and point B
+ let distance = pointA.getMagnitudeTo(pointB);
+
+ // Calculate the distance between each generated point
+ let step = distance / numberOfPoints;
+
+ /**
+ * @type {Vector[]}
+ */
+ let points = [];
+
+ // Generate an array of points from point A to point B
+ for (let i = 0; i <= numberOfPoints; i++) {
+ // Calculate the position of the current point along the trajectory
+ let position = new Vector(trajectory.x * step * i, trajectory.y * step * i);
+
+ // Add the position to the starting point to get the actual point
+ let point = new Vector(pointA.x + position.x, pointA.y + position.y);
+
+ // Add the point to the array
+ points.push(point);
+ }
+
+ return points;
+ };
+
+ /**
+ * Find a third point in the same direction as point A to point B
+ * @param {Vector} vecA
+ * @param {Vector} vecB
+ * @param {number} distance
+ * @returns {Vector}
+ */
+ Vector.spotOnDistance = function (vecA, vecB, distance) {
+ // Calculate the vector from point A to point B
+ let trajectory = new Vector(vecB.x - vecA.x, vecB.y - vecA.y);
+
+ // Normalize the trajectory vector
+ trajectory.normalize();
+
+ // Multiply the normalized trajectory by the distance
+ trajectory.multiply(distance);
+
+ return new Vector(vecA.x + trajectory.x, vecA.y + trajectory.y);
+ };
+
+ module.exports = Vector;
+})(module);
diff --git a/libs/SoloPlay/Modules/utilities.js b/libs/SoloPlay/Modules/utilities.js
index cad74a80..d4aed495 100644
--- a/libs/SoloPlay/Modules/utilities.js
+++ b/libs/SoloPlay/Modules/utilities.js
@@ -1,69 +1,69 @@
var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
+ return (mod && mod.__esModule) ? mod : { "default": mod };
};
(function (factory) {
- if (typeof module === "object" && typeof module.exports === "object") {
- let v = factory(require, exports);
- if (v !== undefined) module.exports = v;
- } else if (typeof define === "function" && define.amd) {
- define(["require", "exports", "../../modules/sdk", ], factory);
- }
+ if (typeof module === "object" && typeof module.exports === "object") {
+ let v = factory(require, exports);
+ if (v !== undefined) module.exports = v;
+ } else if (typeof define === "function" && define.amd) {
+ define(["require", "exports", "../../modules/sdk", ], factory);
+ }
})(function (require, exports) {
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.randomString = exports.recursiveSearch = exports.mixinFunctions = exports.mixin = void 0;
- let sdk_1 = __importDefault(require("../../modules/sdk"));
- function mixin(target) {
- let sources = [];
- for (let _i = 1; _i < arguments.length; _i++) {
- sources[_i - 1] = arguments[_i];
- }
- sources.forEach(function (source) {
- return Object.getOwnPropertyNames(source.prototype)
- .forEach(function (key) { return Object.defineProperty(target.prototype, key, Object.getOwnPropertyDescriptor(source.prototype, key)); });
- });
- }
- exports.mixin = mixin;
- function mixinFunctions(target) {
- let sources = [];
- for (let _i = 1; _i < arguments.length; _i++) {
- sources[_i - 1] = arguments[_i];
- }
- sources.forEach(function (source) {
- return Object.getOwnPropertyNames(source.prototype)
- .forEach(function (key) {
- let propertyDescriptor = Object.getOwnPropertyDescriptor(source.prototype, key);
- let current = Object.getOwnPropertyDescriptor(target.prototype, key);
- if (!current && propertyDescriptor.hasOwnProperty("value") && typeof propertyDescriptor.value === "function") {
- Object.defineProperty(target.prototype, key, Object.getOwnPropertyDescriptor(source.prototype, key));
- }
- });
- });
- }
- exports.mixinFunctions = mixinFunctions;
- function recursiveSearch(o, n, changed) {
- if (changed === void 0) { changed = {}; }
- Object.keys(n).forEach(function (key) {
- if (typeof n[key] !== "object") {
- if (!o.hasOwnProperty(key) || o[key] !== n[key]) {
- changed[key] = n[key];
- }
- } else {
- if (typeof changed[key] !== "object" || !changed[key]) {
- changed[key] = {};
- }
- recursiveSearch((o === null || o === void 0 ? void 0 : o[key]) || {}, (n === null || n === void 0 ? void 0 : n[key]) || {}, changed[key]);
- if (!Object.keys(changed[key]).length)
- delete changed[key];
- }
- });
- return changed;
- }
- exports.recursiveSearch = recursiveSearch;
- function randomString(min, max) {
- return Array.apply(null, { length: min + ~~(rand(0, max - min)) })
- .map(function (_) { return "abcdefghijklmnopqrstuvwxyz".charAt(Math.floor(Math.random() * 26)); })
- .join("");
- }
- exports.randomString = randomString;
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.randomString = exports.recursiveSearch = exports.mixinFunctions = exports.mixin = void 0;
+ let sdk_1 = __importDefault(require("../../modules/sdk"));
+ function mixin(target) {
+ let sources = [];
+ for (let _i = 1; _i < arguments.length; _i++) {
+ sources[_i - 1] = arguments[_i];
+ }
+ sources.forEach(function (source) {
+ return Object.getOwnPropertyNames(source.prototype)
+ .forEach(function (key) { return Object.defineProperty(target.prototype, key, Object.getOwnPropertyDescriptor(source.prototype, key)); });
+ });
+ }
+ exports.mixin = mixin;
+ function mixinFunctions(target) {
+ let sources = [];
+ for (let _i = 1; _i < arguments.length; _i++) {
+ sources[_i - 1] = arguments[_i];
+ }
+ sources.forEach(function (source) {
+ return Object.getOwnPropertyNames(source.prototype)
+ .forEach(function (key) {
+ let propertyDescriptor = Object.getOwnPropertyDescriptor(source.prototype, key);
+ let current = Object.getOwnPropertyDescriptor(target.prototype, key);
+ if (!current && propertyDescriptor.hasOwnProperty("value") && typeof propertyDescriptor.value === "function") {
+ Object.defineProperty(target.prototype, key, Object.getOwnPropertyDescriptor(source.prototype, key));
+ }
+ });
+ });
+ }
+ exports.mixinFunctions = mixinFunctions;
+ function recursiveSearch(o, n, changed) {
+ if (changed === void 0) { changed = {}; }
+ Object.keys(n).forEach(function (key) {
+ if (typeof n[key] !== "object") {
+ if (!o.hasOwnProperty(key) || o[key] !== n[key]) {
+ changed[key] = n[key];
+ }
+ } else {
+ if (typeof changed[key] !== "object" || !changed[key]) {
+ changed[key] = {};
+ }
+ recursiveSearch((o === null || o === void 0 ? void 0 : o[key]) || {}, (n === null || n === void 0 ? void 0 : n[key]) || {}, changed[key]);
+ if (!Object.keys(changed[key]).length)
+ delete changed[key];
+ }
+ });
+ return changed;
+ }
+ exports.recursiveSearch = recursiveSearch;
+ function randomString(min, max) {
+ return Array.apply(null, { length: min + ~~(rand(0, max - min)) })
+ .map(function (_) { return "abcdefghijklmnopqrstuvwxyz".charAt(Math.floor(Math.random() * 26)); })
+ .join("");
+ }
+ exports.randomString = randomString;
});
diff --git a/libs/SoloPlay/Scripts/a1chests.js b/libs/SoloPlay/Scripts/a1chests.js
index 8eaed4a8..0feebf60 100644
--- a/libs/SoloPlay/Scripts/a1chests.js
+++ b/libs/SoloPlay/Scripts/a1chests.js
@@ -5,29 +5,27 @@
*
*/
-function a1chests() {
- const areas = [sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2];
-
- myPrint("starting a1 chests");
- Town.doChores();
+function a1chests () {
+ myPrint("starting a1 chests");
+ Town.doChores();
- for (let i = 0; i < areas.length; i++) {
- try {
- // Don't run pits for its chest, when it was cleared during the pits script
- if ((SoloIndex.doneList.includes("pits") || me.barbarian) && areas[i] === sdk.areas.PitLvl2) {
- continue;
- }
+ [sdk.areas.CaveLvl2, sdk.areas.UndergroundPassageLvl2, sdk.areas.HoleLvl2, sdk.areas.PitLvl2]
+ .forEach(function (area) {
+ try {
+ // Don't run pits for its chest, when it was cleared during the pits script
+ if ((SoloIndex.doneList.includes("pits") || me.barbarian) && area === sdk.areas.PitLvl2) {
+ return;
+ }
- myPrint("Moving to " + Pather.getAreaName(areas[i]));
- Pather.journeyTo(areas[i]);
- Precast.doPrecast();
- Misc.openChestsInArea(areas[i]);
- Town.doChores();
- } catch (e) {
- console.debug("ÿc8Kolbot-SoloPlayÿc0: Failed to move to " + Pather.getAreaName(areas[i]), e);
- continue;
- }
- }
+ myPrint("Moving to " + getAreaName(area));
+ Pather.journeyTo(area);
+ Precast.doPrecast();
+ Misc.openChestsInArea(area);
+ Town.doChores();
+ } catch (e) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to " + getAreaName(area), e);
+ }
+ });
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/a5chests.js b/libs/SoloPlay/Scripts/a5chests.js
index c04c7874..525b830e 100644
--- a/libs/SoloPlay/Scripts/a5chests.js
+++ b/libs/SoloPlay/Scripts/a5chests.js
@@ -5,30 +5,35 @@
*
*/
-function a5chests() {
- const areas = [sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit, sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar];
-
- myPrint("starting a5 chests");
- Town.doChores();
+function a5chests () {
+ const areas = [];
- for (let i = 0; i < areas.length; i++) {
- try {
- if (!Pather.canTeleport() && me.nightmare && [sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit].includes(areas[i])) {
- continue;
- } else if (!Pather.canTeleport() && me.nightmare && me.charlvl >= 70 && [sdk.areas.Abaddon, sdk.areas.PitofAcheron, sdk.areas.InfernalPit, sdk.areas.GlacialTrail, sdk.areas.DrifterCavern].includes(areas[i])) {
- continue;
- }
+ if (me.nightmare && !Pather.canTeleport()) {
+ me.charlvl >= 70
+ ? areas.push(sdk.areas.DrifterCavern, sdk.areas.IcyCellar)
+ : areas.push(sdk.areas.GlacialTrail, sdk.areas.DrifterCavern, sdk.areas.IcyCellar);
+ } else {
+ areas.push(
+ sdk.areas.Abaddon, sdk.areas.PitofAcheron,
+ sdk.areas.InfernalPit, sdk.areas.GlacialTrail,
+ sdk.areas.DrifterCavern, sdk.areas.IcyCellar
+ );
+ }
- myPrint("Moving to " + Pather.getAreaName(areas[i]));
- Pather.journeyTo(areas[i]);
- Precast.doPrecast();
- Misc.openChestsInArea(areas[i]);
- Town.doChores();
- } catch (e) {
- console.debug("ÿc8Kolbot-SoloPlayÿc0: Failed to move to " + Pather.getAreaName(areas[i]), e);
- continue;
- }
- }
+ myPrint("starting a5 chests");
+ Town.doChores();
- return true;
+ areas.forEach(function (area) {
+ try {
+ myPrint("Moving to " + getAreaName(area));
+ Pather.journeyTo(area);
+ Precast.doPrecast();
+ Misc.openChestsInArea(area);
+ Town.doChores();
+ } catch (e) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to " + getAreaName(area), e);
+ }
+ });
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/amulet.js b/libs/SoloPlay/Scripts/amulet.js
index 1bd9b44b..cea7fda0 100644
--- a/libs/SoloPlay/Scripts/amulet.js
+++ b/libs/SoloPlay/Scripts/amulet.js
@@ -6,32 +6,35 @@
*/
function amulet () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting amulet");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting amulet");
- Pather.checkWP(sdk.areas.LostCity, true) ? Pather.useWaypoint(sdk.areas.LostCity) : Pather.getWP(sdk.areas.LostCity);
- Precast.doPrecast(true);
- Pather.moveToExit([sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2], true);
- Precast.doPrecast(true);
+ Pather.checkWP(sdk.areas.LostCity, true)
+ ? Pather.useWaypoint(sdk.areas.LostCity)
+ : Pather.getWP(sdk.areas.LostCity);
+ Precast.doPrecast(true);
+ Pather.moveToExit([sdk.areas.ValleyofSnakes, sdk.areas.ClawViperTempleLvl1, sdk.areas.ClawViperTempleLvl2], true);
+ Precast.doPrecast(true);
- if (!Pather.useTeleport()) {
- // change this to be array loop, sometimes bot gets lucky to have a clearish path to the chest but
- // then because Attack.clear on nodeaction we move from the chest even though we were there and the vipers can't get to the altar
- Pather.moveTo(15065, 14047);
- Pather.moveTo(15063, 14066);
- Pather.moveTo(15051, 14066);
- Pather.moveTo(15045, 14051);
- } else {
- Pather.moveTo(15045, 14051, null, false);
- }
+ if (!Pather.useTeleport()) {
+ // change this to be array loop, sometimes bot gets lucky to have a clearish path to the chest but
+ // then because Attack.clear on nodeaction we move from the chest even though we were there and the vipers can't get to the altar
+ [[15065, 14047], [15063, 14066], [15051, 14066], [15045, 14051]]
+ .some(function (pos) {
+ Pather.moveTo(pos[0], pos[1]);
+ return [15045, 14051].distance < 5 || me.getItem(sdk.items.quest.ViperAmulet);
+ });
+ } else {
+ Pather.moveTo(15045, 14051, null, false);
+ }
- if (!Quest.collectItem(sdk.quest.item.ViperAmulet, sdk.quest.chest.ViperAmuletChest)) {
- myPrint("Failed to collect viper amulet");
- return false;
- }
+ if (!Quest.collectItem(sdk.quest.item.ViperAmulet, sdk.quest.chest.ViperAmuletChest)) {
+ myPrint("Failed to collect viper amulet");
+ return false;
+ }
- Town.npcInteract("drognan");
- Quest.stashItem(sdk.items.quest.ViperAmulet);
+ Town.npcInteract("drognan");
+ Quest.stashItem(sdk.items.quest.ViperAmulet);
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/ancients.js b/libs/SoloPlay/Scripts/ancients.js
index 4ad37fe4..3b610940 100644
--- a/libs/SoloPlay/Scripts/ancients.js
+++ b/libs/SoloPlay/Scripts/ancients.js
@@ -7,64 +7,65 @@
*/
function ancients () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting ancients");
+ include("core/Common/Ancients.js");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting ancients");
- Pather.checkWP(sdk.areas.AncientsWay) ? Pather.useWaypoint(sdk.areas.AncientsWay) : Pather.getWP(sdk.areas.AncientsWay);
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.AncientsWay, sdk.areas.ArreatSummit, true); // enter Arreat Summit
+ Pather.checkWP(sdk.areas.AncientsWay) ? Pather.useWaypoint(sdk.areas.AncientsWay) : Pather.getWP(sdk.areas.AncientsWay);
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.AncientsWay, sdk.areas.ArreatSummit, true); // enter Arreat Summit
- // failed to move to Arreat Summit
- if (!me.inArea(sdk.areas.ArreatSummit)) {
- return false;
- }
+ // failed to move to Arreat Summit
+ if (!me.inArea(sdk.areas.ArreatSummit)) {
+ return false;
+ }
- // ancients prep
- Town.doChores(false, { thawing: true, antidote: true, stamina: true, fullChores: true });
+ // ancients prep
+ Town.doChores(false, { thawing: true, antidote: true, stamina: true, fullChores: true });
- let tempConfig = Misc.copy(Config); // save and update config settings
+ let tempConfig = copyObj(Config); // save and update config settings
- Config.TownCheck = false;
- Config.MercWatch = false;
- Config.TownHP = 0;
- Config.TownMP = 0;
- Config.HPBuffer = 15;
- Config.MPBuffer = 15;
- Config.LifeChicken = 10;
- CharData.updateConfig();
- me.overhead("updated settings");
+ Config.TownCheck = false;
+ Config.MercWatch = false;
+ Config.TownHP = 0;
+ Config.TownMP = 0;
+ Config.HPBuffer = 15;
+ Config.MPBuffer = 15;
+ Config.LifeChicken = 10;
+ CharData.updateConfig();
+ me.overhead("updated settings");
- Town.buyPotions();
- if (!Pather.usePortal(sdk.areas.ArreatSummit, me.name)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to take portal back to Arreat Summit");
- Pather.clearToExit(sdk.areas.AncientsWay, sdk.areas.ArreatSummit, true); // enter Arreat Summit
- }
-
- Precast.doPrecast(true);
+ NPCAction.buyPotions();
+ if (!Pather.usePortal(sdk.areas.ArreatSummit, me.name)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to take portal back to Arreat Summit");
+ Pather.clearToExit(sdk.areas.AncientsWay, sdk.areas.ArreatSummit, true); // enter Arreat Summit
+ }
+
+ Precast.doPrecast(true);
- // move to altar
- if (!Pather.moveToPreset(sdk.areas.ArreatSummit, sdk.unittype.Object, sdk.quest.chest.AncientsAltar)) {
- console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to move to ancients' altar");
- }
+ // move to altar
+ if (!Pather.moveToPreset(sdk.areas.ArreatSummit, sdk.unittype.Object, sdk.quest.chest.AncientsAltar)) {
+ console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to move to ancients' altar");
+ }
- Common.Ancients.touchAltar();
- Common.Ancients.startAncients(true, true);
-
- me.cancel();
- Config = tempConfig;
- CharData.updateConfig();
- me.overhead("restored settings");
- Precast.doPrecast(true);
+ Common.Ancients.touchAltar();
+ Common.Ancients.startAncients(true, true);
+
+ me.cancel();
+ Config = tempConfig;
+ CharData.updateConfig();
+ me.overhead("restored settings");
+ Precast.doPrecast(true);
- try {
- if (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed)) {
- Pather.clearToExit(sdk.areas.ArreatSummit, sdk.areas.WorldstoneLvl1, true);
- Pather.clearToExit(sdk.areas.WorldstoneLvl1, sdk.areas.WorldstoneLvl2, true);
- Pather.getWP(sdk.areas.WorldstoneLvl2);
- }
- } catch (err) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Cleared Ancients. Failed to get WSK Waypoint");
- }
+ try {
+ if (Misc.checkQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed)) {
+ Pather.clearToExit(sdk.areas.ArreatSummit, sdk.areas.WorldstoneLvl1, true);
+ Pather.clearToExit(sdk.areas.WorldstoneLvl1, sdk.areas.WorldstoneLvl2, true);
+ Pather.getWP(sdk.areas.WorldstoneLvl2);
+ }
+ } catch (err) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Cleared Ancients. Failed to get WSK Waypoint");
+ }
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/ancienttunnels.js b/libs/SoloPlay/Scripts/ancienttunnels.js
index aa22f6c9..ef03848c 100644
--- a/libs/SoloPlay/Scripts/ancienttunnels.js
+++ b/libs/SoloPlay/Scripts/ancienttunnels.js
@@ -6,34 +6,38 @@
*/
function ancienttunnels () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting ancient tunnels");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting ancient tunnels");
- Pather.checkWP(sdk.areas.LostCity, true) ? Pather.useWaypoint(sdk.areas.LostCity) : Pather.getWP(sdk.areas.LostCity);
- Precast.doPrecast(true);
+ Pather.checkWP(sdk.areas.LostCity, true) ? Pather.useWaypoint(sdk.areas.LostCity) : Pather.getWP(sdk.areas.LostCity);
+ Precast.doPrecast(true);
- if (me.hell && me.classic) {
- Attack.clearLevel();
- } else {
- try {
- Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.SuperChest) && Misc.openChests(5) && Pickit.pickItems();
- } catch (e) {
- console.error(e);
- }
+ if (me.hell && me.classic) {
+ Attack.clearLevel();
+ } else {
+ try {
+ if (Pather.moveToPresetObject(me.area, sdk.objects.SuperChest)) {
+ Misc.openChests(5) && Pickit.pickItems();
+ }
+ } catch (e) {
+ console.error(e);
+ }
- try {
- Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.DarkElder) && Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.DarkElder));
- } catch (e) {
- console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Dark Elder");
- }
- }
+ try {
+ if (Pather.moveToPresetMonster(me.area, sdk.monsters.preset.DarkElder)) {
+ Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.DarkElder));
+ }
+ } catch (e) {
+ console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Dark Elder");
+ }
+ }
- if (!Pather.moveToExit(sdk.areas.AncientTunnels, true)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Ancient Tunnels");
- return false;
- }
+ if (!Pather.moveToExit(sdk.areas.AncientTunnels, true)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Ancient Tunnels");
+ return false;
+ }
- Attack.clearLevel();
+ Attack.clearLevel();
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/andariel.js b/libs/SoloPlay/Scripts/andariel.js
index 6ebe6c8c..774fc41c 100644
--- a/libs/SoloPlay/Scripts/andariel.js
+++ b/libs/SoloPlay/Scripts/andariel.js
@@ -7,94 +7,101 @@
// todo: clean this up
function andariel () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting andy");
-
- if (me.normal && Misc.checkQuest(sdk.quest.id.SistersToTheSlaughter, sdk.quest.states.ReqComplete)) {
- Pather.changeAct();
-
- return true;
- }
-
- let questBug = (!me.normal && !me.andariel);
-
- Pather.checkWP(sdk.areas.CatacombsLvl2, true) ? Pather.useWaypoint(sdk.areas.CatacombsLvl2) : Pather.getWP(sdk.areas.CatacombsLvl2);
- Precast.doPrecast(true);
- Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true);
-
- if (me.poisonRes < 75) {
- Town.doChores(true, {thawing: me.coldRes < 75, antidote: true});
- Pather.usePortal(sdk.areas.CatacombsLvl4, me.name);
- }
-
- Precast.doPrecast(true);
-
- let oldPickRange = Config.PickRange;
-
- if (questBug) {
- Config.PickRange = -1;
- me.barbarian && (Config.FindItem = false);
- } else {
- Config.PickRange = 5; // Only pick what is directly around me
- }
-
- 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();
- }
- }
-
- Config.MercWatch = false;
-
- Attack.killTarget("Andariel");
-
- if (questBug) {
- Config.TownCheck = false;
- Config.MercWatch = false;
- Config.HealStatus = false;
- Config.UseMerc = false;
- Config.TownHP = 0;
- Config.TownMP = 0;
- Config.PickRange = -1;
- Misc.townEnabled = false;
- CharData.updateConfig();
-
- if (Pather.changeAct()) {
- delay(2000 + me.ping);
-
- // Now check my area
- if (me.act === 2) {
- // Act change sucessful, Andy has been bugged
- // let result = (Misc.checkQuest(sdk.quest.id.SistersToTheSlaughter, 15) ? "Sucessful" : "Unsucessful");
- // myPrint("Andy bugged was " + result);
- myPrint("Bugging andy");
- scriptBroadcast("quit");
- }
- }
- }
-
- delay(2000 + me.ping); // Wait for minions to die.
- Config.PickRange = oldPickRange; // Reset to normal value
- Pickit.pickItems();
- Config.MercWatch = true;
-
- !me.andariel && Pather.changeAct();
-
- return true;
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting andy");
+
+ if (me.normal && Misc.checkQuest(sdk.quest.id.SistersToTheSlaughter, sdk.quest.states.ReqComplete)) {
+ Pather.changeAct();
+
+ return true;
+ }
+
+ let questBug = (!me.normal && !me.andariel);
+
+ Pather.checkWP(sdk.areas.CatacombsLvl2, true)
+ ? Pather.useWaypoint(sdk.areas.CatacombsLvl2)
+ : Pather.getWP(sdk.areas.CatacombsLvl2);
+ Precast.doPrecast(true);
+ Pather.moveToExit([sdk.areas.CatacombsLvl3, sdk.areas.CatacombsLvl4], true);
+
+ if (me.charlvl < 12) {
+ myPrint("Still to early, NG");
+ return true;
+ }
+
+ if (me.poisonRes < 75) {
+ Town.doChores(true, { thawing: me.coldRes < 75, antidote: true });
+ Pather.usePortal(sdk.areas.CatacombsLvl4);
+ }
+
+ Precast.doPrecast(true);
+
+ let oldPickRange = Config.PickRange;
+
+ if (questBug) {
+ Config.PickRange = -1;
+ me.barbarian && (Config.FindItem = false);
+ } else {
+ Config.PickRange = 5; // Only pick what is directly around me
+ }
+
+ 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();
+ }
+ }
+
+ Config.MercWatch = false;
+
+ Attack.killTarget("Andariel");
+
+ if (questBug) {
+ Config.TownCheck = false;
+ Config.MercWatch = false;
+ Config.HealStatus = false;
+ Config.UseMerc = false;
+ Config.TownHP = 0;
+ Config.TownMP = 0;
+ Config.PickRange = -1;
+ SoloEvents.townChicken.disabled = true;
+ CharData.updateConfig();
+
+ if (Pather.changeAct()) {
+ delay(2000 + me.ping);
+
+ // Now check my area
+ if (me.act === 2) {
+ // Act change sucessful, Andy has been bugged
+ // let result = (Misc.checkQuest(sdk.quest.id.SistersToTheSlaughter, 15) ? "Sucessful" : "Unsucessful");
+ // myPrint("Andy bugged was " + result);
+ myPrint("Bugging andy");
+ scriptBroadcast("quit");
+ }
+ }
+ }
+
+ delay(2000 + me.ping); // Wait for minions to die.
+ Config.PickRange = oldPickRange; // Reset to normal value
+ Pickit.pickItems();
+ Config.MercWatch = true;
+
+ !me.andariel && Pather.changeAct();
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/anya.js b/libs/SoloPlay/Scripts/anya.js
index f1fdd5cd..8dbd6f80 100644
--- a/libs/SoloPlay/Scripts/anya.js
+++ b/libs/SoloPlay/Scripts/anya.js
@@ -1,61 +1,125 @@
/**
* @filename anya.js
-* @author isid0re, theBGuy
+* @author theBGuy
* @desc Anya rescue from frozen river
*
*/
function anya () {
- Town.doChores(false, { fullChores: true });
- Town.goToTown(5);
- myPrint("starting anya");
-
- Pather.checkWP(sdk.areas.CrystalizedPassage, true) ? Pather.useWaypoint(sdk.areas.CrystalizedPassage) : Pather.getWP(sdk.areas.CrystalizedPassage);
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.CrystalizedPassage, sdk.areas.FrozenRiver, Pather.useTeleport());
-
- if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.FrozenAnyasPlatform)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Anya");
- return false;
- }
-
- let frozenanya = Game.getObject(sdk.objects.FrozenAnya);
- // 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) {
- Pather.moveToUnit(frozenanya);
- Packet.entityInteract(frozenanya);
- Misc.poll(() => getIsTalkingNPC(), 2000, 50);
- me.cancel();
- }
-
- Town.npcInteract("malah");
- Town.doChores();
- 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;
- }
- Attack.clearPos(frozenanya.x, frozenanya.y, 15);
- }
- }
-
- Town.goToTown(5);
- Town.npcInteract("malah");
- Quest.unfinishedQuests();
- Town.doChores();
- Town.npcInteract("anya");
-
- return true;
+ Town.doChores(false, { fullChores: true });
+ // double check after chores, as that calls Quest.unfinishedQuests
+ if (Misc.checkQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.Completed)) return true;
+
+ Town.goToTown(5);
+ myPrint("starting anya");
+
+ Pather.checkWP(sdk.areas.CrystalizedPassage, true)
+ ? Pather.useWaypoint(sdk.areas.CrystalizedPassage)
+ : Pather.getWP(sdk.areas.CrystalizedPassage);
+
+ // Move to anya's platform
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.CrystalizedPassage, sdk.areas.FrozenRiver, Pather.useTeleport());
+
+ if (!Pather.moveToPresetObject(me.area, sdk.objects.FrozenAnyasPlatform, { callback: () => {
+ let fStein = Game.getMonster(getLocaleString(sdk.locale.monsters.Frozenstein));
+ // let frozenanya = Game.getObject(sdk.objects.FrozenAnya);
+ return (fStein && fStein.distance < 30) /*&& /* (frozenanya && frozenanya.distance < 35) */;
+ } })) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Anya");
+ return false;
+ }
+
+ // Making sure it's safe, needs work
+ let presetLoc = Game.getPresetObject(me.area, sdk.objects.FrozenAnyasPlatform).realCoords();
+ let fStein = Game.getMonster(getLocaleString(sdk.locale.monsters.Frozenstein));
+
+ if (presetLoc && fStein && getDistance(presetLoc, fStein) < 15) {
+ // need to write a clearWhile function
+ Attack.clear(15, sdk.monsters.spectype.All, fStein);
+ }
+
+ let frozenanya = Game.getObject(sdk.objects.FrozenAnya);
+
+ if (!frozenanya) {
+ Pather.moveToEx(presetLoc.x, presetLoc.y, { callback: () => Game.getObject(sdk.objects.FrozenAnya) });
+ 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();
+ }
+ Attack.clearPos(frozenanya.x, frozenanya.y, 15);
+ }
+ }
+ }
+
+ /**
+ * 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 */
+ Quest.unfinishedQuests();
+ }
+
+ return !!Misc.checkQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.Completed);
}
diff --git a/libs/SoloPlay/Scripts/baal.js b/libs/SoloPlay/Scripts/baal.js
index a401a891..7f6d6095 100644
--- a/libs/SoloPlay/Scripts/baal.js
+++ b/libs/SoloPlay/Scripts/baal.js
@@ -7,291 +7,328 @@
*/
function baal () {
- Config.BossPriority = false;
-
- let decoyTick = 0;
- let decoyDuration = (me.amazon ? Skill.getDuration(sdk.skills.Decoy) : 0);
-
- const preattack = function () {
- switch (me.classid) {
- case sdk.player.class.Amazon:
- if (Skill.canUse(sdk.skills.Decoy)) {
- let decoy = Game.getMonster(sdk.summons.Dopplezon);
-
- if (!decoy || (getTickCount() - decoyTick >= decoyDuration)) {
- Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, 15092, 5028);
- decoyTick = getTickCount();
- }
- }
-
- break;
- case sdk.player.class.Sorceress:
- if ([sdk.skills.Meteor, sdk.skills.Blizzard, sdk.skills.FrozenOrb].includes(Config.AttackSkill[1])) {
- !me.skillDelay ? Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 15093, 5024) : delay(50);
- }
-
- return true;
- case sdk.player.class.Paladin:
- if (Config.AttackSkill[3] !== sdk.skills.BlessedHammer) return false;
- [15093, 5029].distance > 3 && Pather.moveTo(15093, 5029);
- Config.AttackSkill[4] > 0 && Skill.setSkill(Config.AttackSkill[4], sdk.skills.hand.Right);
-
- Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Left);
-
- return true;
- 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, 15093, 5029);
-
- return true;
- }
-
- break;
- case sdk.player.class.Assassin:
- if (Config.UseTraps) {
- let check = ClassAttack.checkTraps({x: 15093, y: 5029});
-
- if (check) {
- ClassAttack.placeTraps({x: 15093, y: 5029}, 5);
-
- return true;
- }
- }
-
- if (Config.AttackSkill[3] === sdk.skills.ShockWeb) {
- return Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Right, 15094, 5028);
- }
-
- break;
- }
-
- return false;
- };
-
- const clearWaves = function () {
- let boss;
- let tick = getTickCount();
-
- MainLoop:
- while (true) {
- if (!Game.getMonster(sdk.monsters.ThroneBaal)) return true;
-
- Misc.townCheck();
-
- switch (Common.Baal.checkThrone()) {
- case 1:
- Attack.clearClassids(sdk.monsters.WarpedFallen, sdk.monsters.WarpedShaman) && (tick = getTickCount());
-
- break;
- case 2:
- boss = Game.getMonster("Achmel the Cursed");
-
- if (boss) {
- if (!Attack.canAttack(boss)) throw new Error("Immune boss");
- if (me.paladin && me.hell && Check.currentBuild().caster) throw new Error("Too much effort for hammerdin");
- }
-
- Attack.clearClassids(sdk.monsters.BaalSubjectMummy, sdk.monsters.BaalColdMage) && (tick = getTickCount());
-
- break;
- case 3:
- Attack.clearClassids(sdk.monsters.Council4) && (tick = getTickCount());
- Common.Baal.checkHydra() && (tick = getTickCount());
-
- break;
- case 4:
- Attack.clearClassids(sdk.monsters.VenomLord2) && (tick = getTickCount());
-
- break;
- case 5:
- boss = Game.getMonster("Lister the Tormentor");
- if (boss && !Attack.canAttack(boss)) throw new Error("Immune boss");
-
- 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(() => {
- 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)) {
- tick = getTickCount();
- Common.Baal.clearThrone();
- }
-
- if (!preattack()) {
- delay(50);
- }
-
- 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);
- }
-
- return true;
- };
-
- const unSafeCheck = function (soulAmount = 0, totalAmount = 0) {
- let count = 0;
- let soul = Game.getMonster(sdk.monsters.BurningSoul1);
-
- if (soul) {
- do {
- if (getDistance(me, soul) < 45) {
- count += 1;
- }
- } while (soul.getNext());
- }
-
- if (count > soulAmount) return true;
-
- let monster = Game.getMonster();
-
- if (monster) {
- do {
- if (!monster.getParent() && monster.classid !== sdk.monsters.BurningSoul1 && getDistance(me, monster) < 45) {
- count += 1;
- }
- } while (monster.getNext());
- }
-
- return count > totalAmount;
- };
-
- const canClearThrone = function () {
- Pather.moveTo(15094, 5029);
- let [canAttack, cantAttack] = [[], []];
- getUnits(sdk.unittype.Monster).filter(i => !!i && i.attackable).forEach(mon => {
- Attack.canAttack(mon) ? canAttack.push(mon) : cantAttack.push(mon);
- });
-
- console.debug("Can Attack: " + canAttack.length, " Can't Attack: " + cantAttack.length);
-
- return ((!canAttack.length && !cantAttack.length) || (canAttack.length > cantAttack.length));
- };
-
- // START
- Town.doChores(false, { fullChores: true });
- myPrint("starting baal");
-
- Pather.checkWP(sdk.areas.WorldstoneLvl2, true) ? Pather.useWaypoint(sdk.areas.WorldstoneLvl2) : Pather.getWP(sdk.areas.WorldstoneLvl2, true);
- Precast.doPrecast(true);
- const oldCPRange = Config.ClearPath.Range;
- const canTele = Pather.canTeleport();
- try {
- canTele && (Config.ClearPath.Range = 0);
- canTele
- ? Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], true, false)
- : (Pather.clearToExit(sdk.areas.WorldstoneLvl2, sdk.areas.WorldstoneLvl3, true) && Pather.clearToExit(sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction, true));
- } finally {
- oldCPRange !== Config.ClearPath.Range && (Config.ClearPath.Range = oldCPRange);
- }
-
- // Enter throne room
- Pather.moveTo(15095, 5029, 5);
- Pather.moveTo(15113, 5040, 5);
-
- let totalTick = getTickCount();
-
- // souls hurt
- if (unSafeCheck(8, 20) && me.lightRes < 70 && me.nightmare) throw new Error("Unsafe to clear");
- if (!canClearThrone()) throw new Error("Too many mobs I can't attack");
-
- try {
- if (((me.hell && me.paladin && !Attack.auradin) || me.barbarian || me.gold < 25000 || (!me.baal && SetUp.finalBuild !== "Bumper"))) {
- Messaging.sendToScript(SoloEvents.filePath, "addBaalEvent");
- }
-
- Attack.clear(15);
- Common.Baal.clearThrone();
-
- if (me.coldRes < 75 || me.poisonRes < 75) {
- Town.doChores(null, {thawing: me.coldRes < 75, antidote: me.poisonRes < 75});
- Town.move("portalspot");
- Pather.usePortal(sdk.areas.ThroneofDestruction, me.name);
- }
-
- if (!clearWaves()) throw new Error("Can't clear waves");
-
- Common.Baal.clearThrone(); // double check
- Pather.moveTo(15094, me.paladin ? 5029 : 5038);
- Pickit.pickItems();
- Pather.moveTo(15094, me.paladin ? 5029 : 5038);
- Pickit.pickItems();
- Pather.moveTo(15090, 5008);
- delay(2500 + me.ping);
- Precast.doPrecast(true);
-
- if (SetUp.finalBuild === "Bumper") throw new Error("BUMPER");
-
- if (Misc.poll(() => me.getMobCount(15) > 1)) {
- clearWaves();
- }
-
- Config.BossPriority = true;
-
- if (Common.Baal.killBaal()) {
- // Grab static gold
- NTIP.addLine("[name] == gold # [gold] >= 25");
- Pather.moveTo(15072, 5894);
- Pickit.pickItems();
- Pather.moveTo(15095, 5881);
- Pickit.pickItems();
- } else {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Couldn't access portal.");
- }
- } catch (e) {
- console.warn(e.message ? e.message : e);
- } finally {
- Messaging.sendToScript(SoloEvents.filePath, "removeBaalEvent");
- }
-
- return true;
+ include("core/Common/Baal.js");
+ Config.BossPriority = false;
+
+ let decoyTick = 0;
+
+ const preattack = function () {
+ switch (me.classid) {
+ case sdk.player.class.Amazon:
+ if (Skill.canUse(sdk.skills.Decoy)) {
+ let decoy = Game.getMonster(sdk.summons.Dopplezon);
+
+ if (!decoy || (getTickCount() - decoyTick >= Skill.getDuration(sdk.skills.Decoy))) {
+ Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, 15092, 5028);
+ decoyTick = getTickCount();
+ }
+ }
+
+ break;
+ case sdk.player.class.Sorceress:
+ if ([sdk.skills.Meteor, sdk.skills.Blizzard, sdk.skills.FrozenOrb].includes(Config.AttackSkill[1])) {
+ !me.skillDelay
+ ? Skill.cast(Config.AttackSkill[1], sdk.skills.hand.Right, 15093, 5024)
+ : delay(50);
+ }
+
+ return true;
+ case sdk.player.class.Necromancer:
+ if (Config.AttackSkill[3] === sdk.skills.PoisonNova) {
+ if ([15093, 5029].distance > 3) {
+ Pather.moveTo(15093, 5029);
+ }
+
+ Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Left);
+ } else if (Skill.canUse(sdk.skills.DimVision)) {
+ Skill.cast(sdk.skills.DimVision, sdk.skills.hand.Right, 15093, 5024);
+ }
+
+ return true;
+ case sdk.player.class.Paladin:
+ if (Config.AttackSkill[3] !== sdk.skills.BlessedHammer) return false;
+ if ([15093, 5029].distance > 3) {
+ Pather.moveTo(15093, 5029);
+ }
+ Config.AttackSkill[4] > 0 && Skill.setSkill(Config.AttackSkill[4], sdk.skills.hand.Right);
+
+ Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Left);
+
+ return true;
+ 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, 15093, 5029);
+
+ return true;
+ }
+
+ break;
+ case sdk.player.class.Assassin:
+ if (Config.UseTraps) {
+ let check = ClassAttack.checkTraps({ x: 15093, y: 5029 });
+
+ if (check) {
+ ClassAttack.placeTraps({ x: 15093, y: 5029 }, 5);
+
+ return true;
+ }
+ }
+
+ if (Config.AttackSkill[3] === sdk.skills.ShockWeb) {
+ return Skill.cast(Config.AttackSkill[3], sdk.skills.hand.Right, 15094, 5028);
+ }
+
+ break;
+ }
+
+ return false;
+ };
+
+ const clearWaves = function () {
+ let boss;
+ let tick = getTickCount();
+
+ MainLoop:
+ while (true) {
+ if (!Game.getMonster(sdk.monsters.ThroneBaal)) return true;
+
+ switch (Common.Baal.checkThrone()) {
+ case 1:
+ Attack.clearClassids(sdk.monsters.WarpedFallen, sdk.monsters.WarpedShaman) && (tick = getTickCount());
+
+ break;
+ case 2:
+ boss = Game.getMonster("Achmel the Cursed");
+
+ if (boss) {
+ if (!Attack.canAttack(boss)) throw new Error("Immune boss");
+ if (me.paladin && me.hell && Check.currentBuild().caster) throw new Error("Too much effort for hammerdin");
+ }
+
+ Attack.clearClassids(sdk.monsters.BaalSubjectMummy, sdk.monsters.BaalColdMage) && (tick = getTickCount());
+
+ break;
+ case 3:
+ Attack.clearClassids(sdk.monsters.Council4) && (tick = getTickCount());
+ Common.Baal.checkHydra() && (tick = getTickCount());
+
+ break;
+ case 4:
+ Attack.clearClassids(sdk.monsters.VenomLord2) && (tick = getTickCount());
+
+ break;
+ case 5:
+ boss = Game.getMonster("Lister the Tormentor");
+ if (boss && !Attack.canAttack(boss)) throw new Error("Immune boss");
+
+ 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)) {
+ tick = getTickCount();
+ Common.Baal.clearThrone();
+ }
+
+ if (!preattack()) {
+ delay(50);
+ }
+
+ break;
+ }
+
+ switch (me.classid) {
+ case sdk.player.class.Amazon:
+ case sdk.player.class.Sorceress:
+ case sdk.player.class.Assassin:
+ if ([15116, 5026].distance > 3) {
+ Pather.moveTo(15116, 5026);
+ }
+ break;
+ case sdk.player.class.Necromancer:
+ if (Config.AttackSkill[3] === sdk.skills.BoneSpear) {
+ if ([15115, 5047].distance > 3) {
+ Pather.moveTo(15115, 5047);
+ }
+ } else if (Config.AttackSkill[3] === sdk.skills.PoisonNova) {
+ if ([15094, 5029].distance > 3) {
+ Pather.moveTo(15094, 5029);
+ }
+ }
+
+ break;
+ case sdk.player.class.Paladin:
+ if (Config.AttackSkill[3] === sdk.skills.BlessedHammer) {
+ if ([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])) {
+ if ([15116, 5026].distance > 3) {
+ Pather.moveTo(15116, 5026);
+ }
+ break;
+ }
+
+ if (Config.AttackSkill[3] === sdk.skills.Tornado) {
+ if ([15094, 5029].distance > 3) {
+ Pather.moveTo(15106, 5041);
+ }
+ break;
+ }
+ // eslint-disable-next-line no-fallthrough
+ case sdk.player.class.Barbarian:
+ if ([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);
+ }
+
+ return true;
+ };
+
+ const unSafeCheck = function (soulAmount = 0, totalAmount = 0) {
+ let count = 0;
+ let soul = Game.getMonster(sdk.monsters.BurningSoul1);
+
+ if (soul) {
+ do {
+ if (getDistance(me, soul) < 45) {
+ count += 1;
+ }
+ } while (soul.getNext());
+ }
+
+ if (count > soulAmount) return true;
+
+ let monster = Game.getMonster();
+
+ if (monster) {
+ do {
+ if (!monster.getParent() && monster.classid !== sdk.monsters.BurningSoul1 && getDistance(me, monster) < 45) {
+ count += 1;
+ }
+ } while (monster.getNext());
+ }
+
+ return count > totalAmount;
+ };
+
+ const canClearThrone = function () {
+ Pather.moveTo(15094, 5029);
+ let [canAttack, cantAttack] = [[], []];
+ getUnits(sdk.unittype.Monster).filter(i => !!i && i.attackable).forEach(mon => {
+ Attack.canAttack(mon) ? canAttack.push(mon) : cantAttack.push(mon);
+ });
+
+ console.debug("Can Attack: " + canAttack.length, " Can't Attack: " + cantAttack.length);
+
+ return ((!canAttack.length && !cantAttack.length) || (canAttack.length > cantAttack.length));
+ };
+
+ // START
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting baal");
+
+ Pather.checkWP(sdk.areas.WorldstoneLvl2, true) ? Pather.useWaypoint(sdk.areas.WorldstoneLvl2) : Pather.getWP(sdk.areas.WorldstoneLvl2, true);
+ Precast.doPrecast(true);
+ const oldCPRange = Config.ClearPath.Range;
+ const canTele = Pather.canTeleport();
+ try {
+ canTele && (Config.ClearPath.Range = 0);
+ canTele
+ ? Pather.moveToExit([sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction], true, false)
+ : (Pather.clearToExit(sdk.areas.WorldstoneLvl2, sdk.areas.WorldstoneLvl3, true) && Pather.clearToExit(sdk.areas.WorldstoneLvl3, sdk.areas.ThroneofDestruction, true));
+ } finally {
+ oldCPRange !== Config.ClearPath.Range && (Config.ClearPath.Range = oldCPRange);
+ }
+
+ // Enter throne room
+ const dollQuit = me.hardcore;
+ Pather.moveToEx(15095, 5029, { callback: () => {
+ if (dollQuit && Game.getMonster(sdk.monsters.SoulKiller)) {
+ throw new ScriptError("Unsafe for hardcore, dolls found");
+ }
+ } });
+ Pather.moveTo(15113, 5040, 5);
+
+ let totalTick = getTickCount();
+
+ // souls hurt
+ if (unSafeCheck(8, 20) && me.lightRes < 70 && me.nightmare) throw new Error("Unsafe to clear");
+ if (!canClearThrone()) throw new Error("Too many mobs I can't attack");
+
+ try {
+ if (((me.hell && me.paladin && !Attack.auradin) || me.barbarian || me.gold < 25000 || (!me.baal && SetUp.finalBuild !== "Bumper"))) {
+ Messaging.sendToScript(SoloEvents.filePath, "addBaalEvent");
+ }
+
+ Attack.clear(15);
+ Common.Baal.clearThrone();
+
+ if (me.coldRes < 75 || me.poisonRes < 75) {
+ Town.doChores(null, { thawing: me.coldRes < 75, antidote: me.poisonRes < 75 });
+ Town.move("portalspot");
+ Pather.usePortal(sdk.areas.ThroneofDestruction, me.name);
+ }
+
+ if (!clearWaves()) throw new Error("Can't clear waves");
+
+ Common.Baal.clearThrone(); // double check
+ Pather.moveTo(15094, me.paladin ? 5029 : 5038);
+ Pickit.pickItems();
+ Pather.moveTo(15094, me.paladin ? 5029 : 5038);
+ Pickit.pickItems();
+ Pather.moveTo(15090, 5008);
+ delay(2500 + me.ping);
+ Precast.doPrecast(true);
+
+ if (SetUp.finalBuild === "Bumper") throw new Error("BUMPER");
+
+ if (Misc.poll(() => me.getMobCount(15) > 1)) {
+ clearWaves();
+ }
+
+ Config.BossPriority = true;
+
+ if (Common.Baal.killBaal()) {
+ // Grab static gold
+ NTIP.addLine("[name] == gold # [gold] >= 25");
+ Pather.moveTo(15072, 5894);
+ Pickit.pickItems();
+ Pather.moveTo(15095, 5881);
+ Pickit.pickItems();
+ } else {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Couldn't access portal.");
+ }
+ } catch (e) {
+ console.warn(e.message ? e.message : e);
+ } finally {
+ Messaging.sendToScript(SoloEvents.filePath, "removeBaalEvent");
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/beetleburst.js b/libs/SoloPlay/Scripts/beetleburst.js
index 09dcac3c..877e84cb 100644
--- a/libs/SoloPlay/Scripts/beetleburst.js
+++ b/libs/SoloPlay/Scripts/beetleburst.js
@@ -1,18 +1,69 @@
/**
* @filename beetleburst.js
-* @author isid0re
+* @author isid0re, theBGuy
* @desc kill beetleburst for exp
*
*/
function beetleburst () {
- Town.doChores();
- myPrint("ÿc8Kolbot-SoloPlayÿc0: starting beetleburst");
+ // const clearBeetles = function () {
+ // let room = getRoom();
+ // if (!room) return false;
- Pather.checkWP(sdk.areas.FarOasis, true) ? Pather.useWaypoint(sdk.areas.FarOasis) : Pather.getWP(sdk.areas.FarOasis);
- Precast.doPrecast(true);
- Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Beetleburst);
- Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Beetleburst));
+ // const rooms = [];
+ // /** @param {Monster} monster */
+ // const check = function (monster) {
+ // return [
+ // sdk.monsters.DungSoldier,
+ // sdk.monsters.DeathBeetle,
+ // sdk.monsters.BlackVultureNest
+ // ].includes(monster.classid) && monster.distance <= 30;
+ // };
- return true;
+ // 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;
+ // };
+
+ Town.doChores();
+ myPrint("ÿc8Kolbot-SoloPlayÿc0: starting beetleburst");
+
+ Pather.checkWP(sdk.areas.FarOasis, true)
+ ? Pather.useWaypoint(sdk.areas.FarOasis)
+ : Pather.getWP(sdk.areas.FarOasis);
+ Precast.doPrecast(true);
+
+ // clearBeetles();
+ Pather.moveToPreset(me.area, sdk.unittype.Monster, sdk.monsters.preset.Beetleburst);
+ Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Beetleburst));
+
+ if (!me.haveWaypoint(sdk.areas.LostCity)) {
+ Pather.moveToExit(sdk.areas.LostCity, true);
+ if (Pather.moveToPresetMonster(me.area, sdk.monsters.preset.DarkElder)) {
+ Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.DarkElder));
+ }
+ Pather.getWP(sdk.areas.LostCity);
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/bishibosh.js b/libs/SoloPlay/Scripts/bishibosh.js
index 16101d3f..34e69963 100644
--- a/libs/SoloPlay/Scripts/bishibosh.js
+++ b/libs/SoloPlay/Scripts/bishibosh.js
@@ -6,15 +6,24 @@
*/
function bishibosh () {
- if (!me.inArea(sdk.areas.ColdPlains)) {
- Town.doChores();
- Pather.useWaypoint(sdk.areas.ColdPlains);
- }
- Precast.doPrecast(true);
+ if (!me.inArea(sdk.areas.ColdPlains)) {
+ Town.doChores();
+ Pather.useWaypoint(sdk.areas.ColdPlains);
+ }
+ Precast.doPrecast(true);
+ const BISHIBOSH = getLocaleString(sdk.locale.monsters.Bishibosh);
+ let bishDead = false;
- Pather.moveToPreset(sdk.areas.ColdPlains, sdk.unittype.Monster, sdk.monsters.preset.Bishibosh);
- Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.Bishibosh));
- Pickit.pickItems();
+ Pather.moveToPresetMonster(sdk.areas.ColdPlains, sdk.monsters.preset.Bishibosh, { callback: function () {
+ let bishi = Game.getMonster(BISHIBOSH);
+ if (bishi && (bishi.distance < 10 || bishi.dead)) {
+ bishi.dead && (bishDead = true);
+ return true;
+ }
+ return false;
+ } });
+ !bishDead && Attack.clear(15, 0, BISHIBOSH);
+ Pickit.pickItems();
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/bloodraven.js b/libs/SoloPlay/Scripts/bloodraven.js
index 840c1e97..2f4385b3 100644
--- a/libs/SoloPlay/Scripts/bloodraven.js
+++ b/libs/SoloPlay/Scripts/bloodraven.js
@@ -6,75 +6,78 @@
*/
function bloodraven () {
- Town.doChores(false, { fullChores: true });
- myPrint("ÿc8Kolbot-SoloPlayÿc0: starting blood raven");
+ Town.doChores(false, { fullChores: true });
+ myPrint("ÿc8Kolbot-SoloPlayÿc0: starting blood raven");
- if (!Pather.checkWP(sdk.areas.StonyField, true)) {
- Pather.getWP(sdk.areas.StonyField);
- if (me.charlvl < 6) {
- Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 10, 10, false, true);
- Attack.killTarget(getLocaleString(sdk.locale.monsters.Rakanishu));
- }
- } else {
- if (me.hell && Pather.canTeleport() && me.charlvl < 74/*xp penalty makes this not worth it after 74*/) {
- Misc.getExpShrine([sdk.areas.StonyField, sdk.areas.ColdPlains, sdk.areas.DarkWood, sdk.areas.BloodMoor]);
- if (!me.inArea(sdk.areas.ColdPlains)) {
- Town.goToTown() && Pather.useWaypoint(sdk.areas.ColdPlains);
- }
- } else {
- Pather.useWaypoint(sdk.areas.ColdPlains);
- if (me.charlvl < 5) {
- SoloIndex.doneList.includes("cave") ? Attack.clearLevelUntilLevel(5) : Loader.skipTown.push("cave") && Loader.runScript("cave");
- }
- }
- }
+ if (!Pather.checkWP(sdk.areas.StonyField, true)) {
+ Pather.getWP(sdk.areas.StonyField);
+ if (me.charlvl < 6) {
+ Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 10, 10, false, true);
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.Rakanishu));
+ }
+ } else {
+ if (me.hell && Pather.canTeleport() && me.charlvl < 74/*xp penalty makes this not worth it after 74*/) {
+ Misc.getExpShrine([sdk.areas.StonyField, sdk.areas.ColdPlains, sdk.areas.DarkWood, sdk.areas.BloodMoor]);
+ if (!me.inArea(sdk.areas.ColdPlains)) {
+ Town.goToTown() && Pather.useWaypoint(sdk.areas.ColdPlains);
+ }
+ } else {
+ Pather.useWaypoint(sdk.areas.ColdPlains);
+ if (me.charlvl < 5) {
+ SoloIndex.doneList.includes("cave")
+ ? Attack.clearLevelUntilLevel(5)
+ : Loader.skipTown.push("cave") && Loader.runScript("cave");
+ }
+ }
+ }
- Precast.doPrecast(true);
+ Precast.doPrecast(true);
- me.overhead("blood raven");
- Pather.moveToExit(sdk.areas.BurialGrounds, true);
- me.sorceress && !me.normal
- ? Pather.moveNearPreset(sdk.areas.BurialGrounds, sdk.unittype.Monster, sdk.monsters.preset.BloodRaven, 20)
- : Pather.moveToPreset(sdk.areas.BurialGrounds, sdk.unittype.Monster, sdk.monsters.preset.BloodRaven);
- Attack.killTarget("Blood Raven");
- Pickit.pickItems();
+ me.overhead("blood raven");
+ Pather.journeyTo(sdk.areas.BurialGrounds);
+ me.sorceress && !me.normal
+ ? Pather.moveNearPreset(sdk.areas.BurialGrounds, sdk.unittype.Monster, sdk.monsters.preset.BloodRaven, 20)
+ : Pather.moveToPreset(sdk.areas.BurialGrounds, sdk.unittype.Monster, sdk.monsters.preset.BloodRaven);
+ Attack.killTarget("Blood Raven");
+ Pickit.pickItems();
- if (me.normal && !me.bloodraven && me.canTpToTown()) {
- Town.npcInteract("kashya");
- return true;
- } else if (me.paladin && Check.currentBuild().caster && !Pather.canTeleport()) {
- return true;
- }
+ if (me.normal && !me.bloodraven && me.canTpToTown()) {
+ Town.npcInteract("kashya");
+ return true;
+ } else if (me.paladin && Check.currentBuild().caster && !Pather.canTeleport()) {
+ return true;
+ }
- myPrint("blood raven :: starting mausoleum");
+ myPrint("blood raven :: starting mausoleum");
- if (!Pather.moveToExit([sdk.areas.BurialGrounds, sdk.areas.Mausoleum], true)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Mausoleum");
- }
+ if (!Pather.moveToExit([sdk.areas.BurialGrounds, sdk.areas.Mausoleum], true)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Mausoleum");
+ }
- me.inArea(sdk.areas.Mausoleum) && Attack.clearLevel();
+ me.inArea(sdk.areas.Mausoleum) && Attack.clearLevel();
- if (me.hell) {
- switch (me.gametype) {
- case sdk.game.gametype.Classic:
- if (Pather.accessToAct(3)) {
- return true;
- }
+ if (me.hell) {
+ switch (me.gametype) {
+ case sdk.game.gametype.Classic:
+ if (me.accessToAct(3)) {
+ return true;
+ }
- break;
- case sdk.game.gametype.Expansion:
- if ((me.charlvl < 80 || me.charlvl > 85) && !((me.sorceress || me.druid || me.assassin) && Item.getEquippedItem(sdk.body.RightArm).tier < 100000)) {
- return true;
- }
+ break;
+ case sdk.game.gametype.Expansion:
+ if ((me.charlvl < 80 || me.charlvl > 85)
+ && !((me.sorceress || me.druid || me.assassin) && me.equipped.get(sdk.body.RightArm).tier < 100000)) {
+ return true;
+ }
- break;
- }
- } else {
- return true;
- }
+ break;
+ }
+ } else {
+ return true;
+ }
- myPrint("blood raven :: starting crypt");
- Pather.journeyTo(sdk.areas.Crypt) && Attack.clearLevel();
-
- return true;
+ myPrint("blood raven :: starting crypt");
+ Pather.journeyTo(sdk.areas.Crypt) && Attack.clearLevel();
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/boneash.js b/libs/SoloPlay/Scripts/boneash.js
index 1e3003ee..8efedf7a 100644
--- a/libs/SoloPlay/Scripts/boneash.js
+++ b/libs/SoloPlay/Scripts/boneash.js
@@ -6,14 +6,23 @@
*/
function boneash () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting boneash");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting boneash");
- Pather.checkWP(sdk.areas.InnerCloister, true) ? Pather.useWaypoint(sdk.areas.InnerCloister) : Pather.getWP(sdk.areas.InnerCloister);
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.InnerCloister, sdk.areas.Cathedral, true);
- Pather.moveTo(20047, 4898);
- Attack.killTarget(getLocaleString(sdk.locale.monsters.BoneAsh));
+ Pather.checkWP(sdk.areas.InnerCloister, true)
+ ? Pather.useWaypoint(sdk.areas.InnerCloister)
+ : Pather.getWP(sdk.areas.InnerCloister);
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.InnerCloister, sdk.areas.Cathedral, true);
+ Pather.moveTo(20047, 4898);
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.BoneAsh));
- return true;
+ if (!me.haveWaypoint(sdk.areas.CatacombsLvl2)) {
+ myPrint("Getting cata 2 wp");
+ Pather.moveToExit([sdk.areas.CatacombsLvl1, sdk.areas.CatacombsLvl2], true);
+ Pather.getWP(sdk.areas.CatacombsLvl2);
+ Pather.useWaypoint(sdk.areas.RogueEncampment);
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/brain.js b/libs/SoloPlay/Scripts/brain.js
index 30150723..c1ef03a7 100644
--- a/libs/SoloPlay/Scripts/brain.js
+++ b/libs/SoloPlay/Scripts/brain.js
@@ -6,22 +6,24 @@
*/
function brain () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting brain");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting brain");
- Pather.checkWP(sdk.areas.FlayerJungle, true) ? Pather.useWaypoint(sdk.areas.FlayerJungle) : Pather.getWP(sdk.areas.FlayerJungle);
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.FlayerJungle, sdk.areas.FlayerDungeonLvl1, Pather.useTeleport());
- Pather.clearToExit(sdk.areas.FlayerDungeonLvl1, sdk.areas.FlayerDungeonLvl2, Pather.useTeleport());
- Pather.clearToExit(sdk.areas.FlayerDungeonLvl2, sdk.areas.FlayerDungeonLvl3, Pather.useTeleport());
+ Pather.checkWP(sdk.areas.FlayerJungle, true)
+ ? Pather.useWaypoint(sdk.areas.FlayerJungle)
+ : Pather.getWP(sdk.areas.FlayerJungle);
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.FlayerJungle, sdk.areas.FlayerDungeonLvl1, Pather.useTeleport());
+ Pather.clearToExit(sdk.areas.FlayerDungeonLvl1, sdk.areas.FlayerDungeonLvl2, Pather.useTeleport());
+ Pather.clearToExit(sdk.areas.FlayerDungeonLvl2, sdk.areas.FlayerDungeonLvl3, Pather.useTeleport());
- if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsBrainChest)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to get the Brain");
- }
+ if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsBrainChest)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to get the Brain");
+ }
- Attack.clear(0x7);
- Quest.collectItem(sdk.items.quest.KhalimsBrain, sdk.quest.chest.KhalimsBrainChest);
- Quest.stashItem(sdk.items.quest.KhalimsBrain);
+ Attack.kill(getLocaleString(sdk.locale.monsters.WitchDoctorEndugu));
+ Quest.collectItem(sdk.items.quest.KhalimsBrain, sdk.quest.chest.KhalimsBrainChest);
+ Quest.stashItem(sdk.items.quest.KhalimsBrain);
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/cave.js b/libs/SoloPlay/Scripts/cave.js
index d9d2869e..0b525076 100644
--- a/libs/SoloPlay/Scripts/cave.js
+++ b/libs/SoloPlay/Scripts/cave.js
@@ -6,56 +6,82 @@
*/
function cave () {
- !me.inArea(sdk.areas.ColdPlains) && Pather.journeyTo(sdk.areas.ColdPlains);
- Pather.moveToExit([sdk.areas.CaveLvl1, sdk.areas.CaveLvl2], true, true);
+ !me.inArea(sdk.areas.ColdPlains) && Pather.journeyTo(sdk.areas.ColdPlains);
+ if (me.charlvl <= 3) {
+ // scenic route
+ [
+ Pather.getExitCoords(me.area, sdk.areas.StonyField),
+ Pather.getExitCoords(me.area, sdk.areas.BurialGrounds)
+ ].sort(function (a, b) {
+ return [a.x, a.y].distance - [b.x, b.y].distance;
+ }).forEach(function (el) {
+ Pather.moveTo(el.x, el.y);
+ });
+ Pather.moveToExit(sdk.areas.ColdPlains, true);
+ } else if (me.charlvl <= 5 && !me.haveWaypoint(sdk.areas.StonyField)) {
+ const cLvl1 = Pather.getExitCoords(me.area, sdk.areas.CaveLvl1);
+ const sFields = Pather.getExitCoords(me.area, sdk.areas.StonyField);
+ const plainsWpCoords = AreaData.get(sdk.areas.ColdPlains).waypointCoords();
+ const wpToCave = getDistance(cLvl1.x, cLvl1.y, plainsWpCoords.x, plainsWpCoords.y);
+ // const wpToStony = getDistance(sFields.x, sFields.y, plainsWpCoords.x, plainsWpCoords.y);
+ const stonyToCave = getDistance(cLvl1.x, cLvl1.y, sFields.x, sFields.y);
+ Pather.moveToExit(sdk.areas.StonyField, true);
+ Pather.getWP(sdk.areas.StonyField);
+ if (sFields.distance + stonyToCave < wpToCave) {
+ Pather.moveToExit(sdk.areas.ColdPlains, true);
+ } else {
+ Pather.useWaypoint(sdk.areas.ColdPlains);
+ }
+ }
+ Pather.moveToExit([sdk.areas.CaveLvl1, sdk.areas.CaveLvl2], true, true);
- // coords from sonic
- const clearCoords = [
- {"x": 7549, "y": 12554, "radius": 10},
- {"x": 7560, "y": 12551, "radius": 10},
- {"x": 7573, "y": 12550, "radius": 10},
- {"x": 7576, "y": 12563, "radius": 10},
- {"x": 7586, "y": 12564, "radius": 10},
- {"x": 7596, "y": 12567, "radius": 10},
- {"x": 7596, "y": 12578, "radius": 10},
- {"x": 7606, "y": 12559, "radius": 10},
- {"x": 7612, "y": 12549, "radius": 10},
- {"x": 7611, "y": 12540, "radius": 10},
- {"x": 7608, "y": 12528, "radius": 10},
- {"x": 7595, "y": 12529, "radius": 10},
- {"x": 7588, "y": 12519, "radius": 10},
- {"x": 7574, "y": 12520, "radius": 10},
- {"x": 7564, "y": 12523, "radius": 10},
- {"x": 7568, "y": 12567, "radius": 10},
- {"x": 7565, "y": 12574, "radius": 10},
- {"x": 7560, "y": 12583, "radius": 10},
- {"x": 7554, "y": 12578, "radius": 10},
- {"x": 7546, "y": 12573, "radius": 10},
- {"x": 7537, "y": 12573, "radius": 10},
- {"x": 7528, "y": 12574, "radius": 10},
- {"x": 7519, "y": 12575, "radius": 10},
- {"x": 7510, "y": 12566, "radius": 10},
- {"x": 7510, "y": 12584, "radius": 10},
- {"x": 7514, "y": 12593, "radius": 10},
- {"x": 7521, "y": 12595, "radius": 10},
- {"x": 7526, "y": 12600, "radius": 10},
- {"x": 7525, "y": 12606, "radius": 10},
- {"x": 7535, "y": 12596, "radius": 10},
- {"x": 7543, "y": 12596, "radius": 10},
- {"x": 7550, "y": 12596, "radius": 10},
- {"x": 7557, "y": 12595, "radius": 10},
- {"x": 7556, "y": 12605, "radius": 10},
- {"x": 7556, "y": 12611, "radius": 10},
- {"x": 7566, "y": 12608, "radius": 10},
- {"x": 7580, "y": 12613, "radius": 10},
- {"x": 7589, "y": 12610, "radius": 10},
- {"x": 7594, "y": 12601, "radius": 10},
- {"x": 7600, "y": 12601, "radius": 10}
- ];
+ // coords from sonic
+ const clearCoords = [
+ { "x": 7549, "y": 12554, "radius": 10 },
+ { "x": 7560, "y": 12551, "radius": 10 },
+ { "x": 7573, "y": 12550, "radius": 10 },
+ { "x": 7576, "y": 12563, "radius": 10 },
+ { "x": 7586, "y": 12564, "radius": 10 },
+ { "x": 7596, "y": 12567, "radius": 10 },
+ { "x": 7596, "y": 12578, "radius": 10 },
+ { "x": 7606, "y": 12559, "radius": 10 },
+ { "x": 7612, "y": 12549, "radius": 10 },
+ { "x": 7611, "y": 12540, "radius": 10 },
+ { "x": 7608, "y": 12528, "radius": 10 },
+ { "x": 7595, "y": 12529, "radius": 10 },
+ { "x": 7588, "y": 12519, "radius": 10 },
+ { "x": 7574, "y": 12520, "radius": 10 },
+ { "x": 7564, "y": 12523, "radius": 10 },
+ { "x": 7568, "y": 12567, "radius": 10 },
+ { "x": 7565, "y": 12574, "radius": 10 },
+ { "x": 7560, "y": 12583, "radius": 10 },
+ { "x": 7554, "y": 12578, "radius": 10 },
+ { "x": 7546, "y": 12573, "radius": 10 },
+ { "x": 7537, "y": 12573, "radius": 10 },
+ { "x": 7528, "y": 12574, "radius": 10 },
+ { "x": 7519, "y": 12575, "radius": 10 },
+ { "x": 7510, "y": 12566, "radius": 10 },
+ { "x": 7510, "y": 12584, "radius": 10 },
+ { "x": 7514, "y": 12593, "radius": 10 },
+ { "x": 7521, "y": 12595, "radius": 10 },
+ { "x": 7526, "y": 12600, "radius": 10 },
+ { "x": 7525, "y": 12606, "radius": 10 },
+ { "x": 7535, "y": 12596, "radius": 10 },
+ { "x": 7543, "y": 12596, "radius": 10 },
+ { "x": 7550, "y": 12596, "radius": 10 },
+ { "x": 7557, "y": 12595, "radius": 10 },
+ { "x": 7556, "y": 12605, "radius": 10 },
+ { "x": 7556, "y": 12611, "radius": 10 },
+ { "x": 7566, "y": 12608, "radius": 10 },
+ { "x": 7580, "y": 12613, "radius": 10 },
+ { "x": 7589, "y": 12610, "radius": 10 },
+ { "x": 7594, "y": 12601, "radius": 10 },
+ { "x": 7600, "y": 12601, "radius": 10 }
+ ];
- !me.inArea(sdk.areas.CaveLvl2) && Pather.journeyTo(sdk.areas.CaveLvl2);
- Attack.clearCoordList(clearCoords, 10);
- Town.goToTown();
+ !me.inArea(sdk.areas.CaveLvl2) && Pather.journeyTo(sdk.areas.CaveLvl2);
+ Attack.clearCoordList(clearCoords, 10);
+ Town.goToTown();
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/corpsefire.js b/libs/SoloPlay/Scripts/corpsefire.js
index 92735cf8..ad6851a3 100644
--- a/libs/SoloPlay/Scripts/corpsefire.js
+++ b/libs/SoloPlay/Scripts/corpsefire.js
@@ -5,15 +5,15 @@
*
*/
-function corpsefire() {
- myPrint("starting corpsefire");
- Town.doChores(null, {thawing: me.coldRes < 75, antidote: me.poisonRes < 75});
+function corpsefire () {
+ myPrint("starting corpsefire");
+ Town.doChores(null, { thawing: me.coldRes < 75, antidote: me.poisonRes < 75 });
- Pather.checkWP(sdk.areas.ColdPlains, true) ? Pather.useWaypoint(sdk.areas.ColdPlains) : Pather.getWP(sdk.areas.ColdPlains);
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.ColdPlains, sdk.areas.BloodMoor, true);
- Pather.clearToExit(sdk.areas.BloodMoor, sdk.areas.DenofEvil, true);
- Attack.clearLevel();
+ Pather.checkWP(sdk.areas.ColdPlains, true) ? Pather.useWaypoint(sdk.areas.ColdPlains) : Pather.getWP(sdk.areas.ColdPlains);
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.ColdPlains, sdk.areas.BloodMoor, true);
+ Pather.clearToExit(sdk.areas.BloodMoor, sdk.areas.DenofEvil, true);
+ Attack.clearLevel();
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/countess.js b/libs/SoloPlay/Scripts/countess.js
index f2b94fc8..e63b5bb0 100644
--- a/libs/SoloPlay/Scripts/countess.js
+++ b/libs/SoloPlay/Scripts/countess.js
@@ -6,38 +6,65 @@
*/
function countess () {
- const floors = [
- sdk.areas.ForgottenTower, sdk.areas.TowerCellarLvl1,
- sdk.areas.TowerCellarLvl2, sdk.areas.TowerCellarLvl3,
- sdk.areas.TowerCellarLvl4, sdk.areas.TowerCellarLvl5
- ];
- Town.doChores(false, { fullChores: true });
- myPrint("starting countess");
-
- Pather.checkWP(sdk.areas.BlackMarsh, true) ? Pather.useWaypoint(sdk.areas.BlackMarsh) : Pather.getWP(sdk.areas.BlackMarsh);
- Precast.doPrecast(true);
-
- if (me.charlvl < 12) {
- // @todo - low level, lets take a scenic route and kill those hawk nests
- }
-
- try {
- if (me.charlvl < 15) {
- for (let i = 0; i < floors.length; i += 1) {
- Pather.moveToExit(floors[i], true);
- Attack.clear(0x7);
- }
- } else {
- Pather.moveToExit(floors, true);
- }
-
- Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.SuperChest);
- Attack.killTarget(getLocaleString(sdk.locale.monsters.TheCountess));
- } catch (err) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Countess: " + err);
- }
-
- Pickit.pickItems();
-
- return true;
+ const floors = [
+ sdk.areas.ForgottenTower, sdk.areas.TowerCellarLvl1,
+ sdk.areas.TowerCellarLvl2, sdk.areas.TowerCellarLvl3,
+ sdk.areas.TowerCellarLvl4, sdk.areas.TowerCellarLvl5
+ ];
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting countess");
+
+ Pather.checkWP(sdk.areas.BlackMarsh, true)
+ ? Pather.useWaypoint(sdk.areas.BlackMarsh)
+ : Pather.getWP(sdk.areas.BlackMarsh);
+ Precast.doPrecast(true);
+ let forQuest = !Misc.checkQuest(sdk.quest.id.ForgottenTower, sdk.quest.states.Completed);
+
+ if (me.charlvl < 12) {
+ // @todo - low level, lets take a scenic route and kill those hawk nests
+ }
+
+ try {
+ if (me.charlvl < 15) {
+ for (let i = 0; i < floors.length; i += 1) {
+ Pather.moveToExit(floors[i], true);
+ Attack.clear(0x7);
+ }
+ } else {
+ Pather.moveToExit(floors, true);
+ }
+
+ Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.SuperChest);
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.TheCountess));
+
+ if (forQuest) {
+ Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.SuperChest);
+ let _items = new Set();
+ let _oldSize = 0;
+ let itemEvent = function (gid, mode) {
+ if (gid > 0 && mode === 0) {
+ _items.add(gid);
+ }
+ };
+ try {
+ addEventListener("itemaction", itemEvent);
+ Misc.poll(function () {
+ if (_items.size > _oldSize) {
+ _oldSize = _items.size;
+ return false;
+ }
+
+ return _items.size > 0 && _items.size === _oldSize;
+ }, Time.seconds(5), Time.seconds(1));
+ } finally {
+ removeEventListener("itemaction", itemEvent);
+ }
+ }
+ } catch (err) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Countess: " + err);
+ }
+
+ Pickit.pickItems();
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/cows.js b/libs/SoloPlay/Scripts/cows.js
index 115887b5..23f601c0 100644
--- a/libs/SoloPlay/Scripts/cows.js
+++ b/libs/SoloPlay/Scripts/cows.js
@@ -6,159 +6,160 @@
*/
function cows () {
- this.getLeg = function () {
- if (me.getItem(sdk.items.quest.WirtsLeg)) return me.getItem(sdk.items.quest.WirtsLeg);
-
- // Cain is incomplete, complete it then continue @isid0re
- if (!me.tristram) {
- Loader.runScript("tristram");
- includeIfNotIncluded("SoloPlay/Scripts/tristram.js");
- !me.inTown && Town.goToTown();
- }
-
- Pather.useWaypoint(sdk.areas.StonyField);
- Precast.doPrecast(true);
- Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 8, 8);
- Pather.usePortal(sdk.areas.Tristram);
-
- if (me.inArea(sdk.areas.Tristram)) {
- Pather.moveTo(25048, 5177);
- Quest.collectItem(sdk.items.quest.WirtsLeg, sdk.quest.chest.Wirt);
- Pickit.pickItems();
- Town.goToTown();
- } else {
- return false;
- }
-
- return me.getItem(sdk.items.quest.WirtsLeg);
- };
-
- this.openPortal = function (portalID, ...classIDS) {
- !me.inArea(sdk.areas.RogueEncampment) && Town.goToTown(1);
-
- 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);
- scroll = npc.getItem(sdk.items.ScrollofTownPortal);
- let scrollCost = scroll.getItemCost(sdk.items.cost.ToBuy);
- tpTome.forEach(function (book) {
- while (book.getStat(sdk.stats.Quantity) < 20) {
- scroll = npc.getItem(sdk.items.ScrollofTownPortal);
-
- if (!!scroll && scrollCost < me.gold) {
- scroll.buy(true);
- } else {
- break;
- }
-
- delay(20);
- }
- });
- }
- }
- } finally {
- me.cancelUIFlags();
- }
-
- !Town.openStash() && console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to open stash. (openPortal)");
- !Cubing.emptyCube() && console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to empty cube. (openPortal)");
- if (!me.getItem(sdk.items.quest.WirtsLeg)) return false;
-
- for (let classID of classIDS) {
- let cubingItem;
- if (classID === sdk.items.TomeofTownPortal) {
- // select the tome we just bought rather than the one we had by it's position in our invo
- cubingItem = me.getItemsEx(sdk.items.TomeofTownPortal).filter(i => i.isInInventory).sort((a, b) => a.x - b.x).first();
- } else {
- cubingItem = me.getItem(classID);
- }
-
- if (!cubingItem || !Storage.Cube.MoveTo(cubingItem)) {
- return false;
- }
- }
-
- Misc.poll(() => Cubing.openCube(), Time.seconds(10), 1000);
-
- let tick = getTickCount();
-
- while (getTickCount() - tick < 5000) {
- if (Cubing.openCube()) {
- transmute();
- delay(750);
- let cowPortal = Pather.getPortal(portalID);
-
- if (cowPortal) {
- break;
- }
- }
- }
-
- me.cancel();
- Town.sortInventory();
-
- return true;
- };
-
- if (!me.diffCompleted) throw new Error("Final quest incomplete, cannot make cows yet");
-
- // START
- Town.doChores(false, { fullChores: true });
- myPrint("starting cows");
-
- if (!Pather.getPortal(sdk.areas.MooMooFarm) && !this.getLeg()) return true;
-
- Town.doChores();
- this.openPortal(sdk.areas.MooMooFarm, sdk.items.quest.WirtsLeg, sdk.items.TomeofTownPortal);
- Town.fillTome(sdk.items.TomeofTownPortal);
-
- // when does this become not worth it
- if (Pather.canTeleport()) {
- Misc.getExpShrine([sdk.areas.StonyField, sdk.areas.DarkWood, sdk.areas.BlackMarsh]);
- } else {
- Misc.getExpShrine([sdk.areas.BloodMoor]);
- }
-
- Town.move("stash");
-
- if (Misc.poll(() => Pather.usePortal(sdk.areas.MooMooFarm), Time.seconds(30), Time.seconds(1))) {
- const Worker = require("../../modules/Worker");
- let kingTick = getTickCount();
- let king;
- let kingPreset;
-
- Worker.runInBackground.kingTracker = function () {
- if (me.inArea(sdk.areas.MooMooFarm)) {
- if (getTickCount() - kingTick < 1000) return true;
- kingTick = getTickCount();
- king = Game.getMonster(getLocaleString(sdk.locale.monsters.TheCowKing));
- // only get the preset unit once
- !kingPreset && (kingPreset = Game.getPresetMonster(me.area, sdk.monsters.preset.TheCowKing));
-
- if (king && kingPreset) {
- if (getDistance(me.x, me.y, getRoom(kingPreset.roomx * 5 + kingPreset.x), getRoom(kingPreset.roomy * 5 + kingPreset.y)) <= 25) {
- myPrint("exit cows. Near the king");
- throw new Error("exit cows. Near the king");
- }
- }
- }
-
- return true;
- };
-
- Precast.doPrecast(true);
- Common.Cows.clearCowLevel();
- }
-
- return true;
+ const getLeg = function () {
+ if (me.getItem(sdk.items.quest.WirtsLeg)) return me.getItem(sdk.items.quest.WirtsLeg);
+
+ // Cain is incomplete, complete it then continue @isid0re
+ if (!me.tristram) {
+ Loader.runScript("tristram");
+ includeIfNotIncluded("SoloPlay/Scripts/tristram.js");
+ !me.inTown && Town.goToTown();
+ }
+
+ Pather.useWaypoint(sdk.areas.StonyField);
+ Precast.doPrecast(true);
+ Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 8, 8);
+ Pather.usePortal(sdk.areas.Tristram);
+
+ if (me.inArea(sdk.areas.Tristram)) {
+ Pather.moveTo(25048, 5177);
+ Quest.collectItem(sdk.items.quest.WirtsLeg, sdk.quest.chest.Wirt);
+ Pickit.pickItems();
+ Town.goToTown();
+ } else {
+ return false;
+ }
+
+ return me.getItem(sdk.items.quest.WirtsLeg);
+ };
+
+ const openPortal = function (portalID, ...classIDS) {
+ !me.inArea(sdk.areas.RogueEncampment) && Town.goToTown(1);
+
+ 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);
+ scroll = npc.getItem(sdk.items.ScrollofTownPortal);
+ let scrollCost = scroll.getItemCost(sdk.items.cost.ToBuy);
+ tpTome.forEach(function (book) {
+ while (book.getStat(sdk.stats.Quantity) < 20) {
+ scroll = npc.getItem(sdk.items.ScrollofTownPortal);
+
+ if (!!scroll && scrollCost < me.gold) {
+ scroll.buy(true);
+ } else {
+ break;
+ }
+
+ delay(20);
+ }
+ });
+ }
+ }
+ } finally {
+ me.cancelUIFlags();
+ }
+
+ !Town.openStash() && console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to open stash. (openPortal)");
+ !Cubing.emptyCube() && console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to empty cube. (openPortal)");
+ if (!me.getItem(sdk.items.quest.WirtsLeg)) return false;
+
+ for (let classID of classIDS) {
+ let cubingItem;
+ if (classID === sdk.items.TomeofTownPortal) {
+ // select the tome we just bought rather than the one we had by it's position in our invo
+ cubingItem = me.getItemsEx(sdk.items.TomeofTownPortal).filter(i => i.isInInventory).sort((a, b) => a.x - b.x).first();
+ } else {
+ cubingItem = me.getItem(classID);
+ }
+
+ if (!cubingItem || !Storage.Cube.MoveTo(cubingItem)) {
+ return false;
+ }
+ }
+
+ Misc.poll(() => Cubing.openCube(), Time.seconds(10), 1000);
+
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < 5000) {
+ if (Cubing.openCube()) {
+ transmute();
+ delay(750);
+ let cowPortal = Pather.getPortal(portalID);
+
+ if (cowPortal) {
+ break;
+ }
+ }
+ }
+
+ me.cancel();
+ me.sortInventory();
+
+ return true;
+ };
+
+ if (!me.diffCompleted) throw new Error("Final quest incomplete, cannot make cows yet");
+
+ // START
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting cows");
+
+ if (!Pather.getPortal(sdk.areas.MooMooFarm) && !getLeg()) return true;
+
+ Town.doChores();
+ openPortal(sdk.areas.MooMooFarm, sdk.items.quest.WirtsLeg, sdk.items.TomeofTownPortal);
+ NPCAction.fillTome(sdk.items.TomeofTownPortal);
+
+ // when does this become not worth it
+ if (Pather.canTeleport()) {
+ Misc.getExpShrine([sdk.areas.StonyField, sdk.areas.DarkWood, sdk.areas.BlackMarsh]);
+ } else {
+ Misc.getExpShrine([sdk.areas.BloodMoor]);
+ }
+
+ Town.move("stash");
+
+ if (Misc.poll(() => Pather.usePortal(sdk.areas.MooMooFarm), Time.seconds(30), Time.seconds(1))) {
+ include("core/Common/Cows.js");
+ const Worker = require("../../modules/Worker");
+ let kingTick = getTickCount();
+ let king;
+ let kingPreset;
+
+ Worker.runInBackground.kingTracker = function () {
+ if (me.inArea(sdk.areas.MooMooFarm)) {
+ if (getTickCount() - kingTick < 1000) return true;
+ kingTick = getTickCount();
+ king = Game.getMonster(getLocaleString(sdk.locale.monsters.TheCowKing));
+ // only get the preset unit once
+ !kingPreset && (kingPreset = Game.getPresetMonster(me.area, sdk.monsters.preset.TheCowKing));
+
+ if (king && kingPreset) {
+ if (getDistance(me.x, me.y, getRoom(kingPreset.roomx * 5 + kingPreset.x), getRoom(kingPreset.roomy * 5 + kingPreset.y)) <= 25) {
+ myPrint("exit cows. Near the king");
+ throw new Error("exit cows. Near the king");
+ }
+ }
+ }
+
+ return true;
+ };
+
+ Precast.doPrecast(true);
+ Common.Cows.clearCowLevel();
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/creepingfeature.js b/libs/SoloPlay/Scripts/creepingfeature.js
new file mode 100644
index 00000000..e1311e94
--- /dev/null
+++ b/libs/SoloPlay/Scripts/creepingfeature.js
@@ -0,0 +1,18 @@
+/**
+* @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/libs/SoloPlay/Scripts/cube.js b/libs/SoloPlay/Scripts/cube.js
index 36df134c..73c60b0f 100644
--- a/libs/SoloPlay/Scripts/cube.js
+++ b/libs/SoloPlay/Scripts/cube.js
@@ -6,17 +6,17 @@
*/
function cube () {
- Town.doChores(false, { fullChores: true });
- myPrint("ÿc8Kolbot-SoloPlayÿc0: starting cube");
+ Town.doChores(false, { fullChores: true });
+ myPrint("ÿc8Kolbot-SoloPlayÿc0: starting cube");
- Pather.checkWP(sdk.areas.HallsoftheDeadLvl2, true) ? Pather.useWaypoint(sdk.areas.HallsoftheDeadLvl2) : Pather.getWP(sdk.areas.HallsoftheDeadLvl2);
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.HallsoftheDeadLvl2, sdk.areas.HallsoftheDeadLvl3, Pather.useTeleport());
- Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricCubeChest);
- Attack.securePosition(me.x, me.y, 30, 3000, true);
- Quest.collectItem(sdk.items.quest.Cube, sdk.quest.chest.HoradricCubeChest);
- Quest.stashItem(sdk.items.quest.Cube);
- Town.sortStash(true);
+ Pather.checkWP(sdk.areas.HallsoftheDeadLvl2, true) ? Pather.useWaypoint(sdk.areas.HallsoftheDeadLvl2) : Pather.getWP(sdk.areas.HallsoftheDeadLvl2);
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.HallsoftheDeadLvl2, sdk.areas.HallsoftheDeadLvl3, Pather.useTeleport());
+ Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricCubeChest);
+ Attack.securePosition(me.x, me.y, 30, 3000, true);
+ Quest.collectItem(sdk.items.quest.Cube, sdk.quest.chest.HoradricCubeChest);
+ Quest.stashItem(sdk.items.quest.Cube);
+ Town.sortStash(true);
- return me.getItem(sdk.items.quest.Cube);
+ return me.getItem(sdk.items.quest.Cube);
}
diff --git a/libs/SoloPlay/Scripts/den.js b/libs/SoloPlay/Scripts/den.js
index c7457989..5d0099ab 100644
--- a/libs/SoloPlay/Scripts/den.js
+++ b/libs/SoloPlay/Scripts/den.js
@@ -6,170 +6,191 @@
*/
function den () {
- const customGoToTown = function () {
- if (me.inTown) return;
- if (!me.canTpToTown()) {
- Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.ColdPlains], true);
- Pather.getWP(sdk.areas.ColdPlains);
- Pather.useWaypoint(sdk.areas.RogueEncampment);
- } else {
- Town.goToTown();
- }
- };
-
- myPrint("starting den");
-
- me.gold > 500 && Town.initNPC("repair", "shopItems") && Town.shopItems(500, [sdk.items.type.Bow, sdk.items.type.Crossbow, sdk.items.type.Belt]);
- me.gold > 1000 && Town.buyPots(12, "stamina", true);
-
- if (!Pather.checkWP(sdk.areas.ColdPlains) || me.charlvl < 4) {
- Pather.moveToExit(sdk.areas.BloodMoor, true);
-
- try {
- if (me.charlvl < 2) {
- let cPlains = Pather.getExitCoords(me.area, sdk.areas.ColdPlains);
- // sort by the ones closest to us but also by distance to cold plains exit so we end up there
- Game.getPresetObjects(me.area)
- .filter(el => Misc.presetShrineIds.includes(el.id) || Misc.presetChestIds.includes(el.id))
- .sort((a, b) => {
- let [aX, aY] = [a.roomx * 5 + a.x, a.roomy * 5 + a.y];
- let [bX, bY] = [b.roomx * 5 + b.x, b.roomy * 5 + b.y];
- if ([aX, aY].distance < [bX, bY].distance && getDistance(aX, aY, cPlains.x, cPlains.y) > getDistance(bX, bY, cPlains.x, cPlains.y)) {
- return -1;
- }
- if ([aX, aY].distance > [bX, bY].distance && getDistance(aX, aY, cPlains.x, cPlains.y) < getDistance(bX, bY, cPlains.x, cPlains.y)) {
- return 1;
- }
- return [aX, aY].distance - [bX, bY].distance;
- })
- .forEach(el => Pather.moveNearUnit(el, 7));
- } else {
- Pather.moveNearPreset(sdk.areas.BloodMoor, sdk.unittype.Object, sdk.objects.SuperChest, 8) && Misc.openChests(8);
- }
- } catch (e) {
- console.warn(e.message ? e.message : e);
- }
- Pather.getWP(sdk.areas.ColdPlains);
-
- // check if we need to do chores - if so use waypoint to town (preserves portal if we made one at den) - return to cold plains using waypoint
- Storage.Inventory.UsedSpacePercent() > 50 && Pather.useWaypoint(sdk.areas.RogueEncampment) && Town.doChores() && Pather.useWaypoint(sdk.areas.ColdPlains);
- }
-
- if (me.charlvl < 8) {
- me.sorceress && me.charlvl >= 2 && me.charlvl < 8 && Loader.skipTown.push("bishibosh") && Loader.runScript("bishibosh");
- me.charlvl < 6 && Loader.skipTown.push("cave") && Loader.runScript("cave");
- }
-
- if (me.charlvl < 8 || me.gold < 1000) {
- SoloIndex.retryList.push("den");
- return true;
- }
-
- Town.doChores(false, (me.charlvl < 18 ? { stamina: true } : {}));
- !me.inArea(sdk.areas.RogueEncampment) && Town.goToTown(1);
- Town.move("portalspot");
-
- // Check if there are any portals before trying to use one
- let p = Game.getObject(sdk.objects.BluePortal);
-
- if (!!p && [sdk.areas.BloodMoor, sdk.areas.DenofEvil].includes(p.objtype)) {
- Pather.usePortal(null, me.name);
- } else {
- Pather.moveToExit(sdk.areas.BloodMoor, true);
- }
-
- // START
- let attempt = 1;
- let killTracker = false;
- let denLights = false;
- Precast.doPrecast(true);
- Attack.clear(20);
- Pather.moveToExit(sdk.areas.DenofEvil, true);
-
- this.denLightsListener = function (bytes = []) {
- if (!bytes.length) return;
- // d2gs unique event - den lights
- if (bytes[0] === 0x89) {
- denLights = true;
- }
- };
-
- if (me.inArea(sdk.areas.DenofEvil)) {
- addEventListener("gamepacket", this.denLightsListener);
- const Worker = require("../../modules/Worker");
- let corpsefire;
- let corpseTick = getTickCount();
-
- try {
- if (!me.normal) {
- Worker.runInBackground.corpseTracker = function () {
- if (killTracker) return false;
- if (me.inArea(sdk.areas.DenofEvil)) {
- if (getTickCount() - corpseTick < 1000) return true;
- corpseTick = getTickCount();
- corpsefire = Game.getMonster(getLocaleString(sdk.locale.monsters.Corpsefire));
-
- if (corpsefire) {
- if (!Attack.canAttack(corpsefire)) {
- killTracker = true;
- throw new Error("Exit den. Corpsefire is immune");
- } else {
- // we can attack, no need to run this in the background any longer
- return false;
- }
- }
- }
-
- return true;
- };
- }
-
- Worker.runInBackground.denLightsTracker = function () {
- if (killTracker) return false;
- if (me.inArea(sdk.areas.DenofEvil)) {
- if (denLights) {
- killTracker = true;
- throw new Error("EVENT :: DEN COMPLETE");
- }
- }
-
- return true;
- };
-
- while (!Misc.checkQuest(sdk.quest.id.DenofEvil, sdk.quest.states.Completed)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Clearing den attempt: " + attempt);
- Attack.clearLevel();
-
- if (!me.inArea(sdk.areas.DenofEvil)) {
- break;
- }
-
- if (Misc.checkQuest(sdk.quest.id.DenofEvil, sdk.quest.states.PartyMemberComplete)) {
- customGoToTown();
- Town.npcInteract("akara");
-
- break;
- }
-
- if (attempt >= 5) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to complete den");
- customGoToTown();
-
- break;
- }
-
- attempt++;
- }
-
- } catch (e) {
- //
- } finally {
- removeEventListener("gamepacket", this.denLightsListener);
- SoloEvents.finishDen();
- killTracker = true;
- me.getStat(sdk.stats.NewSkills) > 0 && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
- }
- }
-
- return true;
+ const customGoToTown = function () {
+ if (me.inTown) return;
+ if (!me.canTpToTown()) {
+ Pather.moveToExit([sdk.areas.BloodMoor, sdk.areas.ColdPlains], true);
+ Pather.getWP(sdk.areas.ColdPlains);
+ Pather.useWaypoint(sdk.areas.RogueEncampment);
+ } else {
+ Town.goToTown();
+ }
+ };
+
+ myPrint("starting den");
+
+ if (me.charlvl < 10 && me.gold > 500 && Town.initNPC("repair", "shopItems")) {
+ NPCAction.shopItems(500, [sdk.items.type.Bow, sdk.items.type.Crossbow, sdk.items.type.Belt]);
+ }
+ me.gold > 1000 && Town.buyPots(12, "stamina", true);
+
+ if (!Pather.checkWP(sdk.areas.ColdPlains) || me.charlvl < 4) {
+ Pather.moveToExit(sdk.areas.BloodMoor, true);
+
+ try {
+ if (me.charlvl < 2) {
+ let cPlains = Pather.getExitCoords(me.area, sdk.areas.ColdPlains);
+ // sort by the ones closest to us but also by distance to cold plains exit so we end up there
+ Game.getPresetObjects(me.area)
+ .filter(function (el) {
+ return sdk.shrines.Presets.includes(el.id) || sdk.objects.chestIds.includes(el.id);
+ })
+ .sort(function (a, b) {
+ const [aX, aY] = [a.roomx * 5 + a.x, a.roomy * 5 + a.y];
+ const [bX, bY] = [b.roomx * 5 + b.x, b.roomy * 5 + b.y];
+ const [aDistMe, bDistMe] = [[aX, aY].distance, [bX, bY].distance];
+ const [aDistExit, bDistExit] = [
+ getDistance(aX, aY, cPlains.x, cPlains.y),
+ getDistance(bX, bY, cPlains.x, cPlains.y)
+ ];
+ if (aDistMe < bDistMe && aDistExit > bDistExit) {
+ return -1;
+ }
+ if (aDistMe > bDistMe && aDistExit < bDistExit) {
+ return 1;
+ }
+ return aDistMe - bDistMe;
+ })
+ .forEach(function (el) {
+ Pather.moveNearUnit(el, 7);
+ });
+ } else {
+ if (Pather.moveNearPreset(sdk.areas.BloodMoor, sdk.unittype.Object, sdk.objects.SuperChest, 8)) {
+ Misc.openChests(8);
+ }
+ }
+ } catch (e) {
+ console.warn(e.message ? e.message : e);
+ }
+ Pather.getWP(sdk.areas.ColdPlains);
+
+ // check if we need to do chores - if so use waypoint to town (preserves portal if we made one at den) - return to cold plains using waypoint
+ (Storage.Inventory.UsedSpacePercent() > 50
+ && Pather.useWaypoint(sdk.areas.RogueEncampment)
+ && Town.doChores()
+ && Pather.useWaypoint(sdk.areas.ColdPlains));
+ }
+
+ if (me.charlvl < 8) {
+ if (me.sorceress && me.charlvl >= 2 && me.charlvl < 8
+ && Loader.skipTown.push("bishibosh")) {
+ Loader.runScript("bishibosh");
+ }
+ if (me.charlvl < 6 && Loader.skipTown.push("cave")) {
+ Loader.runScript("cave");
+ }
+ }
+
+ if (me.charlvl < 8 || me.gold < 1000) {
+ SoloIndex.retryList.push("den");
+ return true;
+ }
+
+ Town.doChores(false, (me.charlvl < 18 ? { stamina: true } : {}));
+ !me.inArea(sdk.areas.RogueEncampment) && Town.goToTown(1);
+ Town.move("portalspot");
+
+ // Check if there are any portals before trying to use one
+ let p = Game.getObject(sdk.objects.BluePortal);
+
+ if (!!p && [sdk.areas.BloodMoor, sdk.areas.DenofEvil].includes(p.objtype)) {
+ Pather.usePortal(null, me.name);
+ } else {
+ Pather.moveToExit(sdk.areas.BloodMoor, true);
+ }
+
+ // START
+ let attempt = 1;
+ let killTracker = false;
+ let denLights = false;
+ Precast.doPrecast(true);
+ Attack.clear(20);
+ Pather.moveToExit(sdk.areas.DenofEvil, true);
+
+ const denLightsListener = function (bytes = []) {
+ if (!bytes.length) return;
+ // d2gs unique event - den lights
+ if (bytes[0] === 0x89) {
+ denLights = true;
+ }
+ };
+
+ if (me.inArea(sdk.areas.DenofEvil)) {
+ addEventListener("gamepacket", denLightsListener);
+ const Worker = require("../../modules/Worker");
+ let corpsefire;
+ let corpseTick = getTickCount();
+
+ try {
+ if (!me.normal) {
+ Worker.runInBackground.corpseTracker = function () {
+ if (killTracker) return false;
+ if (me.inArea(sdk.areas.DenofEvil)) {
+ if (getTickCount() - corpseTick < 1000) return true;
+ corpseTick = getTickCount();
+ corpsefire = Game.getMonster(getLocaleString(sdk.locale.monsters.Corpsefire));
+
+ if (corpsefire) {
+ if (!Attack.canAttack(corpsefire)) {
+ killTracker = true;
+ throw new Error("Exit den. Corpsefire is immune");
+ } else {
+ // we can attack, no need to run this in the background any longer
+ return false;
+ }
+ }
+ }
+
+ return true;
+ };
+ }
+
+ Worker.runInBackground.denLightsTracker = function () {
+ if (killTracker) return false;
+ if (me.inArea(sdk.areas.DenofEvil)) {
+ if (denLights) {
+ killTracker = true;
+ throw new Error("EVENT :: DEN COMPLETE");
+ }
+ }
+
+ return true;
+ };
+
+ while (!Misc.checkQuest(sdk.quest.id.DenofEvil, sdk.quest.states.Completed)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Clearing den attempt: " + attempt);
+ Attack.clearLevel();
+
+ if (!me.inArea(sdk.areas.DenofEvil)) {
+ break;
+ }
+
+ if (Misc.checkQuest(sdk.quest.id.DenofEvil, sdk.quest.states.PartyMemberComplete)) {
+ customGoToTown();
+ Town.npcInteract("akara");
+
+ break;
+ }
+
+ if (attempt >= 5) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to complete den");
+ customGoToTown();
+
+ break;
+ }
+
+ attempt++;
+ }
+
+ } catch (e) {
+ //
+ } finally {
+ removeEventListener("gamepacket", denLightsListener);
+ SoloEvents.finishDen();
+ killTracker = true;
+ me.getStat(sdk.stats.NewSkills) > 0 && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
+ }
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/developermode.js b/libs/SoloPlay/Scripts/developermode.js
index fdf8a5fc..928f1e74 100644
--- a/libs/SoloPlay/Scripts/developermode.js
+++ b/libs/SoloPlay/Scripts/developermode.js
@@ -4,275 +4,269 @@
* @desc developer mode made easy
*
*/
-include("UnitInfo.js");
function developermode() {
- let uInfo = null;
- let [done, action, command, userAddon, test] = [false, false, false, false, false];
- let [watchSent, watchRecv, blockSent, blockRecv] = [[], [], [], []];
- 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":
- myPrint("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");
- break;
- }
-
- action = msgList[1].toLowerCase();
-
- break;
- case "done":
- done = true;
-
- break;
- case "testing":
- test = true;
-
- break;
- case "command":
- if (msgList.length < 2) {
- console.log("ÿc1Missing arguments");
- break;
- }
-
- 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;
- };
-
- myPrint("ÿc8Kolbot-SoloPlayÿc0: starting developermode");
- addEventListener("gamepacketsent", packetSent);
- addEventListener("gamepacket", packetReceived);
- Config.Silence = false;
-
- try {
- while (!done) {
- if (action) {
- if (!UnitInfo.cleared) {
- UnitInfo.remove();
- userAddon = false;
- }
-
- Loader.runScript(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();
- uInfo = Game.getSelectedUnit();
- UnitInfo.createInfo(uInfo);
- }
-
- if (test) {
- me.overhead("done");
- test = false;
- }
-
- delay(100);
- }
- } finally {
- removeEventListener("gamepacketsent", packetSent);
- removeEventListener("gamepacket", packetReceived);
- Config.Silence = true;
- }
-
- return true;
+ let [done, action, command, userAddon, test] = [false, false, false, false, false];
+ let [watchSent, watchRecv, blockSent, blockRecv] = [[], [], [], []];
+ 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":
+ myPrint("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");
+ break;
+ }
+
+ action = msgList[1].toLowerCase();
+
+ break;
+ case "done":
+ done = true;
+
+ break;
+ case "testing":
+ test = true;
+
+ break;
+ case "command":
+ if (msgList.length < 2) {
+ console.log("ÿc1Missing arguments");
+ break;
+ }
+
+ 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
+ * @param {number[]} pBytes
+ */
+ 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).map(pByte => pByte.toString(16));
+ 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;
+ };
+
+ const UnitInfo = new (require("../../modules/UnitInfo"));
+
+ try {
+ myPrint("ÿc8Kolbot-SoloPlayÿc0: starting developermode");
+ addEventListener("gamepacketsent", packetSent);
+ addEventListener("gamepacket", packetReceived);
+ Config.Silence = false;
+
+ while (!done) {
+ UnitInfo.check();
+
+ if (action) {
+ Loader.runScript(action);
+
+ me.overhead("Done with action");
+ action = false;
+ }
+
+ if (command) {
+ try {
+ eval(command);
+ } catch (e) {
+ console.error(e);
+ }
+
+ me.overhead("Done with action");
+ command = false;
+ }
+
+ if (userAddon) {
+ UnitInfo.createInfo(Game.getSelectedUnit());
+ }
+
+ if (test) {
+ console.debug("done");
+ me.overhead("done");
+ test = false;
+ }
+
+ delay(100);
+ }
+ } finally {
+ removeEventListener("gamepacketsent", packetSent);
+ removeEventListener("gamepacket", packetReceived);
+ Config.Silence = true;
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/diablo.js b/libs/SoloPlay/Scripts/diablo.js
index 92c6b9ab..f72f2186 100644
--- a/libs/SoloPlay/Scripts/diablo.js
+++ b/libs/SoloPlay/Scripts/diablo.js
@@ -8,176 +8,177 @@
// todo: clean this up, listen for lights game packet while opening/checking seals
function diablo () {
- // Start Diablo Quest
- this.diabloPrep = function () {
- let tick = getTickCount();
- let decoyDuration = (me.amazon ? Skill.getDuration(sdk.skills.Decoy) : 0);
-
- while (getTickCount() - tick < 17500) {
- me.getMobCount(20) > 1 && Attack.clear(20);
- if (getTickCount() - tick >= 8000) {
- switch (me.classid) {
- case sdk.player.class.Amazon:
- if (me.getSkill(sdk.skills.Decoy, sdk.skills.subindex.SoftPoints)) {
- let decoy = Game.getMonster(sdk.summons.Dopplezon);
-
- if (!decoy || (getTickCount() - tick >= decoyDuration)) {
- Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, 7793, 5293);
- }
- }
-
- break;
- 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);
- }
- } else {
- delay(500);
- }
-
- if (Game.getMonster(sdk.monsters.Diablo)) {
- return true;
- }
- }
-
- return false;
- };
-
- // START
- Town.doChores(false, { fullChores: true });
- myPrint("starting diablo");
-
- Pather.checkWP(sdk.areas.RiverofFlame, true) ? Pather.useWaypoint(sdk.areas.RiverofFlame) : Pather.getWP(sdk.areas.RiverofFlame);
- Precast.doPrecast(true);
-
- let attempts = 0;
-
- while ([7790, 5544].distance > 15 && attempts < 5) {
- myPrint("Moving to Chaos Sanctuary Entrance :: Attempt: " + attempts);
- Pather.moveTo(7790, 5544);
- attempts++;
- }
-
- if (me.coldRes < 75 || me.poisonRes < 75) {
- Town.doChores(null, {thawing: me.coldRes < 75, antidote: me.poisonRes < 75});
- Town.move("portalspot");
- Pather.usePortal(sdk.areas.ChaosSanctuary, me.name);
- Misc.poll(() => {
- if (me.inArea(sdk.areas.ChaosSanctuary)) {
- console.log("Returned to chaos");
- return true;
- }
- return false;
- }, 500, 100);
- }
-
- Common.Diablo.initLayout();
-
- const oldCP = Object.assign({}, Config.ClearPath);
- const oldBP = Config.BossPriority;
-
- try {
- !me.diablo && me.barbarian && (Config.BossPriority = true);
-
- switch (true) {
- case (Check.brokeAf(false)):
- break;
- case (me.diffCompleted && Pather.canTeleport()):
- let cLvl = me.charlvl;
- if ((me.normal && cLvl > 28) || (me.nightmare && cLvl > 65)) {
- // try running fast diablo - may neeed work
- Config.Diablo.Fast = true;
- Config.ClearPath.Range = 15;
- Config.ClearPath.Spectype = 0xF; // skip normal mobs
- console.debug("CP Settings: ", Config.ClearPath);
- }
- }
-
- Common.Diablo.vizierSeal();
- Common.Diablo.seisSeal();
- Common.Diablo.infectorSeal();
- } catch (e) {
- console.error(e);
- } finally {
- oldCP.Range !== Config.ClearPath.Range && (Object.assign(Config.ClearPath, oldCP));
- oldBP !== Config.BossPriority && (Config.BossPriority = oldBP);
- }
-
- try {
- if (!Check.currentBuild().caster || (Skill.getRange(Config.AttackSkill[1]) < 13)) {
- Messaging.sendToScript(SoloEvents.filePath, "addDiaEvent");
- }
-
- (me.sorceress || me.necromancer || me.assassin) ? Pather.moveNear(7792, 5292, 37) : Pather.moveTo(7788, 5292, 3, 30);
-
- this.diabloPrep();
- let theD = Game.getMonster(sdk.monsters.Diablo);
-
- if (!theD) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Diablo not found. Checking seal bosses.");
- try {
- Common.Diablo.vizierSeal();
- Common.Diablo.seisSeal();
- Common.Diablo.infectorSeal();
- } catch (e) {
- //
- }
-
- (me.sorceress || me.necromancer || me.assassin) ? Pather.moveNear(7792, 5292, 37) : Pather.moveTo(7788, 5292, 3, 30);
- this.diabloPrep();
- }
-
- !Attack.pwnDia() && Attack.killTarget(sdk.monsters.Diablo);
- Pickit.pickItems();
- } catch (e) {
- //
- } finally {
- Messaging.sendToScript(SoloEvents.filePath, "removeDiaEvent");
- }
-
- if (me.classic) return true;
-
- try {
- Pather.changeAct();
- } catch (err) {
- Town.npcInteract("tyrael");
- me.cancel();
- delay(500);
- Pather.useUnit(sdk.unittype.Object, 566, 109);
- }
-
- return true;
+ include("core/Common/Diablo.js");
+ // Start Diablo Quest
+ const diabloPrep = function () {
+ let tick = getTickCount();
+ let decoyDuration = (me.amazon ? Skill.getDuration(sdk.skills.Decoy) : 0);
+
+ while (getTickCount() - tick < 17500) {
+ me.getMobCount(20) > 1 && Attack.clear(20);
+ if (getTickCount() - tick >= 8000) {
+ switch (me.classid) {
+ case sdk.player.class.Amazon:
+ if (me.getSkill(sdk.skills.Decoy, sdk.skills.subindex.SoftPoints)) {
+ let decoy = Game.getMonster(sdk.summons.Dopplezon);
+
+ if (!decoy || (getTickCount() - tick >= decoyDuration)) {
+ Skill.cast(sdk.skills.Decoy, sdk.skills.hand.Right, 7793, 5293);
+ }
+ }
+
+ break;
+ 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);
+ }
+ } else {
+ delay(500);
+ }
+
+ if (Game.getMonster(sdk.monsters.Diablo)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ // START
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting diablo");
+
+ Pather.checkWP(sdk.areas.RiverofFlame, true) ? Pather.useWaypoint(sdk.areas.RiverofFlame) : Pather.getWP(sdk.areas.RiverofFlame);
+ Precast.doPrecast(true);
+
+ let attempts = 0;
+
+ while ([7790, 5544].distance > 15 && attempts < 5) {
+ myPrint("Moving to Chaos Sanctuary Entrance :: Attempt: " + attempts);
+ Pather.moveTo(7790, 5544);
+ attempts++;
+ }
+
+ if (me.coldRes < 75 || me.poisonRes < 75) {
+ Town.doChores(null, { thawing: me.coldRes < 75, antidote: me.poisonRes < 75 });
+ Town.move("portalspot");
+ Pather.usePortal(sdk.areas.ChaosSanctuary, me.name);
+ Misc.poll(() => {
+ if (me.inArea(sdk.areas.ChaosSanctuary)) {
+ console.log("Returned to chaos");
+ return true;
+ }
+ return false;
+ }, 500, 100);
+ }
+
+ Common.Diablo.initLayout();
+
+ const oldCP = Object.assign({}, Config.ClearPath);
+ const oldBP = Config.BossPriority;
+
+ try {
+ !me.diablo && me.barbarian && (Config.BossPriority = true);
+
+ switch (true) {
+ case (Check.brokeAf(false)):
+ break;
+ case (me.diffCompleted && Pather.canTeleport()):
+ let cLvl = me.charlvl;
+ if ((me.normal && cLvl > 28) || (me.nightmare && cLvl > 65)) {
+ // try running fast diablo - may neeed work
+ Config.Diablo.Fast = true;
+ Config.ClearPath.Range = 15;
+ Config.ClearPath.Spectype = 0xF; // skip normal mobs
+ console.debug("CP Settings: ", Config.ClearPath);
+ }
+ }
+
+ Common.Diablo.vizierSeal();
+ Common.Diablo.seisSeal();
+ Common.Diablo.infectorSeal();
+ } catch (e) {
+ console.error(e);
+ } finally {
+ oldCP.Range !== Config.ClearPath.Range && (Object.assign(Config.ClearPath, oldCP));
+ oldBP !== Config.BossPriority && (Config.BossPriority = oldBP);
+ }
+
+ try {
+ if (!Check.currentBuild().caster || (Skill.getRange(Config.AttackSkill[1]) < 13)) {
+ Messaging.sendToScript(SoloEvents.filePath, "addDiaEvent");
+ }
+
+ (me.sorceress || me.necromancer || me.assassin) ? Pather.moveNear(7792, 5292, 37) : Pather.moveTo(7788, 5292, 3, 30);
+
+ diabloPrep();
+ let theD = Game.getMonster(sdk.monsters.Diablo);
+
+ if (!theD) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Diablo not found. Checking seal bosses.");
+ try {
+ Common.Diablo.vizierSeal();
+ Common.Diablo.seisSeal();
+ Common.Diablo.infectorSeal();
+ } catch (e) {
+ //
+ }
+
+ (me.sorceress || me.necromancer || me.assassin) ? Pather.moveNear(7792, 5292, 37) : Pather.moveTo(7788, 5292, 3, 30);
+ diabloPrep();
+ }
+
+ !Attack.pwnDia() && Attack.killTarget(sdk.monsters.Diablo);
+ Pickit.pickItems();
+ } catch (e) {
+ //
+ } finally {
+ Messaging.sendToScript(SoloEvents.filePath, "removeDiaEvent");
+ }
+
+ if (me.classic) return true;
+
+ try {
+ Pather.changeAct();
+ } catch (err) {
+ Town.npcInteract("tyrael");
+ me.cancel();
+ delay(500);
+ Pather.useUnit(sdk.unittype.Object, 566, 109);
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/duriel.js b/libs/SoloPlay/Scripts/duriel.js
index 70466383..6f56ddaf 100644
--- a/libs/SoloPlay/Scripts/duriel.js
+++ b/libs/SoloPlay/Scripts/duriel.js
@@ -6,73 +6,73 @@
*/
function duriel () {
- // pre-tasks
- Quest.preReqs();
- Quest.cubeItems(sdk.quest.item.HoradricStaff, sdk.quest.item.ShaftoftheHoradricStaff, sdk.quest.item.ViperAmulet);
+ // pre-tasks
+ Quest.preReqs();
+ Quest.cubeItems(sdk.quest.item.HoradricStaff, sdk.quest.item.ShaftoftheHoradricStaff, sdk.quest.item.ViperAmulet);
- // Start
- Town.doChores(false, { fullChores: true });
- myPrint("starting duriel");
- Pather.checkWP(sdk.areas.CanyonofMagic, true) ? Pather.useWaypoint(sdk.areas.CanyonofMagic) : Pather.getWP(sdk.areas.CanyonofMagic);
- Precast.doPrecast(true);
- Pather.moveToExit(getRoom().correcttomb, true);
- Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.HoradricStaffHolder);
- Attack.securePosition(me.x, me.y, 30, 3000, true, me.hell);
- Quest.placeStaff();
+ // Start
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting duriel");
+ Pather.checkWP(sdk.areas.CanyonofMagic, true) ? Pather.useWaypoint(sdk.areas.CanyonofMagic) : Pather.getWP(sdk.areas.CanyonofMagic);
+ Precast.doPrecast(true);
+ Pather.moveToExit(getRoom().correcttomb, true);
+ Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.HoradricStaffHolder);
+ Attack.securePosition(me.x, me.y, 30, 3000, true, me.hell);
+ Quest.placeStaff();
- // quest-prep
- let preArea = me.area;
- Town.doChores(null, {thawing: me.coldRes < 75});
+ // quest-prep
+ let preArea = me.area;
+ Town.doChores(null, { thawing: me.coldRes < 75 });
- let oldMercWatch = Config.MercWatch;
- Config.MercWatch = false;
+ let oldMercWatch = Config.MercWatch;
+ Config.MercWatch = false;
- if (!Pather.usePortal(preArea, me.name)) {
- if (!Pather.journeyTo(preArea)) throw new Error("Failed to move back to duriels tomb");
- }
+ if (!Pather.usePortal(preArea, me.name)) {
+ if (!Pather.journeyTo(preArea)) throw new Error("Failed to move back to duriels tomb");
+ }
- // move to and kill dury
- let unit = Misc.poll(() => Game.getObject(sdk.objects.PortaltoDurielsLair));
+ // move to and kill dury
+ let unit = Misc.poll(() => Game.getObject(sdk.objects.PortaltoDurielsLair));
- if (me.sorceress && unit && Skill.useTK(unit)) {
- for (let i = 0; i < 3; i++) {
- !me.inArea(sdk.areas.DurielsLair) && Packet.telekinesis(unit);
- if (me.inArea(sdk.areas.DurielsLair)) {
- break;
- }
- }
+ if (me.sorceress && unit && Skill.useTK(unit)) {
+ for (let i = 0; i < 3; i++) {
+ !me.inArea(sdk.areas.DurielsLair) && Packet.telekinesis(unit);
+ if (me.inArea(sdk.areas.DurielsLair)) {
+ break;
+ }
+ }
- if (!me.inArea(sdk.areas.DurielsLair) && !Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair)) {
- Attack.clear(10);
- Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair);
- }
- } else {
- Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair);
- }
+ if (!me.inArea(sdk.areas.DurielsLair) && !Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair)) {
+ Attack.clear(10);
+ Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair);
+ }
+ } else {
+ Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair);
+ }
- me.sorceress && !me.normal ? Attack.pwnDury() : Attack.killTarget("Duriel");
- Pickit.pickItems();
+ me.sorceress && !me.normal ? Attack.pwnDury() : Attack.killTarget("Duriel");
+ Pickit.pickItems();
- !me.duriel && !Misc.checkQuest(sdk.quest.id.TheSevenTombs, 3) && Quest.tyraelTomb();
+ !me.duriel && !Misc.checkQuest(sdk.quest.id.TheSevenTombs, 3) && Quest.tyraelTomb();
- if (Misc.checkQuest(sdk.quest.id.TheSevenTombs, 3)) {
- for (let i = 0; i < 3; i++) {
- if (!me.duriel && !Misc.checkQuest(sdk.quest.id.TheSevenTombs, 4)) {
- Town.move("palace");
- Town.npcInteract("jerhyn");
- }
+ if (Misc.checkQuest(sdk.quest.id.TheSevenTombs, 3)) {
+ for (let i = 0; i < 3; i++) {
+ if (!me.duriel && !Misc.checkQuest(sdk.quest.id.TheSevenTombs, 4)) {
+ Town.move("palace");
+ Town.npcInteract("jerhyn");
+ }
- if (Misc.checkQuest(sdk.quest.id.TheSevenTombs, 4)) {
- Pather.moveToExit(sdk.areas.HaremLvl1, true);
- break;
- } else {
- delay(250 + me.ping);
- }
- }
- }
+ if (Misc.checkQuest(sdk.quest.id.TheSevenTombs, 4)) {
+ Pather.moveToExit(sdk.areas.HaremLvl1, true);
+ break;
+ } else {
+ delay(250 + me.ping);
+ }
+ }
+ }
- Pather.changeAct();
- Config.MercWatch = oldMercWatch;
+ Pather.changeAct();
+ Config.MercWatch = oldMercWatch;
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/eye.js b/libs/SoloPlay/Scripts/eye.js
index 5cb6c092..ea09b613 100644
--- a/libs/SoloPlay/Scripts/eye.js
+++ b/libs/SoloPlay/Scripts/eye.js
@@ -6,27 +6,29 @@
*/
function eye () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting eye");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting eye");
- Pather.checkWP(sdk.areas.SpiderForest, true) ? Pather.useWaypoint(sdk.areas.SpiderForest) : Pather.getWP(sdk.areas.SpiderForest);
- Precast.doPrecast(true);
+ Pather.checkWP(sdk.areas.SpiderForest, true)
+ ? Pather.useWaypoint(sdk.areas.SpiderForest)
+ : Pather.getWP(sdk.areas.SpiderForest);
+ Precast.doPrecast(true);
- if (!Pather.moveToExit([sdk.areas.SpiderForest, sdk.areas.SpiderCavern], true)) {
- if (!me.inArea(sdk.areas.SpiderCavern)) {
- if (!Pather.journeyTo(sdk.areas.SpiderCavern)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to get the eye");
- return false;
- }
- }
- }
+ if (!Pather.moveToExit([sdk.areas.SpiderForest, sdk.areas.SpiderCavern], true)) {
+ if (!me.inArea(sdk.areas.SpiderCavern)) {
+ if (!Pather.journeyTo(sdk.areas.SpiderCavern)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to get the eye");
+ return false;
+ }
+ }
+ }
- Town.doChores(null, {antidote: me.poisonRes < 75});
- Pather.usePortal(sdk.areas.SpiderCavern, me.name);
- Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsEyeChest);
- Attack.clear(0x7);
- Quest.collectItem(sdk.items.quest.KhalimsEye, sdk.quest.chest.KhalimsEyeChest);
- Quest.stashItem(sdk.items.quest.KhalimsEye);
+ Town.doChores(null, { antidote: me.poisonRes < 75 });
+ Pather.usePortal(sdk.areas.SpiderCavern, me.name);
+ Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsEyeChest);
+ Attack.clear(0x7);
+ Quest.collectItem(sdk.items.quest.KhalimsEye, sdk.quest.chest.KhalimsEyeChest);
+ Quest.stashItem(sdk.items.quest.KhalimsEye);
- return me.getItem(sdk.items.quest.KhalimsEye);
+ return me.getItem(sdk.items.quest.KhalimsEye);
}
diff --git a/libs/SoloPlay/Scripts/eyeback.js b/libs/SoloPlay/Scripts/eyeback.js
new file mode 100644
index 00000000..c25c4ac1
--- /dev/null
+++ b/libs/SoloPlay/Scripts/eyeback.js
@@ -0,0 +1,20 @@
+/**
+* @filename eyeback.js
+* @author kolton
+* @desc kill Eyeback the Unleashed
+*
+*/
+
+function eyeback () {
+ Town.doChores();
+ 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.killTarget(getLocaleString(sdk.locale.monsters.EyebacktheUnleashed));
+
+ return true;
+}
diff --git a/libs/SoloPlay/Scripts/fireeye.js b/libs/SoloPlay/Scripts/fireeye.js
new file mode 100644
index 00000000..8ceed54e
--- /dev/null
+++ b/libs/SoloPlay/Scripts/fireeye.js
@@ -0,0 +1,25 @@
+/**
+* @filename fireeye.js
+* @author theBGuy
+* @desc kill fireye in palace cellar lvl 3
+*
+*/
+
+function fireeye () {
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting fireeye");
+
+ if (!me.summoner && !Misc.checkQuest(sdk.quest.id.TheArcaneSanctuary, 3/* talked to Jerhyn */)) {
+ Town.npcInteract("jerhyn");
+ }
+
+ Pather.checkWP(sdk.areas.ArcaneSanctuary, true)
+ ? Pather.useWaypoint(sdk.areas.ArcaneSanctuary)
+ : Pather.getWP(sdk.areas.ArcaneSanctuary);
+ Precast.doPrecast(true);
+
+ if (!Pather.usePortal(null)) throw new Error("Failed to move to Fire Eye");
+ Attack.clear(15, 0, getLocaleString(sdk.locale.monsters.FireEye));
+
+ return true;
+}
diff --git a/libs/SoloPlay/Scripts/getkeys.js b/libs/SoloPlay/Scripts/getkeys.js
index 9dff255f..11144355 100644
--- a/libs/SoloPlay/Scripts/getkeys.js
+++ b/libs/SoloPlay/Scripts/getkeys.js
@@ -5,32 +5,32 @@
*
*/
-function getkeys() {
- Town.doChores();
+function getkeys () {
+ Town.doChores();
- if (!me.findItems(sdk.items.quest.KeyofTerror) || me.findItems(sdk.items.quest.KeyofTerror).length < 3) {
- try {
- Loader.runScript("countess");
- } catch (countessError) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Countess failed");
- }
- }
+ if (!me.findItems(sdk.items.quest.KeyofTerror) || me.findItems(sdk.items.quest.KeyofTerror).length < 3) {
+ try {
+ Loader.runScript("countess");
+ } catch (countessError) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Countess failed");
+ }
+ }
- if (!me.findItems(sdk.items.quest.KeyofHate) || me.findItems(sdk.items.quest.KeyofHate).length < 3) {
- try {
- Loader.runScript("summoner");
- } catch (summonerError) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Summoner failed");
- }
- }
+ if (!me.findItems(sdk.items.quest.KeyofHate) || me.findItems(sdk.items.quest.KeyofHate).length < 3) {
+ try {
+ Loader.runScript("summoner");
+ } catch (summonerError) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Summoner failed");
+ }
+ }
- if (!me.findItems(sdk.items.quest.KeyofDestruction) || me.findItems(sdk.items.quest.KeyofDestruction).length < 3) {
- try {
- Loader.runScript("nith");
- } catch (nihlathakError) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Nihlathak failed");
- }
- }
+ if (!me.findItems(sdk.items.quest.KeyofDestruction) || me.findItems(sdk.items.quest.KeyofDestruction).length < 3) {
+ try {
+ Loader.runScript("nith");
+ } catch (nihlathakError) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Nihlathak failed");
+ }
+ }
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/heart.js b/libs/SoloPlay/Scripts/heart.js
index d92fd503..ca54b913 100644
--- a/libs/SoloPlay/Scripts/heart.js
+++ b/libs/SoloPlay/Scripts/heart.js
@@ -1,28 +1,30 @@
/**
* @filename heart.js
-* @author isid0re, theBGuy
+* @author theBGuy
* @desc get the heart for khalims will
*
*/
function heart () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting heart");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting heart");
- Pather.checkWP(sdk.areas.KurastBazaar, true) ? Pather.useWaypoint(sdk.areas.KurastBazaar) : Pather.getWP(sdk.areas.KurastBazaar);
- Precast.doPrecast(true);
+ Pather.checkWP(sdk.areas.KurastBazaar, true)
+ ? Pather.useWaypoint(sdk.areas.KurastBazaar)
+ : Pather.getWP(sdk.areas.KurastBazaar);
+ Precast.doPrecast(true);
- if (!Pather.moveToExit([sdk.areas.KurastBazaar, sdk.areas.A3SewersLvl1, sdk.areas.A3SewersLvl2], true)
- || !Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.KhalimsHeartChest)) {
- if (!me.getItem(sdk.items.quest.KhalimsHeart)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to get the heart");
- return false;
- }
- }
+ if (!Pather.journeyTo(sdk.areas.A3SewersLvl2)
+ || !Pather.moveToPresetObject(me.area, sdk.quest.chest.KhalimsHeartChest)) {
+ if (!me.getItem(sdk.items.quest.KhalimsHeart)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to get the heart");
+ return false;
+ }
+ }
- Attack.clear(0x7); // clear level
- Quest.collectItem(sdk.items.quest.KhalimsHeart, sdk.quest.chest.KhalimsHeartChest);
- Quest.stashItem(sdk.items.quest.KhalimsHeart);
+ Attack.clear(0x7); // clear level
+ Quest.collectItem(sdk.items.quest.KhalimsHeart, sdk.quest.chest.KhalimsHeartChest);
+ Quest.stashItem(sdk.items.quest.KhalimsHeart);
- return me.getItem(sdk.items.quest.KhalimsHeart);
+ return me.getItem(sdk.items.quest.KhalimsHeart);
}
diff --git a/libs/SoloPlay/Scripts/hellforge.js b/libs/SoloPlay/Scripts/hellforge.js
index 7030be58..a5b12085 100644
--- a/libs/SoloPlay/Scripts/hellforge.js
+++ b/libs/SoloPlay/Scripts/hellforge.js
@@ -6,72 +6,82 @@
*/
function hellforge () {
- if (Misc.checkQuest(sdk.quest.id.HellsForge, sdk.quest.states.ReqComplete)) {
- Town.goToTown(4) && Town.npcInteract("cain");
- if (Misc.poll(() => Misc.checkQuest(sdk.quest.id.HellsForge, sdk.quest.states.Completed), 2000, 500)) return true;
- }
- myPrint("starting hellforge");
- Town.doChores(false, {thawing: me.coldRes < 75, antidote: me.poisonRes < 75, fullChores: true});
-
- Pather.checkWP(sdk.areas.RiverofFlame, true) ? Pather.useWaypoint(sdk.areas.RiverofFlame) : Pather.getWP(sdk.areas.RiverofFlame);
- Precast.doPrecast(true);
+ if (Misc.checkQuest(sdk.quest.id.HellsForge, sdk.quest.states.ReqComplete)) {
+ Town.goToTown(4) && Town.npcInteract("cain");
+ if (Misc.poll(() => Misc.checkQuest(sdk.quest.id.HellsForge, sdk.quest.states.Completed), 2000, 500)) return true;
+ }
+ myPrint("starting hellforge");
+ Town.doChores(false, { thawing: me.coldRes < 75, antidote: me.poisonRes < 75, fullChores: true });
+
+ Pather.checkWP(sdk.areas.RiverofFlame, true) ? Pather.useWaypoint(sdk.areas.RiverofFlame) : Pather.getWP(sdk.areas.RiverofFlame);
+ Precast.doPrecast(true);
- if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge)) {
- console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Hephasto");
- }
+ /**
+ * @todo
+ * - Calculate safe spots to cast from so we can kill heph from a distance
+ * - Possibly try luring the rest of the monsters away from the forge?
+ * - Generate path and use callback to stop after we detect heph in range instead of moving all the way to the forge
+ */
- try {
- Attack.clear(20, 0, getLocaleString(sdk.locale.monsters.HephastoTheArmorer));
- } catch (err) {
- console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Hephasto");
- }
+ if (!Pather.moveToPresetObject(me.area, sdk.quest.chest.HellForge, { callback: () => {
+ let heph = Game.getMonster(getLocaleString(sdk.locale.monsters.HephastoTheArmorer));
+ return (heph && heph.distance < 30);
+ } })) {
+ console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Hephasto");
+ }
- Pickit.pickItems();
- let forge = Game.getObject(sdk.quest.chest.HellForge);
- !!forge && Attack.clearPos(forge.x, forge.y, 25) && Attack.securePosition(forge.x, forge.y, 25, 3000);
+ try {
+ Attack.clear(20, 0, getLocaleString(sdk.locale.monsters.HephastoTheArmorer));
+ } catch (err) {
+ console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Hephasto");
+ }
- if (!me.getItem(sdk.items.quest.HellForgeHammer)) {
- // we don't have the hammer, is Hephasto dead?
- let heph = getUnits(sdk.unittype.Monster).filter((unit) => unit.classid === sdk.monsters.Hephasto).first();
- !!heph && heph.attackable && Attack.kill(heph);
- // hammer on ground?
- let ham = getUnits(sdk.unittype.Item).filter((unit) => unit.classid === sdk.items.quest.HellForgeHammer).first();
- !!ham && ham.onGroundOrDropping && Pather.moveToUnit(ham) && Pickit.pickItem(ham);
- // do we have the hammer now?
- if (!me.getItem(sdk.items.quest.HellForgeHammer)) {
- console.warn("Failed to collect Hellforge hammer");
-
- return true;
- }
- }
+ Pickit.pickItems();
+ let forge = Game.getObject(sdk.quest.chest.HellForge);
+ !!forge && Attack.clearPos(forge.x, forge.y, 25) && Attack.securePosition(forge.x, forge.y, 25, 3000);
- Town.doChores();
- Town.npcInteract("cain");
+ if (!me.getItem(sdk.items.quest.HellForgeHammer)) {
+ // we don't have the hammer, is Hephasto dead?
+ let heph = getUnits(sdk.unittype.Monster).filter((unit) => unit.classid === sdk.monsters.Hephasto).first();
+ !!heph && heph.attackable && Attack.kill(heph);
+ // hammer on ground?
+ let ham = getUnits(sdk.unittype.Item).filter((unit) => unit.classid === sdk.items.quest.HellForgeHammer).first();
+ !!ham && ham.onGroundOrDropping && Pather.moveToUnit(ham) && Pickit.pickItem(ham);
+ // do we have the hammer now?
+ if (!me.getItem(sdk.items.quest.HellForgeHammer)) {
+ console.warn("Failed to collect Hellforge hammer");
+
+ return true;
+ }
+ }
- let oldItem = me.getItemsEx().filter(function (item) {
- return item.isEquipped && item.bodylocation === 4 && !item.isOnSwap;
- }).first();
+ Town.doChores();
+ Town.npcInteract("cain");
- if (!Quest.equipItem(sdk.items.quest.HellForgeHammer, 4)) {
- console.warn("Failed to equip HellForge Hammer");
+ let oldItem = me.getItemsEx().filter(function (item) {
+ return item.isEquipped && item.bodylocation === 4 && !item.isOnSwap;
+ }).first();
- return true;
- }
-
- Pather.usePortal(sdk.areas.RiverofFlame, me.name);
+ if (!Quest.equipItem(sdk.items.quest.HellForgeHammer, 4)) {
+ console.warn("Failed to equip HellForge Hammer");
- if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge)) {
- console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to move to forge");
+ return true;
+ }
+
+ Pather.usePortal(sdk.areas.RiverofFlame, me.name);
- return true;
- }
+ if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge)) {
+ console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to move to forge");
- Misc.openChest(sdk.quest.chest.HellForge);
- Quest.smashSomething(sdk.quest.chest.HellForge) && delay(4500 + me.ping);
- !!oldItem && oldItem.isInInventory && oldItem.equip(4);
- Pickit.pickItems();
- Item.autoEquip();
- Town.npcInteract("cain");
+ return true;
+ }
- return true;
+ Misc.openChest(sdk.quest.chest.HellForge);
+ Quest.smashSomething(sdk.quest.chest.HellForge) && delay(4500 + me.ping);
+ !!oldItem && oldItem.isInInventory && oldItem.equip(4);
+ Pickit.pickItems();
+ Item.autoEquip();
+ Town.npcInteract("cain");
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/hephasto.js b/libs/SoloPlay/Scripts/hephasto.js
index 52599be6..2aac4bbd 100644
--- a/libs/SoloPlay/Scripts/hephasto.js
+++ b/libs/SoloPlay/Scripts/hephasto.js
@@ -5,24 +5,24 @@
*
*/
-function hephasto() {
- myPrint("starting hephasto");
- Town.doChores(null, {thawing: me.coldRes < 75, antidote: me.poisonRes < 75});
+function hephasto () {
+ myPrint("starting hephasto");
+ Town.doChores(null, { thawing: me.coldRes < 75, antidote: me.poisonRes < 75 });
- Pather.checkWP(sdk.areas.RiverofFlame, true) ? Pather.useWaypoint(sdk.areas.RiverofFlame) : Pather.getWP(sdk.areas.RiverofFlame);
- Precast.doPrecast(true);
+ Pather.checkWP(sdk.areas.RiverofFlame, true) ? Pather.useWaypoint(sdk.areas.RiverofFlame) : Pather.getWP(sdk.areas.RiverofFlame);
+ Precast.doPrecast(true);
- if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge)) {
- console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Hephasto");
- }
+ if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge)) {
+ console.warn("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Hephasto");
+ }
- try {
- Attack.killTarget(getLocaleString(sdk.locale.monsters.HephastoTheArmorer));
- } catch (err) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Hephasto");
- }
+ try {
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.HephastoTheArmorer));
+ } catch (err) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Hephasto");
+ }
- Pickit.pickItems();
+ Pickit.pickItems();
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/izual.js b/libs/SoloPlay/Scripts/izual.js
index 0ec09826..5f074b8f 100644
--- a/libs/SoloPlay/Scripts/izual.js
+++ b/libs/SoloPlay/Scripts/izual.js
@@ -6,20 +6,20 @@
*/
function izual () {
- myPrint("starting izual");
+ myPrint("starting izual");
- Town.doChores(false, {thawing: true, antidote: true, stamina: true, fullChores: true});
+ Town.doChores(false, { thawing: true, antidote: true, stamina: true, fullChores: true });
- Pather.checkWP(sdk.areas.CityoftheDamned, true) ? Pather.useWaypoint(sdk.areas.CityoftheDamned) : Pather.getWP(sdk.areas.CityoftheDamned);
- Precast.doPrecast(true);
- Pather.moveToPreset(sdk.areas.PlainsofDespair, sdk.unittype.Monster, sdk.monsters.Izual);
- Attack.killTarget("Izual");
+ Pather.checkWP(sdk.areas.CityoftheDamned, true) ? Pather.useWaypoint(sdk.areas.CityoftheDamned) : Pather.getWP(sdk.areas.CityoftheDamned);
+ Precast.doPrecast(true);
+ Pather.moveToPreset(sdk.areas.PlainsofDespair, sdk.unittype.Monster, sdk.monsters.Izual);
+ Attack.killTarget("Izual");
- if (!Misc.checkQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.Completed)) {
- Town.goToTown();
- Town.npcInteract("tyrael");
- me.getStat(sdk.stats.NewSkills) > 0 && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
- }
+ if (!Misc.checkQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.Completed)) {
+ Town.goToTown();
+ Town.npcInteract("tyrael");
+ me.getStat(sdk.stats.NewSkills) > 0 && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
+ }
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/jail.js b/libs/SoloPlay/Scripts/jail.js
index 0e2063ad..7c28f7c7 100644
--- a/libs/SoloPlay/Scripts/jail.js
+++ b/libs/SoloPlay/Scripts/jail.js
@@ -6,33 +6,33 @@
*/
function jail () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting jail");
- const levels = [sdk.areas.JailLvl1, sdk.areas.JailLvl2, sdk.areas.JailLvl3];
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting jail");
+ const levels = [sdk.areas.JailLvl1, sdk.areas.JailLvl2, sdk.areas.JailLvl3];
- Pather.checkWP(sdk.areas.JailLvl1, true) ? Pather.useWaypoint(sdk.areas.JailLvl1) : Pather.getWP(sdk.areas.JailLvl1);
+ Pather.checkWP(sdk.areas.JailLvl1, true) ? Pather.useWaypoint(sdk.areas.JailLvl1) : Pather.getWP(sdk.areas.JailLvl1);
- for (let i = 1; i < levels.length; i++) {
- myPrint("clearing jail level " + i);
+ for (let i = 1; i < levels.length; i++) {
+ myPrint("clearing jail level " + i);
- Precast.doPrecast(true);
- Attack.clearLevelEx({quitWhen: function () {
- if (!me.hell) return false; // don't quit
- let dangerMob = Game.getMonster();
+ Precast.doPrecast(true);
+ Attack.clearLevelEx({ quitWhen: function () {
+ if (!me.hell) return false; // don't quit
+ let dangerMob = Game.getMonster();
- if (dangerMob) {
- do {
- if (dangerMob.attackable && [sdk.monsters.Tainted, sdk.monsters.Tainted2].includes(dangerMob.classid)) {
- myPrint("Tainted mob found. Moving to next level");
- return true;
- }
- } while (dangerMob.getNext());
- }
+ if (dangerMob) {
+ do {
+ if (dangerMob.attackable && [sdk.monsters.Tainted, sdk.monsters.Tainted2].includes(dangerMob.classid)) {
+ myPrint("Tainted mob found. Moving to next level");
+ return true;
+ }
+ } while (dangerMob.getNext());
+ }
- return false;
- }});
- Pather.clearToExit(me.area, levels[i], true);
- }
+ return false;
+ } });
+ Pather.clearToExit(me.area, levels[i], true);
+ }
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/lamessen.js b/libs/SoloPlay/Scripts/lamessen.js
index 0b8a2d68..cd618f14 100644
--- a/libs/SoloPlay/Scripts/lamessen.js
+++ b/libs/SoloPlay/Scripts/lamessen.js
@@ -1,23 +1,25 @@
/**
* @filename lamessen.js
-* @author isid0re, theBGuy
-* @desc get the lam essen's tome
+* @author theBGuy
+* @desc Get lamessen's tome
*
*/
function lamessen () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting lamessen");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting lamessen");
- Pather.checkWP(sdk.areas.KurastBazaar, true) ? Pather.useWaypoint(sdk.areas.KurastBazaar) : Pather.getWP(sdk.areas.KurastBazaar);
- Precast.doPrecast(true);
+ Pather.checkWP(sdk.areas.KurastBazaar, true) ? Pather.useWaypoint(sdk.areas.KurastBazaar) : Pather.getWP(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 to LamEssen Tome");
- }
+ if (!Pather.moveToExit(sdk.areas.RuinedTemple, true) || !Pather.moveToPresetObject(me.area, sdk.quest.chest.LamEsensTomeHolder)) {
+ throw new Error("Failed to move to LamEssen Tome");
+ }
- Quest.collectItem(sdk.items.quest.LamEsensTome, sdk.quest.chest.LamEsensTomeHolder);
- Quest.unfinishedQuests();
+ if (!Misc.checkQuest(sdk.quest.id.LamEsensTome, sdk.quest.states.Completed)) {
+ Quest.collectItem(sdk.items.quest.LamEsensTome, sdk.quest.chest.LamEsensTomeHolder);
+ }
+ Quest.unfinishedQuests();
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/lowerkurast.js b/libs/SoloPlay/Scripts/lowerkurast.js
index 1d81f224..e01386a3 100644
--- a/libs/SoloPlay/Scripts/lowerkurast.js
+++ b/libs/SoloPlay/Scripts/lowerkurast.js
@@ -6,12 +6,12 @@
*/
function lowerkurast () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting lower kurast");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting lower kurast");
- Pather.checkWP(sdk.areas.LowerKurast, true) ? Pather.useWaypoint(sdk.areas.LowerKurast) : Pather.getWP(sdk.areas.LowerKurast);
- Precast.doPrecast(true);
- Misc.openChestsInArea(sdk.areas.LowerKurast);
+ Pather.checkWP(sdk.areas.LowerKurast, true) ? Pather.useWaypoint(sdk.areas.LowerKurast) : Pather.getWP(sdk.areas.LowerKurast);
+ Precast.doPrecast(true);
+ Misc.openChestsInArea(sdk.areas.LowerKurast);
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/maggotlair.js b/libs/SoloPlay/Scripts/maggotlair.js
index 1a286cd1..402954ae 100644
--- a/libs/SoloPlay/Scripts/maggotlair.js
+++ b/libs/SoloPlay/Scripts/maggotlair.js
@@ -6,53 +6,57 @@
*/
function maggotlair () {
- this.clearBeetles = 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.isBeetle && monster.distance <= 30 && monster.attackable) {
- monList.push(copyUnit(monster));
- }
- } while (monster.getNext());
- }
-
- if (!Attack.clearList(monList)) return false;
- }
- }
-
- return true;
- };
-
- // START
- Town.doChores(false, { fullChores: true });
- myPrint("starting maggot lair beetle bursting");
-
- Pather.checkWP(sdk.areas.FarOasis, true) ? Pather.useWaypoint(sdk.areas.FarOasis) : Pather.getWP(sdk.areas.FarOasis);
- Precast.doPrecast(true);
- [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3].forEach((mLair, i) => {
- Pather.moveToExit(mLair, true) && this.clearBeetles();
- console.log("Cleared mLair " + (i + 1));
- });
-
- return true;
+ const clearBeetles = 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.isBeetle || monster.isSpecial)
+ && monster.distance <= 30
+ && monster.attackable) {
+ monList.push(copyUnit(monster));
+ }
+ } while (monster.getNext());
+ }
+
+ if (!Attack.clearList(monList)) return false;
+ }
+ }
+
+ return true;
+ };
+
+ // START
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting maggot lair beetle bursting");
+
+ Pather.checkWP(sdk.areas.FarOasis, true)
+ ? Pather.useWaypoint(sdk.areas.FarOasis)
+ : Pather.getWP(sdk.areas.FarOasis);
+ Precast.doPrecast(true);
+ [sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3].forEach((mLair, i) => {
+ Pather.moveToExit(mLair, true) && clearBeetles();
+ console.log("Cleared mLair " + (i + 1));
+ });
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/mausoleum.js b/libs/SoloPlay/Scripts/mausoleum.js
index 65513f23..4ef5c8cd 100644
--- a/libs/SoloPlay/Scripts/mausoleum.js
+++ b/libs/SoloPlay/Scripts/mausoleum.js
@@ -6,32 +6,32 @@
*/
function mausoleum () {
- Town.doChores();
- myPrint("ÿc8Kolbot-SoloPlayÿc0: starting mausoleum");
+ Town.doChores();
+ myPrint("ÿc8Kolbot-SoloPlayÿc0: starting mausoleum");
- me.gold > 1000 && Town.buyPots(12, "stamina", true);
+ me.gold > 1000 && Town.buyPots(12, "stamina", true);
- if (!Pather.checkWP(sdk.areas.ColdPlains)) {
- Pather.moveToExit(sdk.areas.BloodMoor, true);
+ if (!Pather.checkWP(sdk.areas.ColdPlains)) {
+ Pather.moveToExit(sdk.areas.BloodMoor, true);
- try {
- Pather.moveToPreset(sdk.areas.BloodMoor, sdk.unittype.Object, sdk.objects.SuperChest) && Misc.openChests(5);
- } catch (e) {
- console.warn(e.message ? e.message : e);
- }
- Pather.getWP(sdk.areas.ColdPlains);
+ try {
+ Pather.moveToPreset(sdk.areas.BloodMoor, sdk.unittype.Object, sdk.objects.SuperChest) && Misc.openChests(5);
+ } catch (e) {
+ console.warn(e.message ? e.message : e);
+ }
+ Pather.getWP(sdk.areas.ColdPlains);
- // check if we need to do chores
- Storage.Inventory.UsedSpacePercent() > 50 && Pather.useWaypoint(sdk.areas.RogueEncampment) && Town.doChores();
- }
+ // check if we need to do chores
+ Storage.Inventory.UsedSpacePercent() > 50 && Pather.useWaypoint(sdk.areas.RogueEncampment) && Town.doChores();
+ }
- Pather.useWaypoint(sdk.areas.ColdPlains);
- Precast.doPrecast(true);
- Pather.moveToExit([sdk.areas.BurialGrounds, sdk.areas.Mausoleum], true);
- // need to figure out better clearLevel method, for now just clear to superchest
+ Pather.useWaypoint(sdk.areas.ColdPlains);
+ Precast.doPrecast(true);
+ Pather.moveToExit([sdk.areas.BurialGrounds, sdk.areas.Mausoleum], true);
+ // need to figure out better clearLevel method, for now just clear to superchest
- me.inArea(sdk.areas.Mausoleum) && Pather.moveToPreset(sdk.areas.Mausoleum, sdk.unittype.Object, sdk.objects.SmallSparklyChest);
- Misc.openChests(5);
-
- return true;
+ me.inArea(sdk.areas.Mausoleum) && Pather.moveToPreset(sdk.areas.Mausoleum, sdk.unittype.Object, sdk.objects.SmallSparklyChest);
+ Misc.openChests(5);
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/mephisto.js b/libs/SoloPlay/Scripts/mephisto.js
index c895da3c..f763baa9 100644
--- a/libs/SoloPlay/Scripts/mephisto.js
+++ b/libs/SoloPlay/Scripts/mephisto.js
@@ -6,75 +6,75 @@
*/
function mephisto () {
- this.killCouncil = function () {
- let coords = [17600, 8125, 17600, 8015, 17643, 8068];
+ const killCouncil = function () {
+ let coords = [17600, 8125, 17600, 8015, 17643, 8068];
- for (let i = 0; i < coords.length; i += 2) {
- [coords[i], coords[i + 1]].distance > 60 && Pather.moveNear(coords[i], coords[i + 1], 60);
- if ([coords[i], coords[i + 1]].mobCount({ range: 30 }) === 0) continue;
- Pather.moveTo(coords[i], coords[i + 1]);
- Attack.clearList(Attack.getMob([sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3], 0, 40));
- }
+ for (let i = 0; i < coords.length; i += 2) {
+ [coords[i], coords[i + 1]].distance > 60 && Pather.moveNear(coords[i], coords[i + 1], 60);
+ if ([coords[i], coords[i + 1]].mobCount({ range: 30 }) === 0) continue;
+ Pather.moveTo(coords[i], coords[i + 1]);
+ Attack.clearList(Attack.getMob([sdk.monsters.Council1, sdk.monsters.Council2, sdk.monsters.Council3], 0, 40));
+ }
- return true;
- };
+ return true;
+ };
- Town.doChores(false, { fullChores: true });
- myPrint("starting mephisto");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting mephisto");
- Pather.checkWP(sdk.areas.DuranceofHateLvl2, true) ? Pather.useWaypoint(sdk.areas.DuranceofHateLvl2) : Pather.getWP(sdk.areas.DuranceofHateLvl2);
- Precast.doPrecast(true);
- const oldCPRange = Config.ClearPath.Range;
- const canTele = Pather.canTeleport();
- try {
- canTele && (Config.ClearPath.Range = 0);
- canTele
- ? Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true, false)
- : Pather.clearToExit(sdk.areas.DuranceofHateLvl2, sdk.areas.DuranceofHateLvl3, true);
- } finally {
- oldCPRange !== Config.ClearPath.Range && (Config.ClearPath.Range = oldCPRange);
- }
-
- if (!me.inArea(sdk.areas.DuranceofHateLvl3)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to mephisto");
- return false;
- }
+ Pather.checkWP(sdk.areas.DuranceofHateLvl2, true) ? Pather.useWaypoint(sdk.areas.DuranceofHateLvl2) : Pather.getWP(sdk.areas.DuranceofHateLvl2);
+ Precast.doPrecast(true);
+ const oldCPRange = Config.ClearPath.Range;
+ const canTele = Pather.canTeleport();
+ try {
+ canTele && (Config.ClearPath.Range = 0);
+ canTele
+ ? Pather.moveToExit(sdk.areas.DuranceofHateLvl3, true, false)
+ : Pather.clearToExit(sdk.areas.DuranceofHateLvl2, sdk.areas.DuranceofHateLvl3, true);
+ } finally {
+ oldCPRange !== Config.ClearPath.Range && (Config.ClearPath.Range = oldCPRange);
+ }
+
+ if (!me.inArea(sdk.areas.DuranceofHateLvl3)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to mephisto");
+ return false;
+ }
- // Town stuff
- if (me.coldRes < 75 || me.poisonRes < 75) {
- Town.doChores(null, {thawing: me.coldRes < 75, antidote: me.poisonRes < 75});
- // Re-enter portal
- Pather.usePortal(sdk.areas.DuranceofHateLvl3, me.name);
- Precast.doPrecast(true);
- }
+ // Town stuff
+ if (me.coldRes < 75 || me.poisonRes < 75) {
+ Town.doChores(null, { thawing: me.coldRes < 75, antidote: me.poisonRes < 75 });
+ // Re-enter portal
+ Pather.usePortal(sdk.areas.DuranceofHateLvl3, me.name);
+ Precast.doPrecast(true);
+ }
- const oldPickRange = Config.PickRange;
- const oldUseMerc = Config.MercWatch;
+ const oldPickRange = Config.PickRange;
+ const oldUseMerc = Config.MercWatch;
- if (me.mephisto) {
- // activate bridge
- Pather.moveTo(17587, 8069);
- delay(400);
- }
+ if (me.mephisto) {
+ // activate bridge
+ Pather.moveTo(17587, 8069);
+ delay(400);
+ }
- Pather.moveTo(17563, 8072);
- Attack.killTarget("Mephisto");
+ Pather.moveTo(17563, 8072);
+ Attack.killTarget("Mephisto");
- // Reset to normal value
- Config.MercWatch !== oldUseMerc && (Config.MercWatch = oldUseMerc);
- Config.PickRange !== oldPickRange && (Config.PickRange = oldPickRange);
-
- Pickit.pickItems();
- me.mephisto && !me.hell && this.killCouncil();
+ // Reset to normal value
+ Config.MercWatch !== oldUseMerc && (Config.MercWatch = oldUseMerc);
+ Config.PickRange !== oldPickRange && (Config.PickRange = oldPickRange);
+
+ Pickit.pickItems();
+ me.mephisto && !me.hell && killCouncil();
- Pather.moveTo(17581, 8070);
- delay(250 + me.ping * 2);
- Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct4, sdk.areas.PandemoniumFortress);
- Misc.poll(() => me.inArea(sdk.areas.PandemoniumFortress), 1000, 30);
+ Pather.moveTo(17581, 8070);
+ delay(250 + me.ping * 2);
+ Pather.useUnit(sdk.unittype.Object, sdk.objects.RedPortalToAct4, sdk.areas.PandemoniumFortress);
+ Misc.poll(() => me.inArea(sdk.areas.PandemoniumFortress), 1000, 30);
- while (!me.gameReady) {
- delay(40);
- }
+ while (!me.gameReady) {
+ delay(40);
+ }
- return me.inArea(sdk.areas.PandemoniumFortress);
+ return me.inArea(sdk.areas.PandemoniumFortress);
}
diff --git a/libs/SoloPlay/Scripts/nith.js b/libs/SoloPlay/Scripts/nith.js
index 33592870..5948cb8b 100644
--- a/libs/SoloPlay/Scripts/nith.js
+++ b/libs/SoloPlay/Scripts/nith.js
@@ -1,34 +1,41 @@
/**
-* @filename nith.js
-* @author theBGuy
-* @credit kolton
-* @desc kill Nihlathak for Destruction key
+* @filename nith.js
+* @author theBGuy
+* @credit kolton
+* @desc kill Nihlathak for Destruction key
+*
*/
-function nith() {
- Town.doChores();
- myPrint("starting nith");
-
- Pather.checkWP(sdk.areas.HallsofPain, true) ? Pather.useWaypoint(sdk.areas.HallsofPain) : Pather.getWP(sdk.areas.HallsofPain);
- Precast.doPrecast(false);
-
- if (!Pather.moveToExit(sdk.areas.HallsofVaught, true)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to go to Nihlathak");
-
- return true;
- }
-
- Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.objects.NihlathaksPlatform);
-
- // Stop script in hardcore mode if vipers are found
- if (me.hardcore && Game.getMonster(sdk.monsters.TombViper2)) {
- console.log("Tomb Vipers found.");
-
- return true;
- }
-
- Attack.killTarget(sdk.monsters.Nihlathak);
- Pickit.pickItems();
-
- return true;
+function nith () {
+ me.inTown && Town.doChores();
+ myPrint("starting nith");
+
+ if (Pather.checkWP(sdk.areas.HallsofPain, true)) {
+ 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)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to go to Nihlathak");
+
+ return true;
+ }
+
+ // Stop script in hardcore mode if vipers are found
+ // faster detection of TombVipers
+ Pather.moveToPresetObject(me.area, sdk.objects.NihlathaksPlatform, { callback: () => {
+ if (me.hardcore && Game.getMonster(sdk.monsters.TombViper2)) {
+ console.log("Tomb Vipers found.");
+ throw new ScriptError("Tomb Vipers found.");
+ }
+ } });
+
+ Attack.killTarget(sdk.monsters.Nihlathak);
+ Pickit.pickItems();
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/orgtorch.js b/libs/SoloPlay/Scripts/orgtorch.js
index 2ae18228..9a98fc46 100644
--- a/libs/SoloPlay/Scripts/orgtorch.js
+++ b/libs/SoloPlay/Scripts/orgtorch.js
@@ -5,362 +5,362 @@
*
*/
-function orgtorch() {
- this.doneAreas = [];
-
- // Identify & mule
- this.checkTorch = function () {
- if (me.inArea(sdk.areas.UberTristram)) {
- Pather.moveTo(25105, 5140);
- Pather.usePortal(sdk.areas.Harrogath);
- }
-
- Town.doChores();
-
- let torch = me.findItem(604, 0, null, 7);
-
- if (!torch || torch === undefined) {
- return false;
- }
-
- if (!torch.identified) {
- Town.identify();
- }
-
- for (let i = 0; i < 7; i++) {
- if (torch.getStat(sdk.stats.AddClassSkills, i)) {
- SoloEvents.sendToList({profile: me.profile, ladder: me.ladder, torchType: i});
-
- return true;
- }
- }
-
- return false;
- };
-
- // Check whether the killer is alone in the game
- this.aloneInGame = function () {
- let party = getParty();
-
- if (party) {
- do {
- if (party.name !== me.name) {
- return false;
- }
- } while (party.getNext());
- }
-
- return true;
- };
-
- // Try to lure a monster - wait until it's close enough
- this.lure = function (bossId) {
- let tick,
- unit = Game.getMonster(bossId);
-
- if (unit) {
- 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(sdk.items.quest.DiablosHorn),
- brains = me.findItems(sdk.items.quest.MephistosBrain),
- eyes = me.findItems(sdk.items.quest.BaalsEye);
-
- if (!horns || !brains || !eyes) {
- return false;
- }
-
- // We just need one set to make a torch
- return horns.length && brains.length && eyes.length;
- };
-
- // Get fade in River of Flames
- this.getFade = function () {
- if (!me.getState(sdk.states.Fade)
- && (me.checkItem({name: sdk.locale.items.Treachery, equipped: true}).have
- || me.checkItem({name: sdk.locale.items.LastWish, equipped: true}).have
- || me.checkItem({name: sdk.locale.items.SpiritWard, equipped: true}).have)) {
- if (!me.getState(sdk.states.Fade)) {
- myPrint(sdk.colors.Orange + "OrgTorch :: " + sdk.colors.White + "Getting Fade");
- Pather.useWaypoint(sdk.states.RiverofFlame);
- Precast.doPrecast(true);
- Pather.moveTo(7811, 5872);
-
- me.paladin && me.checkSkill(sdk.skills.Salvation, sdk.skills.subindex.SoftPoints) && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right);
-
- while (!me.getState(sdk.states.Fade)) {
- delay(100);
- }
-
- myPrint(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 portal,
- item1 = mode === 0
- ? me.findItem(sdk.items.quest.KeyofTerror, sdk.items.mode.inStorage)
- : me.findItem(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage),
- item2 = mode === 0
- ? me.findItem(sdk.items.quest.KeyofHate, sdk.items.mode.inStorage)
- : me.findItem(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage),
- item3 = mode === 0
- ? me.findItem(sdk.items.quest.KeyofDestruction, sdk.items.mode.inStorage)
- : me.findItem(sdk.items.quest.MephistosBrain, 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);
-
- portal = Game.getObject("portal");
-
- if (portal) {
- do {
- switch (mode) {
- case 0:
- if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain].indexOf(portal.objtype) > -1 && this.doneAreas.indexOf(portal.objtype) === -1) {
- this.doneAreas.push(portal.objtype);
-
- return copyUnit(portal);
- }
-
- break;
- case 1:
- if (portal.objtype === sdk.areas.UberTristram) {
- return copyUnit(portal);
- }
-
- break;
- }
- } while (portal.getNext());
- }
- }
-
- return false;
- };
-
- // Do mini ubers or Tristram based on area we're already in
- this.pandemoniumRun = function () {
- let i, findLoc;
-
- switch (me.area) {
- case sdk.areas.MatronsDen:
- Precast.doPrecast(true);
- Pather.moveToPreset(sdk.areas.MatronsDen, sdk.unittype.Object, 397, 2, 2);
- Attack.killTarget(707);
- Pickit.pickItems();
- Town.goToTown();
-
- break;
- case sdk.areas.ForgottenSands:
- Precast.doPrecast(true);
-
- findLoc = [20196, 8694, 20308, 8588, 20187, 8639, 20100, 8550, 20103, 8688, 20144, 8709, 20263, 8811, 20247, 8665];
-
- for (i = 0; i < findLoc.length; i += 2) {
- Pather.moveTo(findLoc[i], findLoc[i + 1]);
- delay(500);
-
- if (Game.getMonster(708)) {
- break;
- }
- }
-
- Attack.killTarget(708);
- Pickit.pickItems();
- Town.goToTown();
-
- break;
- case sdk.areas.FurnaceofPain:
- Precast.doPrecast(true);
- Pather.moveToPreset(sdk.areas.FurnaceofPain, sdk.unittype.Object, 397, 2, 2);
- Attack.killTarget(706);
- Pickit.pickItems();
- Town.goToTown();
-
- break;
- case sdk.areas.UberTristram:
- Pather.moveTo(25068, 5078);
- Precast.doPrecast(true);
-
- findLoc = [25040, 5101, 25040, 5166, 25122, 5170];
-
- for (i = 0; i < findLoc.length; i += 2) {
- Pather.moveTo(findLoc[i], findLoc[i + 1]);
- }
-
- if (me.paladin && me.checkSkill(sdk.skills.Salvation, sdk.skills.subindex.SoftPoints)) {
- Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right);
- }
-
- this.lure(704);
- Pather.moveTo(25129, 5198);
-
- if (me.paladin && me.checkSkill(sdk.skills.Salvation, sdk.skills.subindex.SoftPoints)) {
- Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right);
- }
-
- this.lure(704);
-
- if (!Game.getMonster(704)) {
- Pather.moveTo(25122, 5170);
- }
-
- Attack.killTarget(704);
-
- Pather.moveTo(25162, 5141);
- delay(3250);
-
- if (!Game.getMonster(709)) {
- Pather.moveTo(25122, 5170);
- }
-
- Attack.killTarget(709);
-
- if (!Game.getMonster(705)) {
- Pather.moveTo(25122, 5170);
- }
-
- Attack.killTarget(705);
- Pickit.pickItems();
- this.checkTorch();
-
- break;
- }
- };
-
- this.juvCheck = function () {
- let i,
- needJuvs = 0,
- col = Town.checkColumns(Storage.BeltSize());
-
- for (i = 0; i < 4; i += 1) {
- if (Config.BeltColumn[i] === "rv") {
- needJuvs += col[i];
- }
- }
-
- console.log("Need " + needJuvs + " juvs.");
-
- return needJuvs;
- };
-
- // Start
- let i, portal, tkeys, hkeys, dkeys, brains, eyes, horns;
-
- // Do town chores and quit if MakeTorch is true and we have a torch.
- this.checkTorch();
+function orgtorch () {
+ this.doneAreas = [];
+
+ // Identify & mule
+ this.checkTorch = function () {
+ if (me.inArea(sdk.areas.UberTristram)) {
+ Pather.moveTo(25105, 5140);
+ Pather.usePortal(sdk.areas.Harrogath);
+ }
+
+ Town.doChores();
+
+ let torch = me.findItem(604, 0, null, 7);
+
+ if (!torch || torch === undefined) {
+ return false;
+ }
+
+ if (!torch.identified) {
+ Town.identify();
+ }
+
+ for (let i = 0; i < 7; i++) {
+ if (torch.getStat(sdk.stats.AddClassSkills, i)) {
+ SoloEvents.sendToList({ profile: me.profile, ladder: me.ladder, torchType: i });
+
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ // Check whether the killer is alone in the game
+ this.aloneInGame = function () {
+ let party = getParty();
+
+ if (party) {
+ do {
+ if (party.name !== me.name) {
+ return false;
+ }
+ } while (party.getNext());
+ }
+
+ return true;
+ };
+
+ // Try to lure a monster - wait until it's close enough
+ this.lure = function (bossId) {
+ let tick,
+ unit = Game.getMonster(bossId);
+
+ if (unit) {
+ 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(sdk.items.quest.DiablosHorn),
+ brains = me.findItems(sdk.items.quest.MephistosBrain),
+ eyes = me.findItems(sdk.items.quest.BaalsEye);
+
+ if (!horns || !brains || !eyes) {
+ return false;
+ }
+
+ // We just need one set to make a torch
+ return horns.length && brains.length && eyes.length;
+ };
+
+ // Get fade in River of Flames
+ this.getFade = function () {
+ if (!me.getState(sdk.states.Fade)
+ && (me.checkItem({ name: sdk.locale.items.Treachery, equipped: true }).have
+ || me.checkItem({ name: sdk.locale.items.LastWish, equipped: true }).have
+ || me.checkItem({ name: sdk.locale.items.SpiritWard, equipped: true }).have)) {
+ if (!me.getState(sdk.states.Fade)) {
+ myPrint(sdk.colors.Orange + "OrgTorch :: " + sdk.colors.White + "Getting Fade");
+ Pather.useWaypoint(sdk.states.RiverofFlame);
+ Precast.doPrecast(true);
+ Pather.moveTo(7811, 5872);
+
+ me.paladin && me.checkSkill(sdk.skills.Salvation, sdk.skills.subindex.SoftPoints) && Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right);
+
+ while (!me.getState(sdk.states.Fade)) {
+ delay(100);
+ }
+
+ myPrint(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 portal,
+ item1 = mode === 0
+ ? me.findItem(sdk.items.quest.KeyofTerror, sdk.items.mode.inStorage)
+ : me.findItem(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage),
+ item2 = mode === 0
+ ? me.findItem(sdk.items.quest.KeyofHate, sdk.items.mode.inStorage)
+ : me.findItem(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage),
+ item3 = mode === 0
+ ? me.findItem(sdk.items.quest.KeyofDestruction, sdk.items.mode.inStorage)
+ : me.findItem(sdk.items.quest.MephistosBrain, 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);
+
+ portal = Game.getObject("portal");
+
+ if (portal) {
+ do {
+ switch (mode) {
+ case 0:
+ if ([sdk.areas.MatronsDen, sdk.areas.ForgottenSands, sdk.areas.FurnaceofPain].indexOf(portal.objtype) > -1 && this.doneAreas.indexOf(portal.objtype) === -1) {
+ this.doneAreas.push(portal.objtype);
+
+ return copyUnit(portal);
+ }
+
+ break;
+ case 1:
+ if (portal.objtype === sdk.areas.UberTristram) {
+ return copyUnit(portal);
+ }
+
+ break;
+ }
+ } while (portal.getNext());
+ }
+ }
+
+ return false;
+ };
+
+ // Do mini ubers or Tristram based on area we're already in
+ this.pandemoniumRun = function () {
+ let i, findLoc;
+
+ switch (me.area) {
+ case sdk.areas.MatronsDen:
+ Precast.doPrecast(true);
+ Pather.moveToPreset(sdk.areas.MatronsDen, sdk.unittype.Object, 397, 2, 2);
+ Attack.killTarget(707);
+ Pickit.pickItems();
+ Town.goToTown();
+
+ break;
+ case sdk.areas.ForgottenSands:
+ Precast.doPrecast(true);
+
+ findLoc = [20196, 8694, 20308, 8588, 20187, 8639, 20100, 8550, 20103, 8688, 20144, 8709, 20263, 8811, 20247, 8665];
+
+ for (i = 0; i < findLoc.length; i += 2) {
+ Pather.moveTo(findLoc[i], findLoc[i + 1]);
+ delay(500);
+
+ if (Game.getMonster(708)) {
+ break;
+ }
+ }
+
+ Attack.killTarget(708);
+ Pickit.pickItems();
+ Town.goToTown();
+
+ break;
+ case sdk.areas.FurnaceofPain:
+ Precast.doPrecast(true);
+ Pather.moveToPreset(sdk.areas.FurnaceofPain, sdk.unittype.Object, 397, 2, 2);
+ Attack.killTarget(706);
+ Pickit.pickItems();
+ Town.goToTown();
+
+ break;
+ case sdk.areas.UberTristram:
+ Pather.moveTo(25068, 5078);
+ Precast.doPrecast(true);
+
+ findLoc = [25040, 5101, 25040, 5166, 25122, 5170];
+
+ for (i = 0; i < findLoc.length; i += 2) {
+ Pather.moveTo(findLoc[i], findLoc[i + 1]);
+ }
+
+ if (me.paladin && me.checkSkill(sdk.skills.Salvation, sdk.skills.subindex.SoftPoints)) {
+ Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right);
+ }
+
+ this.lure(704);
+ Pather.moveTo(25129, 5198);
+
+ if (me.paladin && me.checkSkill(sdk.skills.Salvation, sdk.skills.subindex.SoftPoints)) {
+ Skill.setSkill(sdk.skills.Salvation, sdk.skills.hand.Right);
+ }
+
+ this.lure(704);
+
+ if (!Game.getMonster(704)) {
+ Pather.moveTo(25122, 5170);
+ }
+
+ Attack.killTarget(704);
+
+ Pather.moveTo(25162, 5141);
+ delay(3250);
+
+ if (!Game.getMonster(709)) {
+ Pather.moveTo(25122, 5170);
+ }
+
+ Attack.killTarget(709);
+
+ if (!Game.getMonster(705)) {
+ Pather.moveTo(25122, 5170);
+ }
+
+ Attack.killTarget(705);
+ Pickit.pickItems();
+ this.checkTorch();
+
+ break;
+ }
+ };
+
+ this.juvCheck = function () {
+ let i,
+ needJuvs = 0,
+ col = Storage.Belt.checkColumns(Storage.BeltSize());
+
+ for (i = 0; i < 4; i += 1) {
+ if (Config.BeltColumn[i] === "rv") {
+ needJuvs += col[i];
+ }
+ }
+
+ console.log("Need " + needJuvs + " juvs.");
+
+ return needJuvs;
+ };
+
+ // Start
+ let i, portal, tkeys, hkeys, dkeys, brains, eyes, horns;
+
+ // Do town chores and quit if MakeTorch is true and we have a torch.
+ this.checkTorch();
- // Count keys and organs
- tkeys = me.findItems(sdk.items.quest.KeyofTerror, sdk.items.mode.inStorage).length || 0;
- hkeys = me.findItems(sdk.items.quest.KeyofHate, sdk.items.mode.inStorage).length || 0;
- dkeys = me.findItems(sdk.items.quest.KeyofDestruction, sdk.items.mode.inStorage).length || 0;
- brains = me.findItems(sdk.items.quest.MephistosBrain, sdk.items.mode.inStorage).length || 0;
- eyes = me.findItems(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage).length || 0;
- horns = me.findItems(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage).length || 0;
-
- // End the script if we don't have enough keys nor organs
- if ((tkeys < 3 || hkeys < 3 || dkeys < 3) && (brains < 1 || eyes < 1 || horns < 1)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Not enough keys or organs.");
-
- return true;
- }
+ // Count keys and organs
+ tkeys = me.findItems(sdk.items.quest.KeyofTerror, sdk.items.mode.inStorage).length || 0;
+ hkeys = me.findItems(sdk.items.quest.KeyofHate, sdk.items.mode.inStorage).length || 0;
+ dkeys = me.findItems(sdk.items.quest.KeyofDestruction, sdk.items.mode.inStorage).length || 0;
+ brains = me.findItems(sdk.items.quest.MephistosBrain, sdk.items.mode.inStorage).length || 0;
+ eyes = me.findItems(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage).length || 0;
+ horns = me.findItems(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage).length || 0;
+
+ // End the script if we don't have enough keys nor organs
+ if ((tkeys < 3 || hkeys < 3 || dkeys < 3) && (brains < 1 || eyes < 1 || horns < 1)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Not enough keys or organs.");
+
+ return true;
+ }
- Config.UseMerc = false;
-
- // We have enough keys, do mini ubers
- if (tkeys >= 3 && hkeys >= 3 && dkeys >= 3) {
- this.getFade();
- console.log("ÿc8Kolbot-SoloPlayÿc0: Making organs.");
- D2Bot.printToConsole("ÿc8Kolbot-SoloPlayÿc0 :: OrgTorch: Making organs.", sdk.colors.D2Bot.Orange);
+ Config.UseMerc = false;
+
+ // We have enough keys, do mini ubers
+ if (tkeys >= 3 && hkeys >= 3 && dkeys >= 3) {
+ this.getFade();
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Making organs.");
+ D2Bot.printToConsole("ÿc8Kolbot-SoloPlayÿc0 :: OrgTorch: Making organs.", sdk.colors.D2Bot.Orange);
- for (i = 0; i < 3; i += 1) {
- // Abort if we have a complete set of organs
- // check after at least one portal is made
- if (i > 0 && this.completeSetCheck()) {
- break;
- }
+ for (i = 0; i < 3; i += 1) {
+ // Abort if we have a complete set of organs
+ // check after at least one portal is made
+ if (i > 0 && this.completeSetCheck()) {
+ break;
+ }
- portal = this.openPortal(0);
+ portal = this.openPortal(0);
- if (portal) {
- switch (portal.objtype) {
- case sdk.areas.MatronsDen:
- Town.buyPots(10, "Thawing", true, true);
- Town.buyPots(10, "Antidote", true, true);
- Town.buyPots(10, "Antidote", true, true); // Double stack to ensure it lasts
+ if (portal) {
+ switch (portal.objtype) {
+ case sdk.areas.MatronsDen:
+ Town.buyPots(10, "Thawing", true, true);
+ Town.buyPots(10, "Antidote", true, true);
+ Town.buyPots(10, "Antidote", true, true); // Double stack to ensure it lasts
- break;
- case sdk.areas.ForgottenSands:
- Town.buyPots(10, "Thawing", true, true);
+ break;
+ case sdk.areas.ForgottenSands:
+ Town.buyPots(10, "Thawing", true, true);
- break;
- case sdk.areas.FurnaceofPain:
- Town.buyPots(10, "Thawing", true, true);
- Town.buyPots(10, "Antidote", true, true);
+ break;
+ case sdk.areas.FurnaceofPain:
+ Town.buyPots(10, "Thawing", true, true);
+ Town.buyPots(10, "Antidote", true, true);
- break;
- }
+ break;
+ }
- Town.move("stash");
- Pather.usePortal(null, null, portal);
- }
+ Town.move("stash");
+ Pather.usePortal(null, null, portal);
+ }
- this.pandemoniumRun();
- }
- }
+ this.pandemoniumRun();
+ }
+ }
- // Don't make torches if not configured to OR if the char already has one
- if (this.checkTorch()) {
- return true;
- }
+ // Don't make torches if not configured to OR if the char already has one
+ if (this.checkTorch()) {
+ return true;
+ }
- // Count organs
- brains = me.findItems(sdk.items.quest.MephistosBrain, sdk.items.mode.inStorage).length || 0;
- eyes = me.findItems(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage).length || 0;
- horns = me.findItems(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage).length || 0;
-
- // We have enough organs, do Tristram
- if (brains && eyes && horns) {
- this.getFade();
- console.log("ÿc8Kolbot-SoloPlayÿc0: Making torch");
- D2Bot.printToConsole("ÿc8Kolbot-SoloPlayÿc0 :: OrgTorch: Making torch.", sdk.colors.D2Bot.Orange);
-
- portal = this.openPortal(1);
-
- if (portal) {
- Pather.usePortal(null, null, portal);
- }
-
- this.pandemoniumRun();
- }
-
- return true;
+ // Count organs
+ brains = me.findItems(sdk.items.quest.MephistosBrain, sdk.items.mode.inStorage).length || 0;
+ eyes = me.findItems(sdk.items.quest.BaalsEye, sdk.items.mode.inStorage).length || 0;
+ horns = me.findItems(sdk.items.quest.DiablosHorn, sdk.items.mode.inStorage).length || 0;
+
+ // We have enough organs, do Tristram
+ if (brains && eyes && horns) {
+ this.getFade();
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Making torch");
+ D2Bot.printToConsole("ÿc8Kolbot-SoloPlayÿc0 :: OrgTorch: Making torch.", sdk.colors.D2Bot.Orange);
+
+ portal = this.openPortal(1);
+
+ if (portal) {
+ Pather.usePortal(null, null, portal);
+ }
+
+ this.pandemoniumRun();
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/pindle.js b/libs/SoloPlay/Scripts/pindle.js
index 8d6298f3..53a75f24 100644
--- a/libs/SoloPlay/Scripts/pindle.js
+++ b/libs/SoloPlay/Scripts/pindle.js
@@ -6,17 +6,22 @@
*/
function pindle () {
- Town.doChores(false, { fullChores: true });
- Town.goToTown(5);
- myPrint("starting pindle");
+ Town.doChores(false, { fullChores: true });
+ Town.goToTown(5);
+ myPrint("starting pindle");
- !Pather.getPortal(sdk.areas.NihlathaksTemple) && Town.npcInteract("anya");
- if (!Pather.usePortal(sdk.areas.NihlathaksTemple)) return true;
+ Town.move("anya");
+ !Pather.getPortal(sdk.areas.NihlathaksTemple) && Town.npcInteract("anya");
+ if (!Pather.usePortal(sdk.areas.NihlathaksTemple)) return true;
- Precast.doPrecast(true);
- Pather.moveTo(10058, 13234);
- Attack.killTarget(getLocaleString(sdk.locale.monsters.Pindleskin));
- Pickit.pickItems();
+ Precast.doPrecast(true);
+ Pather.moveTo(10058, 13234);
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.Pindleskin));
+ Pickit.pickItems();
- return true;
+ if (SoloIndex.index.nith.shouldRun()) {
+ Loader.skipTown.push("nith");
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/pits.js b/libs/SoloPlay/Scripts/pits.js
index 584259e6..983d0798 100644
--- a/libs/SoloPlay/Scripts/pits.js
+++ b/libs/SoloPlay/Scripts/pits.js
@@ -6,18 +6,18 @@
*/
function pits () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting pits");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting pits");
- Pather.checkWP(sdk.areas.BlackMarsh, true) ? Pather.useWaypoint(sdk.areas.BlackMarsh) : Pather.getWP(sdk.areas.BlackMarsh);
- Precast.doPrecast(true);
+ Pather.checkWP(sdk.areas.BlackMarsh, true) ? Pather.useWaypoint(sdk.areas.BlackMarsh) : Pather.getWP(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");
- Attack.clearLevel();
+ if (!Pather.moveToExit([sdk.areas.TamoeHighland, sdk.areas.PitLvl1], true)) throw new Error("Failed to move to Pit level 1");
+ Attack.clearLevel();
- if (!Pather.moveToExit(sdk.areas.PitLvl2, true)) throw new Error("Failed to move to Pit level 2");
- Attack.clearLevel();
- Misc.openChestsInArea(sdk.areas.PitLvl2);
+ if (!Pather.moveToExit(sdk.areas.PitLvl2, true)) throw new Error("Failed to move to Pit level 2");
+ Attack.clearLevel();
+ Misc.openChestsInArea(sdk.areas.PitLvl2);
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/radament.js b/libs/SoloPlay/Scripts/radament.js
index d7e483c8..8709ba17 100644
--- a/libs/SoloPlay/Scripts/radament.js
+++ b/libs/SoloPlay/Scripts/radament.js
@@ -6,35 +6,35 @@
*/
function radament () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting radament");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting radament");
- if (!Pather.checkWP(sdk.areas.A2SewersLvl2, true)) {
- Town.goToTown(2);
- Pather.moveToExit(sdk.areas.A2SewersLvl1, true);
- Pather.getWP(sdk.areas.A2SewersLvl2);
- } else {
- Pather.useWaypoint(sdk.areas.A2SewersLvl2);
- }
+ if (!Pather.checkWP(sdk.areas.A2SewersLvl2, true)) {
+ Town.goToTown(2);
+ Pather.moveToExit(sdk.areas.A2SewersLvl1, true);
+ Pather.getWP(sdk.areas.A2SewersLvl2);
+ } else {
+ Pather.useWaypoint(sdk.areas.A2SewersLvl2);
+ }
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.A2SewersLvl2, sdk.areas.A2SewersLvl3, true);
- Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricScrollChest);
- Attack.killTarget("Radament");
- let book = Game.getItem(sdk.quest.item.BookofSkill);
- !!book ? Pickit.pickItem(book) : Attack.killTarget("Radament");
- Pickit.pickItems();
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.A2SewersLvl2, sdk.areas.A2SewersLvl3, true);
+ Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HoradricScrollChest);
+ Attack.killTarget("Radament");
+ let book = Game.getItem(sdk.quest.item.BookofSkill);
+ !!book ? Pickit.pickItem(book) : Attack.killTarget("Radament");
+ Pickit.pickItems();
- if (Misc.checkQuest(sdk.quest.id.RadamentsLair, sdk.quest.states.ReqComplete)) {
- Town.npcInteract("atma");
- me.cancelUIFlags();
+ if (Misc.checkQuest(sdk.quest.id.RadamentsLair, sdk.quest.states.ReqComplete)) {
+ Town.npcInteract("atma");
+ me.cancelUIFlags();
- let book = me.getItem(sdk.items.quest.BookofSkill);
- if (book) {
- book.isInStash && Town.openStash() && delay(300);
- book.use() && delay(500) && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
- }
- }
+ let book = me.getItem(sdk.items.quest.BookofSkill);
+ if (book) {
+ book.isInStash && Town.openStash() && delay(300);
+ book.use() && delay(500) && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
+ }
+ }
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/river.js b/libs/SoloPlay/Scripts/river.js
index c2194551..c85bfda4 100644
--- a/libs/SoloPlay/Scripts/river.js
+++ b/libs/SoloPlay/Scripts/river.js
@@ -5,26 +5,26 @@
*
*/
-function river() {
- myPrint("starting river");
+function river () {
+ myPrint("starting river");
- Town.doChores(null, {thawing: me.coldRes < 75, antidote: me.poisonRes < 75});
+ Town.doChores(null, { thawing: me.coldRes < 75, antidote: me.poisonRes < 75 });
- Pather.checkWP(sdk.areas.CityoftheDamned, true) ? Pather.useWaypoint(sdk.areas.CityoftheDamned) : Pather.getWP(sdk.areas.CityoftheDamned);
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, true);
+ Pather.checkWP(sdk.areas.CityoftheDamned, true) ? Pather.useWaypoint(sdk.areas.CityoftheDamned) : Pather.getWP(sdk.areas.CityoftheDamned);
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.CityoftheDamned, sdk.areas.RiverofFlame, true);
- if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Hephasto");
- }
+ if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.HellForge)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Hephasto");
+ }
- try {
- Attack.clear(20, 0, sdk.monsters.Hephasto);
- } catch (err) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Hephasto");
- }
+ try {
+ Attack.clear(20, 0, sdk.monsters.Hephasto);
+ } catch (err) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Hephasto");
+ }
- Pather.getWP(sdk.areas.RiverofFlame, true);
+ Pather.getWP(sdk.areas.RiverofFlame, true);
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/savebarby.js b/libs/SoloPlay/Scripts/savebarby.js
index 142bc274..2ed447ab 100644
--- a/libs/SoloPlay/Scripts/savebarby.js
+++ b/libs/SoloPlay/Scripts/savebarby.js
@@ -7,41 +7,41 @@
*/
function savebarby () {
- let coords = [];
+ let coords = [];
- Town.doChores(false, { fullChores: true });
- myPrint("starting barbies");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting barbies");
- Pather.checkWP(sdk.areas.FrigidHighlands, true) ? Pather.useWaypoint(sdk.areas.FrigidHighlands) : Pather.getWP(sdk.areas.FrigidHighlands);
- Precast.doPrecast(true);
- let barbies = (Game.getPresetObjects(me.area, sdk.quest.chest.BarbCage) || []);
+ Pather.checkWP(sdk.areas.FrigidHighlands, true) ? Pather.useWaypoint(sdk.areas.FrigidHighlands) : Pather.getWP(sdk.areas.FrigidHighlands);
+ Precast.doPrecast(true);
+ let barbies = (Game.getPresetObjects(me.area, sdk.quest.chest.BarbCage) || []);
- if (!barbies) return false;
+ if (!barbies) return false;
- for (let cage = 0 ; cage < barbies.length ; cage += 1) {
- coords.push({
- x: barbies[cage].roomx * 5 + barbies[cage].x - 3, //Dark-f: x-3
- y: barbies[cage].roomy * 5 + barbies[cage].y
- });
- }
+ for (let cage = 0 ; cage < barbies.length ; cage += 1) {
+ coords.push({
+ x: barbies[cage].roomx * 5 + barbies[cage].x - 3, //Dark-f: x-3
+ y: barbies[cage].roomy * 5 + barbies[cage].y
+ });
+ }
- for (let k = 0; k < coords.length; k += 1) {
- me.overhead("let my barby go! " + (k + 1) + "/" + barbies.length);
- Pather.moveToUnit(coords[k], 2, 0);
- let door = Game.getMonster(sdk.monsters.PrisonDoor);
+ for (let k = 0; k < coords.length; k += 1) {
+ me.overhead("let my barby go! " + (k + 1) + "/" + barbies.length);
+ Pather.moveToUnit(coords[k], 2, 0);
+ let door = Game.getMonster(sdk.monsters.PrisonDoor);
- if (door) {
- Pather.moveToUnit(door, 1, 0);
+ if (door) {
+ Pather.moveToUnit(door, 1, 0);
- for (let i = 0; i < 20 && door.hp; i += 1) {
- Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), door);
- }
- }
+ for (let i = 0; i < 20 && door.hp; i += 1) {
+ Skill.cast(Config.AttackSkill[1], Skill.getHand(Config.AttackSkill[1]), door);
+ }
+ }
- delay(1500 + me.ping);
- }
+ delay(1500 + me.ping);
+ }
- Town.npcInteract("qual_kehk");
+ Town.npcInteract("qual_kehk");
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/shenk.js b/libs/SoloPlay/Scripts/shenk.js
index 24eb05e3..a0fcf8b3 100644
--- a/libs/SoloPlay/Scripts/shenk.js
+++ b/libs/SoloPlay/Scripts/shenk.js
@@ -1,22 +1,25 @@
/**
* @filename shenk.js
-* @author isid0re, theBGuy
+* @author theBGuy
+* @credit kolton for the original
* @desc shenk quest for sockets, wp's, and mf
*
*/
function shenk () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting shenk");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting shenk");
- Pather.checkWP(sdk.areas.FrigidHighlands, true) ? Pather.useWaypoint(sdk.areas.FrigidHighlands) : Pather.getWP(sdk.areas.FrigidHighlands);
- Precast.doPrecast(true);
- Pather.moveTo(3745, 5084);
- Attack.killTarget(getLocaleString(sdk.locale.monsters.EldritchtheRectifier));
+ Pather.checkWP(sdk.areas.FrigidHighlands, true)
+ ? Pather.useWaypoint(sdk.areas.FrigidHighlands)
+ : Pather.getWP(sdk.areas.FrigidHighlands);
+ Precast.doPrecast(true);
+ Pather.moveTo(3745, 5084);
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.EldritchtheRectifier));
- Pather.moveToExit(sdk.areas.BloodyFoothills, true);
- Pather.moveTo(3883, 5113);
- Attack.killTarget(getLocaleString(sdk.locale.monsters.ShenktheOverseer));
+ Pather.moveToExit(sdk.areas.BloodyFoothills, true);
+ Pather.moveTo(3883, 5113);
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.ShenktheOverseer));
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/smith.js b/libs/SoloPlay/Scripts/smith.js
index 057c14b8..1daa1623 100644
--- a/libs/SoloPlay/Scripts/smith.js
+++ b/libs/SoloPlay/Scripts/smith.js
@@ -6,34 +6,34 @@
*/
function smith () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting smith");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting smith");
- Pather.checkWP(sdk.areas.OuterCloister, true) ? Pather.useWaypoint(sdk.areas.OuterCloister) : Pather.getWP(sdk.areas.OuterCloister);
- Precast.doPrecast(true);
- Pather.moveToExit(sdk.areas.Barracks);
+ Pather.checkWP(sdk.areas.OuterCloister, true) ? Pather.useWaypoint(sdk.areas.OuterCloister) : Pather.getWP(sdk.areas.OuterCloister);
+ Precast.doPrecast(true);
+ Pather.moveToExit(sdk.areas.Barracks);
- if (!Pather.moveToPreset(sdk.areas.Barracks, sdk.unittype.Object, sdk.quest.chest.MalusHolder)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to the Smith");
- return false;
- }
+ if (!Pather.moveToPreset(sdk.areas.Barracks, sdk.unittype.Object, sdk.quest.chest.MalusHolder)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to the Smith");
+ return false;
+ }
- try {
- Attack.killTarget(sdk.monsters.TheSmith);
- } catch (err) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Smith");
- }
+ try {
+ Attack.killTarget(sdk.monsters.TheSmith);
+ } catch (err) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill Smith");
+ }
- Quest.collectItem(sdk.items.quest.HoradricMalus, sdk.quest.chest.MalusHolder);
- Pickit.pickItems();
- Town.goToTown();
- Town.npcInteract("charsi");
+ Quest.collectItem(sdk.items.quest.HoradricMalus, sdk.quest.chest.MalusHolder);
+ Pickit.pickItems();
+ Town.goToTown();
+ Town.npcInteract("charsi");
- if (!getWaypoint(Pather.wpAreas.indexOf(sdk.areas.JailLvl1))) {
- Pather.usePortal(null, me.name);
- Pather.getWP(sdk.areas.JailLvl1);
- Pather.useWaypoint(sdk.areas.RogueEncampment);
- }
+ if (!getWaypoint(Pather.wpAreas.indexOf(sdk.areas.JailLvl1))) {
+ Pather.usePortal(null, me.name);
+ Pather.getWP(sdk.areas.JailLvl1);
+ Pather.useWaypoint(sdk.areas.RogueEncampment);
+ }
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/staff.js b/libs/SoloPlay/Scripts/staff.js
index 305a0907..427df518 100644
--- a/libs/SoloPlay/Scripts/staff.js
+++ b/libs/SoloPlay/Scripts/staff.js
@@ -6,23 +6,23 @@
*/
function staff () {
- Town.doChores(false, { fullChores: true });
- myPrint("starting staff");
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting staff");
- Pather.checkWP(sdk.areas.FarOasis, true) ? Pather.useWaypoint(sdk.areas.FarOasis) : Pather.getWP(sdk.areas.FarOasis);
- Precast.doPrecast(true);
- Pather.clearToExit(sdk.areas.FarOasis, sdk.areas.MaggotLairLvl1, true);
- Pather.clearToExit(sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, true);
- Pather.clearToExit(sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3, true);
+ Pather.checkWP(sdk.areas.FarOasis, true) ? Pather.useWaypoint(sdk.areas.FarOasis) : Pather.getWP(sdk.areas.FarOasis);
+ Precast.doPrecast(true);
+ Pather.clearToExit(sdk.areas.FarOasis, sdk.areas.MaggotLairLvl1, true);
+ Pather.clearToExit(sdk.areas.MaggotLairLvl1, sdk.areas.MaggotLairLvl2, true);
+ Pather.clearToExit(sdk.areas.MaggotLairLvl2, sdk.areas.MaggotLairLvl3, true);
- if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to staff");
- return me.getItem(sdk.items.quest.ShaftoftheHoradricStaff);
- }
+ if (!Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.ShaftoftheHoradricStaffChest)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to staff");
+ return me.getItem(sdk.items.quest.ShaftoftheHoradricStaff);
+ }
- Quest.collectItem(sdk.items.quest.ShaftoftheHoradricStaff, sdk.quest.chest.ShaftoftheHoradricStaffChest);
- Quest.stashItem(sdk.items.quest.ShaftoftheHoradricStaff);
+ Quest.collectItem(sdk.items.quest.ShaftoftheHoradricStaff, sdk.quest.chest.ShaftoftheHoradricStaffChest);
+ Quest.stashItem(sdk.items.quest.ShaftoftheHoradricStaff);
- return me.getItem(sdk.items.quest.ShaftoftheHoradricStaff);
+ return me.getItem(sdk.items.quest.ShaftoftheHoradricStaff);
}
diff --git a/libs/SoloPlay/Scripts/summoner.js b/libs/SoloPlay/Scripts/summoner.js
index 09b141ed..a21aebec 100644
--- a/libs/SoloPlay/Scripts/summoner.js
+++ b/libs/SoloPlay/Scripts/summoner.js
@@ -6,113 +6,134 @@
*/
function summoner () {
- // @isid0re
- const teleportPads = function () {
- if (!me.inArea(sdk.areas.ArcaneSanctuary) || Pather.useTeleport()) return true;
-
- let tppPath;
- let [wpX, wpY] = [25449, 5449];
- let ntppPath = [[53, 2], [103, -3], [113, -68], [173, -58], [243, -73], [293, -58], [353, -68], [372, -62], [342, -17]];
- let stppPath = [[-56, 2], [-128, -7], [-98, 78], [-176, 62], [-243, 58], [-296, 62], [-372, 62], [-366, 12]];
- let etppPath = [[28, 52], [-12, 92], [53, 112], [72, 118], [88, 172], [54, 227], [43, 247], [88, 292], [82, 378], [-16, 332], [2, 353]];
- let wtppPath = [[-26, -63], [2, -121], [3, -133], [62, -117], [34, -183], [54, -228], [43, -243], [34, -303], [72, -351], [64, -368], [23, -338]];
- let stand = Game.getPresetObject(me.area, sdk.objects.Journal);
- let [tppPathX, tppPathY] = [(stand.roomx * 5 + stand.x), (stand.roomy * 5 + stand.y)];
- console.debug(tppPathX, tppPathY);
- let tppID = [192, 304, 305, 306];
-
- switch (tppPathX) {
- case 25011:
- tppPath = ntppPath;
- break;
- case 25866:
- tppPath = stppPath;
- break;
- case 25431:
- switch (tppPathY) {
- case 5011:
- tppPath = etppPath;
- break;
- case 5861:
- tppPath = wtppPath;
- break;
- }
-
- break;
- }
-
- if (getPath(me.area, me.x, me.y, tppPathX, tppPathY, 0, 10).length === 0) {
- me.overhead("Using telepad layout");
-
- for (let i = 0; i < tppPath.length; i += 1) {
- for (let h = 0; h < 5; h += 1) {
- Pather.moveTo(wpX - tppPath[i][0], wpY - tppPath[i][1]);
-
- for (let activate = 0; activate < tppID.length; activate += 1) {
- let telepad = Game.getObject(tppID[activate]);
-
- if (telepad) {
- do {
- if (Math.abs((telepad.x - (wpX - tppPath[i][0]) + (telepad.y - (wpY - tppPath[i][1])))) <= 0) {
- delay(100 + me.ping);
- telepad.interact();
- }
- } while (telepad.getNext());
- }
- }
- }
- }
- }
-
- return true;
- };
-
- // START
- Town.doChores(false, { fullChores: true });
- myPrint("starting summoner");
-
- Pather.checkWP(sdk.areas.ArcaneSanctuary, true) ? Pather.useWaypoint(sdk.areas.ArcaneSanctuary) : Pather.getWP(sdk.areas.ArcaneSanctuary);
- Precast.doPrecast(true);
- teleportPads();
-
- try {
- Pather.moveNearPreset(sdk.areas.ArcaneSanctuary, sdk.unittype.Object, sdk.objects.Journal, 10);
- } catch (err) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to reach Summoner. Retry");
-
- if (!Pather.moveToPreset(sdk.areas.ArcaneSanctuary, sdk.unittype.Object, sdk.objects.Journal, -3, -3)) {
- throw new Error("Failed to reach summoner");
- }
- }
-
- try {
- Attack.killTarget(sdk.monsters.TheSummoner);
- } catch (e) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill summoner");
-
- return false;
- }
-
- let journal = Game.getObject(sdk.objects.Journal);
-
- if (journal) {
- while (!Pather.getPortal(sdk.areas.CanyonofMagic)) {
- Misc.openChest(journal);
- delay(1000 + me.ping);
- me.cancel();
- }
- }
-
- Pather.usePortal(sdk.areas.CanyonofMagic);
-
- if (!Pather.checkWP(sdk.areas.CanyonofMagic)) {
- Pather.getWP(sdk.areas.CanyonofMagic);
- Pather.useWaypoint(sdk.areas.LutGholein);
- } else {
- Pather.useWaypoint(sdk.areas.LutGholein);
- }
-
- !me.summoner && Town.npcInteract("drognan");
-
- return true;
+ // @isid0re
+ const teleportPads = function () {
+ if (!me.inArea(sdk.areas.ArcaneSanctuary) || Pather.useTeleport()) return true;
+
+ const [wpX, wpY] = [25449, 5449];
+ const tppID = [192, 304, 305, 306];
+ let ntppPath = [
+ [53, 2], [103, -3],
+ [113, -68], [173, -58],
+ [243, -73], [293, -58],
+ [353, -68], [372, -62], [342, -17]
+ ];
+ let stppPath = [
+ [-56, 2], [-128, -7],
+ [-98, 78], [-176, 62],
+ [-243, 58], [-296, 62],
+ [-372, 62], [-366, 12]
+ ];
+ let etppPath = [
+ [28, 52], [-12, 92],
+ [53, 112], [72, 118],
+ [88, 172], [54, 227],
+ [43, 247], [88, 292],
+ [82, 378], [-16, 332], [2, 353]
+ ];
+ let wtppPath = [
+ [-26, -63], [2, -121],
+ [3, -133], [62, -117],
+ [34, -183], [54, -228],
+ [43, -243], [34, -303],
+ [72, -351], [64, -368], [23, -338]
+ ];
+ const stand = Game.getPresetObject(me.area, sdk.objects.Journal).realCoords();
+ console.debug(stand.x, stand.y);
+
+ /** @type {[number, number][]} */
+ let tppPath;
+ if (stand.x === 25011) {
+ tppPath = ntppPath;
+ } else if (stand.x === 25866) {
+ tppPath = stppPath;
+ } else if (stand.x === 25431) {
+ if (stand.y === 5011) {
+ tppPath = etppPath;
+ } else if (stand.y === 5861) {
+ tppPath = wtppPath;
+ }
+ }
+
+ if (getPath(me.area, me.x, me.y, stand.x, stand.y, 0, 10).length === 0) {
+ me.overhead("Using telepad layout");
+
+ for (let [x, y] of tppPath) {
+ for (let h = 0; h < 5; h += 1) {
+ // todo - these are tkable, handle that
+ Pather.moveTo(wpX - x, wpY - y);
+
+ for (let id of tppID) {
+ let telepad = Game.getObject(id);
+
+ if (telepad) {
+ do {
+ if (Math.abs((telepad.x - (wpX - x) + (telepad.y - (wpY - y)))) <= 0) {
+ delay(100 + me.ping);
+ telepad.interact();
+ }
+ } while (telepad.getNext());
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ };
+
+ // START
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting summoner");
+
+ if (!Misc.checkQuest(sdk.quest.id.TheArcaneSanctuary, 3/* talked to Jerhyn */)) {
+ Town.npcInteract("jerhyn");
+ }
+
+ Pather.checkWP(sdk.areas.ArcaneSanctuary, true)
+ ? Pather.useWaypoint(sdk.areas.ArcaneSanctuary)
+ : Pather.getWP(sdk.areas.ArcaneSanctuary);
+ Precast.doPrecast(true);
+ teleportPads();
+
+ try {
+ Pather.moveNearPreset(sdk.areas.ArcaneSanctuary, sdk.unittype.Object, sdk.objects.Journal, 10);
+ } catch (err) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to reach Summoner. Retry");
+
+ if (!Pather.moveToPreset(sdk.areas.ArcaneSanctuary, sdk.unittype.Object, sdk.objects.Journal, -3, -3)) {
+ throw new Error("Failed to reach summoner");
+ }
+ }
+
+ try {
+ Attack.killTarget(sdk.monsters.TheSummoner);
+ } catch (e) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to kill summoner");
+
+ return false;
+ }
+
+ let journal = Game.getObject(sdk.objects.Journal);
+
+ if (journal) {
+ while (!Pather.getPortal(sdk.areas.CanyonofMagic)) {
+ Misc.openChest(journal);
+ delay(1000 + me.ping);
+ me.cancel();
+ }
+ }
+
+ Pather.usePortal(sdk.areas.CanyonofMagic);
+
+ if (!Pather.checkWP(sdk.areas.CanyonofMagic)) {
+ Pather.getWP(sdk.areas.CanyonofMagic);
+ Pather.useWaypoint(sdk.areas.LutGholein);
+ } else {
+ Pather.useWaypoint(sdk.areas.LutGholein);
+ }
+
+ !me.summoner && Town.npcInteract("drognan");
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/templeruns.js b/libs/SoloPlay/Scripts/templeruns.js
index 1b09efb5..ad9f1f4c 100644
--- a/libs/SoloPlay/Scripts/templeruns.js
+++ b/libs/SoloPlay/Scripts/templeruns.js
@@ -1,51 +1,72 @@
/**
* @filename templeruns.js
-* @author isid0re, theBGuy
-* @author Xcon
+* @author theBGuy
* @desc temple runs for exp
*
*/
function templeruns () {
- myPrint("starting temple runs");
- // todo - calculate effort required to clear temple and decide whether to run it or move to next
- const temples = [
- [sdk.areas.FlayerJungle, sdk.areas.LowerKurast], [sdk.areas.KurastBazaar, sdk.areas.RuinedTemple],
- [sdk.areas.KurastBazaar, sdk.areas.DisusedFane], [sdk.areas.UpperKurast, sdk.areas.ForgottenReliquary],
- [sdk.areas.UpperKurast, sdk.areas.ForgottenTemple], [sdk.areas.UpperKurast, sdk.areas.KurastCauseway, sdk.areas.RuinedFane], [sdk.areas.UpperKurast, sdk.areas.KurastCauseway, sdk.areas.DisusedReliquary]
- ];
- Town.doChores(false, { fullChores: true });
+ myPrint("starting temple runs");
+ // todo - calculate effort required to clear temple and decide whether to run it or move to next
+ Town.doChores(false, { fullChores: true });
+ Pather.useWaypoint(sdk.areas.LowerKurast);
+ Misc.openChestsInArea(sdk.areas.LowerKurast);
- for (let run = 0; run < temples.length; run++) {
- Pather.checkWP(temples[run][0], true) ? Pather.useWaypoint(temples[run][0]) : Pather.getWP(temples[run][0]);
- Precast.doPrecast(true);
+ // START
+ [
+ {
+ 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(function (area) {
+ try {
+ if (!me.inArea(area.base)) {
+ if (!Pather.moveToExit(area.base, true)) throw new Error("Failed to change area");
+ }
+ const precastTimeout = getTickCount() + Time.minutes(2);
+ if (Pather.wpAreas.includes(area.base)
+ && !getWaypoint(Pather.wpAreas.indexOf(area.base))) {
+ Pather.getWP(area.base);
+ }
+ /** @type {Map precastTimeout));
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ });
- if (Pather.moveToExit(temples[run], true, true)) {
- if (me.inArea(sdk.areas.LowerKurast)) {
- Misc.openChestsInArea(sdk.areas.LowerKurast);
- } else if (me.inArea(sdk.areas.RuinedTemple) && !me.lamessen) {
- me.overhead("lamessen");
- Pather.moveToPreset(sdk.areas.RuinedTemple, sdk.unittype.Object, sdk.quest.chest.LamEsensTomeHolder);
- Quest.collectItem(sdk.quest.item.LamEsensTome, sdk.quest.chest.LamEsensTomeHolder);
- Quest.unfinishedQuests();
- } else {
- Attack.clearLevel(0xF);
- }
- }
+ if (!Pather.checkWP(sdk.areas.Travincal)) {
+ Pather.getWP(sdk.areas.Travincal);
+ Town.goToTown();
+ }
- Town.goToTown();
- }
-
- if (!me.inTown) {
- Town.goToTown();
-
- if (!me.mephisto) {
- if (!Pather.checkWP(sdk.areas.Travincal)) {
- Pather.getWP(sdk.areas.Travincal);
- Town.goToTown();
- }
- }
- }
-
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/tombs.js b/libs/SoloPlay/Scripts/tombs.js
index ac6d50bc..098aff93 100644
--- a/libs/SoloPlay/Scripts/tombs.js
+++ b/libs/SoloPlay/Scripts/tombs.js
@@ -15,39 +15,44 @@
*/
function tombs () {
- myPrint("starting tombs");
-
- const tombID = [
- sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb1,
- sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7
- ];
- Town.doChores(false, { fullChores: true });
-
- for (let number = 0; number < tombID.length; number++) {
- if (SoloIndex.index.duriel.skipIf()) return true;
- Pather.checkWP(sdk.areas.CanyonofMagic, true) ? Pather.useWaypoint(sdk.areas.CanyonofMagic) : Pather.getWP(sdk.areas.CanyonofMagic);
- Precast.doPrecast(true);
-
- if (Pather.moveToExit(tombID[number], true, true)) {
- me.overhead("Tomb #" + (number + 1));
- const duryTomb = getRoom().correcttomb === me.area;
-
- let obj = Game.getPresetObject(me.area, (!duryTomb ? sdk.objects.SmallSparklyChest : sdk.objects.HoradricStaffHolder));
- !!obj && Pather.moveToUnit(obj);
-
- Attack.clear(50);
- Pickit.pickItems();
-
- if (me.duriel && Game.getObject(sdk.objects.PortaltoDurielsLair)) {
- Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair);
- me.sorceress && !me.normal ? Attack.pwnDury() : Attack.killTarget("Duriel");
- Pickit.pickItems();
- }
- }
-
- Town.goToTown();
- Town.heal();
- }
-
- return true;
+ myPrint("starting tombs");
+
+ const tombID = [
+ sdk.areas.TalRashasTomb4, sdk.areas.TalRashasTomb3, sdk.areas.TalRashasTomb2, sdk.areas.TalRashasTomb1,
+ sdk.areas.TalRashasTomb5, sdk.areas.TalRashasTomb6, sdk.areas.TalRashasTomb7
+ ];
+ Town.doChores(false, { fullChores: true });
+
+ for (let number = 0; number < tombID.length; number++) {
+ if (SoloIndex.index.duriel.skipIf()) return true;
+ Pather.checkWP(sdk.areas.CanyonofMagic, true)
+ ? Pather.useWaypoint(sdk.areas.CanyonofMagic)
+ : Pather.getWP(sdk.areas.CanyonofMagic);
+ Precast.doPrecast(true);
+
+ if (Pather.moveToExit(tombID[number], true, true)) {
+ me.overhead("Tomb #" + (number + 1));
+ const duryTomb = getRoom().correcttomb === me.area;
+
+ let obj = Game.getPresetObject(
+ me.area,
+ (!duryTomb ? sdk.objects.SmallSparklyChest : sdk.objects.HoradricStaffHolder)
+ );
+ !!obj && Pather.moveToUnit(obj);
+
+ Attack.clear(50);
+ Pickit.pickItems();
+
+ if (me.duriel && Game.getObject(sdk.objects.PortaltoDurielsLair)) {
+ Pather.useUnit(sdk.unittype.Object, sdk.objects.PortaltoDurielsLair, sdk.areas.DurielsLair);
+ me.sorceress && !me.normal ? Attack.pwnDury() : Attack.killTarget("Duriel");
+ Pickit.pickItems();
+ }
+ }
+
+ Town.goToTown();
+ Town.heal();
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/travincal.js b/libs/SoloPlay/Scripts/travincal.js
index 2c04786d..3ca2dea6 100644
--- a/libs/SoloPlay/Scripts/travincal.js
+++ b/libs/SoloPlay/Scripts/travincal.js
@@ -6,103 +6,104 @@
*/
function travincal () {
- Quest.preReqs();
- Town.doChores(false, { fullChores: true });
- myPrint("starting travincal");
-
- Pather.checkWP(sdk.areas.Travincal, true) ? Pather.useWaypoint(sdk.areas.Travincal) : Pather.getWP(sdk.areas.Travincal);
- Precast.doPrecast(true);
-
- let council = {
- x: me.x + 76,
- y: me.y - 67
- };
-
- Pather.moveToUnit(council);
- Attack.killTarget("Ismail Vilehand");
- Pickit.pickItems();
-
- // go to orb
- if (!Pather.moveToPreset(sdk.areas.Travincal, sdk.unittype.Object, sdk.objects.CompellingOrb)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to compelling orb");
- }
-
- let orb = Game.getObject(sdk.objects.CompellingOrb);
- !!orb && Attack.clearPos(orb.x, orb.y, 15);
-
- // khalim's will quest not complete
- if (!me.travincal) {
- // cleared council didn't pick flail and hasn't already made flail
- if (!me.getItem(sdk.items.quest.KhalimsFlail) && !me.getItem(sdk.items.quest.KhalimsWill)) {
- let flail = Game.getItem(sdk.items.quest.KhalimsFlail);
-
- Pather.moveToUnit(flail);
- Pickit.pickItems();
- Pather.moveToPreset(sdk.areas.Travincal, sdk.unittype.Object, sdk.objects.CompellingOrb);
- }
-
- // cube flail to will
- if (!me.getItem(sdk.items.quest.KhalimsWill) && me.getItem(sdk.items.quest.KhalimsFlail)) {
- Quest.cubeItems(sdk.items.quest.KhalimsWill,
- sdk.quest.item.KhalimsEye, sdk.quest.item.KhalimsHeart, sdk.quest.item.KhalimsBrain, sdk.quest.item.KhalimsFlail);
- delay(250 + me.ping);
- }
-
- // From SoloLeveling Commit eb818af
- if (!me.inTown && me.getItem(sdk.items.quest.KhalimsWill)) {
- Town.goToTown();
- }
-
- Quest.equipItem(sdk.items.quest.KhalimsWill, 4);
- delay(250 + me.ping);
-
- // return to Trav
- if (!Pather.usePortal(sdk.areas.Travincal, me.name)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to go back to Travincal and smash orb");
- }
-
- Quest.smashSomething(sdk.objects.CompellingOrb);
- Item.autoEquip(); // equip previous weapon
- Town.doChores(false, { fullChores: true });
-
- // return to Trav
- if (!Pather.usePortal(sdk.areas.Travincal, me.name)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to go back to Travincal and take entrance");
- Pather.useWaypoint(sdk.areas.Travincal);
- Pather.moveToPreset(sdk.areas.Travincal, sdk.unittype.Object, sdk.objects.CompellingOrb);
- }
-
- // Wait until exit pops open
- Misc.poll(() => Game.getObject(sdk.objects.DuranceEntryStairs).mode === sdk.objects.mode.Active, 10000);
- // Move close to the exit
- let exit_1 = Game.getObject(sdk.objects.DuranceEntryStairs);
- // Since d2 sucks, move around the thingy
- Pather.moveToUnit(exit_1, 7, 7);
- // keep on clicking the exit until we are not @ travincal anymore
- Misc.poll(function () {
- if (me.inArea(sdk.areas.Travincal)) {
- Pather.moveToUnit(exit_1);
- Misc.click(2, 0, exit_1);
- }
- return me.inArea(sdk.areas.DuranceofHateLvl1);
- }, 10000, 40);
- if (!me.inArea(sdk.areas.DuranceofHateLvl1)) {
- Pather.moveToExit([sdk.areas.DuranceofHateLvl1, sdk.areas.DuranceofHateLvl2]);
- } else {
- Pather.journeyTo(sdk.areas.DuranceofHateLvl2);
- }
- Pather.getWP(sdk.areas.DuranceofHateLvl2);
- Pather.useWaypoint(sdk.areas.KurastDocktown);
-
- if (!Pather.moveToExit(sdk.areas.DuranceofHateLvl1, true)) {
- delay(250 + me.ping * 2);
- Pather.moveToExit(sdk.areas.DuranceofHateLvl1, true);
- }
-
- if (!Pather.checkWP(sdk.areas.DuranceofHateLvl2)) {
- Pather.getWP(sdk.areas.DuranceofHateLvl2);
- }
- }
-
- return true;
+ Quest.preReqs();
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting travincal");
+
+ Pather.checkWP(sdk.areas.Travincal, true)
+ ? Pather.useWaypoint(sdk.areas.Travincal)
+ : Pather.getWP(sdk.areas.Travincal);
+ Precast.doPrecast(true);
+
+ const council = {
+ x: me.x + 76,
+ y: me.y - 67
+ };
+
+ Pather.moveToUnit(council);
+ Attack.killTarget("Ismail Vilehand");
+ Pickit.pickItems();
+
+ // go to orb
+ Pather.moveToPreset(sdk.areas.Travincal, sdk.unittype.Object, sdk.objects.CompellingOrb);
+
+ let orb = Game.getObject(sdk.objects.CompellingOrb);
+ !!orb && Attack.clearPos(orb.x, orb.y, 15);
+
+ // khalim's will quest not complete
+ if (!me.travincal) {
+ // cleared council didn't pick flail and hasn't already made flail
+ if (!me.getItem(sdk.items.quest.KhalimsFlail) && !me.getItem(sdk.items.quest.KhalimsWill)) {
+ let flail = Game.getItem(sdk.items.quest.KhalimsFlail);
+
+ Pather.moveToUnit(flail);
+ Pickit.pickItems();
+ Pather.moveToPreset(sdk.areas.Travincal, sdk.unittype.Object, sdk.objects.CompellingOrb);
+ }
+
+ // cube flail to will
+ if (!me.getItem(sdk.items.quest.KhalimsWill) && me.getItem(sdk.items.quest.KhalimsFlail)) {
+ Quest.cubeItems(
+ sdk.items.quest.KhalimsWill,
+ sdk.quest.item.KhalimsEye,
+ sdk.quest.item.KhalimsHeart,
+ sdk.quest.item.KhalimsBrain,
+ sdk.quest.item.KhalimsFlail
+ );
+ delay(250 + me.ping);
+ }
+
+ // From SoloLeveling Commit eb818af
+ if (!me.inTown && me.getItem(sdk.items.quest.KhalimsWill)) {
+ Town.goToTown();
+ }
+
+ Quest.equipItem(sdk.items.quest.KhalimsWill, sdk.body.RightArm);
+ delay(250 + me.ping);
+
+ // return to Trav
+ if (!Pather.usePortal(sdk.areas.Travincal, me.name)) {
+ Pather.moveToPreset(sdk.areas.Travincal, sdk.unittype.Object, sdk.objects.CompellingOrb);
+ }
+
+ Quest.smashSomething(sdk.objects.CompellingOrb);
+ Item.autoEquip(); // equip previous weapon
+ Town.doChores(false, { fullChores: true });
+
+ // return to Trav
+ if (!Pather.usePortal(sdk.areas.Travincal, me.name)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to go back to Travincal and take entrance");
+ Pather.useWaypoint(sdk.areas.Travincal);
+ Pather.moveToPreset(sdk.areas.Travincal, sdk.unittype.Object, sdk.objects.CompellingOrb);
+ }
+
+ // Wait until exit pops open
+ Misc.poll(() => Game.getObject(sdk.objects.DuranceEntryStairs).mode === sdk.objects.mode.Active, 10000);
+ // Move close to the exit
+ let exit_1 = Game.getObject(sdk.objects.DuranceEntryStairs);
+ // Since d2 sucks, move around the thingy
+ Pather.moveToUnit(exit_1, 7, 7);
+ // keep on clicking the exit until we are not @ travincal anymore
+ Misc.poll(function () {
+ if (me.inArea(sdk.areas.Travincal)) {
+ Pather.moveToUnit(exit_1);
+ Misc.click(2, 0, exit_1);
+ }
+ return me.inArea(sdk.areas.DuranceofHateLvl1);
+ }, 10000, 40);
+
+ if (!me.inArea(sdk.areas.DuranceofHateLvl1)) {
+ Pather.moveToExit([sdk.areas.DuranceofHateLvl1, sdk.areas.DuranceofHateLvl2]);
+ } else {
+ Pather.journeyTo(sdk.areas.DuranceofHateLvl2);
+ }
+ Pather.getWP(sdk.areas.DuranceofHateLvl2);
+ Pather.useWaypoint(sdk.areas.KurastDocktown);
+ }
+
+ if (!Pather.checkWP(sdk.areas.DuranceofHateLvl2)) {
+ Pather.getWP(sdk.areas.DuranceofHateLvl2);
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/treehead.js b/libs/SoloPlay/Scripts/treehead.js
index b655f67c..e4c23d2a 100644
--- a/libs/SoloPlay/Scripts/treehead.js
+++ b/libs/SoloPlay/Scripts/treehead.js
@@ -5,22 +5,22 @@
*
*/
-function treehead() {
- Town.doChores();
- Pather.useWaypoint(sdk.areas.DarkWood);
- Precast.doPrecast(true);
+function treehead () {
+ Town.doChores();
+ Pather.useWaypoint(sdk.areas.DarkWood);
+ Precast.doPrecast(true);
- try {
- Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5);
- } catch (e) {
- Attack.clear(5);
- // Try again
- if (Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) {
- return false;
- }
- }
+ try {
+ Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5);
+ } catch (e) {
+ Attack.clear(5);
+ // Try again
+ if (Pather.moveToPreset(me.area, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) {
+ return false;
+ }
+ }
- Attack.killTarget(getLocaleString(sdk.locale.monsters.TreeheadWoodFist));
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.TreeheadWoodFist));
- return true;
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/tristram.js b/libs/SoloPlay/Scripts/tristram.js
index dcb3abb8..e37e895b 100644
--- a/libs/SoloPlay/Scripts/tristram.js
+++ b/libs/SoloPlay/Scripts/tristram.js
@@ -7,129 +7,143 @@
*/
function tristram () {
- let spots = [
- [25176, 5128], [25175, 5145], [25171, 5159], [25166, 5178],
- [25173, 5192], [25153, 5198], [25136, 5189], [25127, 5167],
- [25120, 5148], [25101, 5136], [25119, 5106], [25121, 5080],
- [25119, 5061], [4933, 4363]
- ];
-
- Town.doChores(false, { fullChores: true });
- myPrint("starting tristram");
-
- // Tristram portal hasn't been opened
- if (!Misc.checkQuest(sdk.quest.id.TheSearchForCain, 4)) {
- const getScroll = () => {
- if (me.getItem(sdk.quest.item.ScrollofInifuss) || me.getItem(sdk.quest.item.KeytotheCairnStones)) return true;
- Precast.doPrecast(true);
- if (!Pather.moveToPreset(sdk.areas.DarkWood, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) {
- console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Tree of Inifuss");
- return false;
- }
-
- Quest.collectItem(sdk.quest.item.ScrollofInifuss, sdk.quest.chest.InifussTree);
- Pickit.pickItems();
- return (me.getItem(sdk.quest.item.ScrollofInifuss || me.getItem(sdk.quest.item.KeytotheCairnStones)));
- };
- // missing scroll and key
- if (!me.getItem(sdk.items.quest.ScrollofInifuss) && !me.getItem(sdk.items.quest.KeytotheCairnStones)) {
- if (!Pather.checkWP(sdk.areas.BlackMarsh, true)) {
- Pather.useWaypoint(sdk.areas.DarkWood);
- getScroll();
- Pather.getWP(sdk.areas.BlackMarsh);
- } else {
- Pather.useWaypoint(sdk.areas.DarkWood);
- getScroll();
- }
- }
-
- if (me.getItem(sdk.items.quest.ScrollofInifuss)) {
- !me.inTown && Town.goToTown();
- Town.npcInteract("akara");
- }
- }
-
- Pather.checkWP(sdk.areas.StonyField, true) ? Pather.useWaypoint(sdk.areas.StonyField) : Pather.getWP(sdk.areas.StonyField);
- Precast.doPrecast(true);
- Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Monster, sdk.monsters.preset.Rakanishu, 10, 10, false, true);
- Attack.killTarget(getLocaleString(sdk.locale.monsters.Rakanishu));
- Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Object, sdk.quest.chest.StoneAlpha, null, null, true);
-
- if (!Misc.checkQuest(sdk.quest.id.TheSearchForCain, 4) && me.getItem(sdk.items.quest.KeytotheCairnStones)) {
- try {
- const stoneIds = [
- sdk.quest.chest.StoneAlpha, sdk.quest.chest.StoneBeta, sdk.quest.chest.StoneGamma,
- sdk.quest.chest.StoneDelta, sdk.quest.chest.StoneLambda
- ];
- const getStones = () => getUnits(sdk.unittype.Object).filter(s => stoneIds.includes(s.classid) && !s.mode);
- let stones = getStones();
- let sTick = getTickCount();
- let retry = true;
-
- while (stones.some((stone) => !stone.mode)) {
- for (let i = 0; i < stones.length; i++) {
- let stone = stones[i];
-
- if (Common.Questing.activateStone(stone)) {
- stones.splice(i, 1);
- i--;
- }
-
- if (getTickCount() - sTick < Time.minutes(2)) {
- if (retry) {
- stones = getStones();
- sTick = getTickCount();
- } else {
- return false;
- }
- }
- Attack.securePosition(me.x, me.y, 10, 0);
- delay(10);
- }
- }
-
- let tick = getTickCount();
- // wait up to two minutes
- while (getTickCount() - tick < 60 * 1000 * 2) {
- if (Pather.usePortal(sdk.areas.Tristram)) {
- break;
- }
- Attack.securePosition(me.x, me.y, 10, 1000);
- }
- } catch (err) {
- console.error(err);
- Pather.usePortal(sdk.areas.Tristram);
- }
- } else {
- Pather.usePortal(sdk.areas.Tristram);
- }
-
- if (me.inArea(sdk.areas.Tristram)) {
- if (!me.tristram) {
- let clearCoords = [
- {"x": 25166, "y": 5108, "radius": 10},
- {"x": 25164, "y": 5115, "radius": 10},
- {"x": 25163, "y": 5121, "radius": 10},
- {"x": 25158, "y": 5126, "radius": 10},
- {"x": 25151, "y": 5125, "radius": 10},
- {"x": 25145, "y": 5129, "radius": 10},
- {"x": 25142, "y": 5135, "radius": 10}
- ];
- Attack.clearCoordList(clearCoords);
-
- let gibbet = Game.getObject(sdk.quest.chest.CainsJail);
-
- if (gibbet && !gibbet.mode) {
- Pather.moveTo(gibbet.x, gibbet.y);
- Misc.openChest(gibbet);
- }
-
- Town.npcInteract("akara");
- Pather.usePortal(sdk.areas.Tristram, me.name);
- }
-
- Attack.clearLocations(spots);
- }
-
- return true;
+ let spots = [
+ [25176, 5128], [25175, 5145], [25171, 5159], [25166, 5178],
+ [25173, 5192], [25153, 5198], [25136, 5189], [25127, 5167],
+ [25120, 5148], [25101, 5136], [25119, 5106], [25121, 5080],
+ [25119, 5061], [4933, 4363]
+ ];
+
+ Town.doChores(false, { fullChores: true });
+ myPrint("starting tristram");
+
+ // Tristram portal hasn't been opened
+ if (!Misc.checkQuest(sdk.quest.id.TheSearchForCain, 4)) {
+ const getScroll = () => {
+ if (me.getItem(sdk.quest.item.ScrollofInifuss) || me.getItem(sdk.quest.item.KeytotheCairnStones)) return true;
+ Precast.doPrecast(true);
+ if (!Pather.moveToPreset(sdk.areas.DarkWood, sdk.unittype.Object, sdk.quest.chest.InifussTree, 5, 5)) {
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Failed to move to Tree of Inifuss");
+ return false;
+ }
+
+ Quest.collectItem(sdk.quest.item.ScrollofInifuss, sdk.quest.chest.InifussTree);
+ Pickit.pickItems();
+ return (me.getItem(sdk.quest.item.ScrollofInifuss || me.getItem(sdk.quest.item.KeytotheCairnStones)));
+ };
+ // missing scroll and key
+ if (!me.getItem(sdk.items.quest.ScrollofInifuss) && !me.getItem(sdk.items.quest.KeytotheCairnStones)) {
+ if (!Pather.checkWP(sdk.areas.BlackMarsh, true)) {
+ Pather.useWaypoint(sdk.areas.DarkWood);
+ getScroll();
+ Pather.getWP(sdk.areas.BlackMarsh);
+ } else {
+ Pather.useWaypoint(sdk.areas.DarkWood);
+ getScroll();
+ }
+ }
+
+ if (me.getItem(sdk.items.quest.ScrollofInifuss)) {
+ !me.inTown && Town.goToTown();
+ Town.npcInteract("akara");
+ }
+ }
+
+ Pather.checkWP(sdk.areas.StonyField, true)
+ ? Pather.useWaypoint(sdk.areas.StonyField)
+ : Pather.getWP(sdk.areas.StonyField);
+ Precast.doPrecast(true);
+ Pather.moveToPresetMonster(sdk.areas.StonyField, sdk.monsters.preset.Rakanishu, { callback: () => {
+ let rak = Game.getMonster(getLocaleString(sdk.locale.monsters.Rakanishu));
+ return rak && (rak.dead || rak.distance < 20);
+ }, offX: 10, offY: 10 });
+ Attack.killTarget(getLocaleString(sdk.locale.monsters.Rakanishu));
+ Pather.moveToPreset(sdk.areas.StonyField, sdk.unittype.Object, sdk.quest.chest.StoneAlpha, null, null, true);
+
+ if (!Misc.checkQuest(sdk.quest.id.TheSearchForCain, 4) && me.getItem(sdk.items.quest.KeytotheCairnStones)) {
+ try {
+ /**
+ * @todo I know there is a way to read the correct stone order from the packet response, need to figure that out
+ */
+ include("core/Common/Cain.js");
+
+ const stoneIds = [
+ sdk.quest.chest.StoneAlpha, sdk.quest.chest.StoneBeta, sdk.quest.chest.StoneGamma,
+ sdk.quest.chest.StoneDelta, sdk.quest.chest.StoneLambda
+ ];
+ const getStones = () => getUnits(sdk.unittype.Object).filter(s => stoneIds.includes(s.classid) && !s.mode);
+ let stones = getStones();
+ let sTick = getTickCount();
+ let retry = true;
+
+ while (stones.some((stone) => !stone.mode)) {
+ for (let i = 0; i < stones.length; i++) {
+ let stone = stones[i];
+
+ if (Common.Cain.activateStone(stone)) {
+ stones.splice(i, 1);
+ i--;
+ }
+
+ if (getTickCount() - sTick < Time.minutes(2)) {
+ if (retry) {
+ stones = getStones();
+ sTick = getTickCount();
+ } else {
+ return false;
+ }
+ }
+ Attack.securePosition(me.x, me.y, 10, 0);
+ delay(10);
+ }
+ }
+
+ let tick = getTickCount();
+ // wait up to two minutes
+ while (getTickCount() - tick < 60 * 1000 * 2) {
+ if (Pather.usePortal(sdk.areas.Tristram)) {
+ break;
+ }
+ Attack.securePosition(me.x, me.y, 10, 1000);
+ }
+ } catch (err) {
+ console.error(err);
+ Pather.usePortal(sdk.areas.Tristram);
+ }
+ } else {
+ Pather.usePortal(sdk.areas.Tristram);
+ }
+
+ if (me.inArea(sdk.areas.Tristram)) {
+ if (!me.tristram) {
+ let clearCoords = [
+ { "x": 25166, "y": 5108, "radius": 10 },
+ { "x": 25164, "y": 5115, "radius": 10 },
+ { "x": 25163, "y": 5121, "radius": 10 },
+ { "x": 25158, "y": 5126, "radius": 10 },
+ { "x": 25151, "y": 5125, "radius": 10 },
+ { "x": 25145, "y": 5129, "radius": 10 },
+ { "x": 25142, "y": 5135, "radius": 10 }
+ ];
+ Attack.clearCoordList(clearCoords);
+
+ let gibbet = Game.getObject(sdk.quest.chest.CainsJail);
+
+ if (gibbet && !gibbet.mode) {
+ Pather.moveTo(gibbet.x, gibbet.y);
+ Misc.openChest(gibbet);
+ }
+
+ Town.npcInteract("akara");
+ Pather.usePortal(sdk.areas.Tristram, me.name);
+ }
+
+ Attack.clearLocations(spots);
+ }
+
+ if (me.cain) {
+ delete Common.Cain;
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Scripts/worldstone.js b/libs/SoloPlay/Scripts/worldstone.js
index 420524b5..40d564c2 100644
--- a/libs/SoloPlay/Scripts/worldstone.js
+++ b/libs/SoloPlay/Scripts/worldstone.js
@@ -5,16 +5,38 @@
*
*/
-function worldstone() {
- myPrint("starting worldstone");
+function worldstone () {
+ myPrint("starting worldstone");
- Town.doChores(null, {thawing: me.coldRes < 75, antidote: me.poisonRes < 75});
+ Town.doChores(null, { thawing: me.coldRes < 75, antidote: me.poisonRes < 75 });
- Pather.useWaypoint(sdk.areas.WorldstoneLvl2);
- Precast.doPrecast(true);
- Attack.clearLevel(Config.ClearType);
- Pather.moveToExit(sdk.areas.WorldstoneLvl1, true) && Attack.clearLevel(Config.ClearType);
- Pather.moveToExit([sdk.areas.WorldstoneLvl2, sdk.areas.WorldstoneLvl3], true) && Attack.clearLevel(Config.ClearType);
+ 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);
- return true;
+ 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/libs/SoloPlay/SoloPlay.js b/libs/SoloPlay/SoloPlay.js
index b3a8f71a..d0db9761 100644
--- a/libs/SoloPlay/SoloPlay.js
+++ b/libs/SoloPlay/SoloPlay.js
@@ -5,28 +5,15 @@
*
*/
js_strict(true);
+include("critical.js");
+
+// globals needed for core gameplay
+includeCoreLibs({ exclude: ["Storage.js"] });
+
+// system libs
+includeSystemLibs();
+include("systems/mulelogger/MuleLogger.js");
-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/Attack.js");
-include("common/Common.js");
-include("common/Cubing.js");
-include("common/CollMap.js");
-include("common/Config.js");
-include("common/misc.js");
-include("common/util.js");
-include("common/Pickit.js");
-include("common/Pather.js");
-include("common/Precast.js");
-include("common/Prototypes.js");
-include("common/Runewords.js");
-include("common/Town.js");
// Include SoloPlay's librarys
include("SoloPlay/Tools/Developer.js");
include("SoloPlay/Tools/Tracker.js");
@@ -35,318 +22,350 @@ include("SoloPlay/Tools/SoloIndex.js");
include("SoloPlay/Functions/ConfigOverrides.js");
include("SoloPlay/Functions/Globals.js");
-// @todo
-// call loader from here and change loader to use the soloplay script files
-// todo - global skip gid array
+// main thread specific
+const LocalChat = require("../modules/LocalChat", null, false);
+
+/**
+ * @todo
+ * - Add priority to runewords/cubing
+ * - proper script skipping + gold runs when needed
+ * - fix autoequip issues with rings and dual wielding
+ * - remove all the logic from main so all it does is call functions
+ */
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");
-
- SetUp.include();
- SetUp.init();
-
- let sojCounter = 0;
- let sojPause = false;
- let startTime = getTickCount();
-
- this.scriptEvent = function (msg) {
- let obj;
-
- if (msg && typeof msg === "string" && msg !== "") {
- switch (true) {
- case msg === "soj":
- sojPause = true;
- sojCounter = 0;
-
- break;
- case msg.substring(0, 8) === "config--":
- console.debug("update config");
- Config = JSON.parse(msg.split("config--")[1]);
-
- break;
- case msg.substring(0, 7) === "skill--":
- console.debug("update skillData");
- obj = JSON.parse(msg.split("skill--")[1]);
- Misc.updateRecursively(CharData.skillData, obj);
-
- break;
- case msg.substring(0, 6) === "data--":
- console.debug("update myData");
- obj = JSON.parse(msg.split("data--")[1]);
- Misc.updateRecursively(myData, obj);
-
- break;
- case msg.toLowerCase() === "test":
- console.debug(sdk.colors.Green + "//-----------DataDump Start-----------//\nÿc8MainData ::\n", getScript(true).name,
- myData, "\nÿc8BuffData ::\n", CharData.buffData, "\nÿc8SkillData ::\n", CharData.skillData, "\n" + sdk.colors.Red + "//-----------DataDump End-----------//");
-
- break;
- }
- }
- };
-
- 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.");
- }
- }
- };
-
- this.setup = function () {
- myPrint("start setup");
- NTIP.arrayLooping(nipItems.Quest, nipItems.General);
-
- try {
- if (impossibleClassicBuilds.includes(SetUp.finalBuild) && me.classic) {
- throw new Error("Kolbot-SoloPlay: " + SetUp.finalBuild + " cannot be used in classic. Change the info tag or remake as an expansion character...Shutting down");
- }
-
- if (impossibleNonLadderBuilds.includes(SetUp.finalBuild) && !Developer.addLadderRW) {
- throw new Error("Kolbot-SoloPlay: " + SetUp.finalBuild + " cannot be used in non-ladder as they require ladder runewords. Change the info tag or remake as an ladder character...Shutting down");
- }
- } catch (e) {
- D2Bot.printToConsole(e, sdk.colors.D2Bot.Red);
- FileTools.remove("data/" + me.profile + ".json");
- FileTools.remove("libs/SoloPlay/Data/" + me.profile + ".GameTime" + ".json");
- D2Bot.stop();
- }
-
- if (me.charlvl === 1) {
- let buckler = me.getItem(sdk.items.Buckler);
- !!buckler && buckler.isEquipped && buckler.drop();
- }
-
- Town.heal() && me.cancelUIFlags();
- Check.checkSpecialCase();
-
- // check if any of our currently equipped items are no longer usable - can happen after respec
- me.getItemsEx()
- .filter(item => item.isEquipped)
- .forEach(item => {
- if (me.getStat(sdk.stats.Strength) < item.strreq
- || me.getStat(sdk.stats.Dexterity) < item.dexreq
- || item.ethereal && item.isBroken) {
- myPrint("No longer able to use: " + item.fname);
- Item.removeItem(null, item);
- } else if (sdk.quest.items.includes(item.classid)) {
- myPrint("Removing Quest Item: " + item.fname);
- Item.removeItem(null, item);
- }
- });
-
- me.getItemsEx()
- .filter(item => item.isInInventory && sdk.quest.items.includes(item.classid))
- .forEach(item => {
- Quest.stashItem(item);
- });
-
- me.cancelUIFlags();
- // initialize final charms if we have any
- Item.initCharms();
-
- return true;
- };
-
- // 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);
-
- // AutoMule/TorchSystem/Gambling/Crafting handler
- if (AutoMule.inGameCheck() || TorchSystem.inGameCheck() || Gambling.inGameCheck() || CraftingSystem.inGameCheck()) {
- return true;
- }
-
- me.maxgametime = Time.seconds(Config.MaxGameTime);
- const 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.lastScript, 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
- if (SoloEvents.inGameCheck()) return true;
- load("libs/SoloPlay/Threads/ToolsThread.js");
- load("libs/SoloPlay/Threads/EventThread.js");
- load("libs/SoloPlay/Threads/TownChicken.js");
-
- // Load guard if we want to see the stack as it runs
- if (Developer.debugging.showStack.enabled) {
- // check in case we reloaded and guard was still running
- let guard = getScript("libs/SoloPlay/Modules/Guard.js");
- !!guard && guard.running && guard.stop();
- Developer.debugging.showStack.profiles.some(profile => profile.toLowerCase() === "all" || profile.toLowerCase() === me.profile.toLowerCase()) && require("../SoloPlay/Modules/Guard");
- delay(1000);
- }
-
- if (Config.PublicMode) {
- Config.PublicMode === true ? require("libs/modules/SimpleParty") : load("tools/Party.js");
- }
-
- // Config.AntiHostile && load("tools/AntiHostile.js");
-
- // One time maintenance - check cursor, get corpse, clear leftover items, pick items in case anything important was dropped
- 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();
-
- // Start Running Script
- this.setup();
-
- // Start Developer mode - this stops the script from progressing past this point and allows running specific scripts/functions through chat commands
- if (Developer.developerMode.enabled) {
- if (Developer.developerMode.profiles.some(profile => profile.toLowerCase() === me.profile.toLowerCase())) {
- Developer.debugging.pathing && (me.automap = true);
- Loader.runScript("developermode");
- }
- }
-
- if (Check.brokeCheck()) return true;
- Check.usePreviousSocketQuest(); // Currently only supports going back to nightmare to socket a lidless if one is equipped.
- myPrint("starting run");
- Loader.run();
- // we have scripts to retry so lets run them
- if (SoloIndex.retryList.length) {
- SoloIndex.scripts = SoloIndex.retryList.slice(0);
- Loader.run();
- }
-
- if (Config.MinGameTime && getTickCount() - startTime < Time.seconds(Config.MinGameTime)) {
- try {
- Town.goToTown();
-
- while (getTickCount() - startTime < Time.seconds(Config.MinGameTime)) {
- me.overhead("Stalling for " + Math.round(((startTime + Time.seconds(Config.MinGameTime)) - getTickCount()) / 1000) + " Seconds");
- delay(1000);
- }
- } catch (e1) {
- console.error(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) {
- console.error(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;
- }
- }
-
- 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");
- }
-
- scriptBroadcast("quit");
-
- return true;
+ D2Bot.init(); // Get D2Bot# handle
+ D2Bot.ingame();
+
+ (function (global, original) {
+ global.load = function (...args) {
+ original.apply(this, args);
+ delay(500);
+ };
+ })([].filter.constructor("return this")(), load);
+
+ /**
+ * Fixes d2bs bug where this returns the "function"
+ */
+ (function (original) {
+ me.move = function (...args) {
+ original.apply(this, args);
+ return true;
+ };
+ })(me.move);
+
+ // 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("threads/heartbeat.js") && load("threads/heartbeat.js");
+
+ SetUp.include();
+ SetUp.init();
+
+ let sojCounter = 0;
+ let sojPause = false;
+ let startTime = getTickCount();
+
+ /**
+ * Handle script/thread communications
+ * @param {string} msg
+ * @returns {void}
+ */
+ const scriptEvent = function (msg) {
+ if (!msg || typeof msg !== "string") return;
+ let obj;
+
+ if (msg.includes("--")) {
+ let sub = msg.match(/\w+?--/gm).first();
+
+ switch (sub) {
+ case "config--":
+ console.debug("update config");
+ Config = JSON.parse(msg.split("config--")[1]);
+
+ return;
+ case "skill--":
+ console.debug("update skillData");
+ obj = JSON.parse(msg.split("skill--")[1]);
+ Misc.updateRecursively(CharData.skillData, obj);
+
+ return;
+ case "data--":
+ console.debug("update me.data");
+ obj = JSON.parse(msg.split("data--")[1]);
+ Misc.updateRecursively(me.data, obj);
+
+ return;
+ }
+ }
+
+ switch (msg) {
+ case "testing":
+ case "finishDen":
+ case "dodge":
+ case "skip":
+ case "killdclone":
+ me.emit("soloEvent", msg);
+
+ break;
+ case "addDiaEvent":
+ console.log("Added dia lightning listener");
+ addEventListener("gamepacket", SoloEvents.diaEvent);
+
+ break;
+ case "removeDiaEvent":
+ console.log("Removed dia lightning listener");
+ removeEventListener("gamepacket", SoloEvents.diaEvent);
+
+ break;
+ case "addBaalEvent":
+ console.log("Added baal wave listener");
+ addEventListener("gamepacket", SoloEvents.baalEvent);
+
+ break;
+ case "removeBaalEvent":
+ console.log("Removed baal wave listener");
+ removeEventListener("gamepacket", SoloEvents.baalEvent);
+
+ break;
+ case "nextScript":
+ // testing - works so maybe can handle other events as well?
+ me.emit("nextScript");
+
+ break;
+ case "soj":
+ sojPause = true;
+ sojCounter = 0;
+
+ break;
+ case "test":
+ {
+ console.debug(sdk.colors.Green + "//-----------DataDump Start-----------//",
+ "\nÿc8ThreadData ::\n", getScript(true),
+ "\nÿc8MainData ::\n", me.data,
+ "\nÿc8BuffData ::\n", CharData.pots,
+ "\nÿc8SkillData ::\n", CharData.skillData,
+ "\nÿc8GlobalVariabls ::\n", Object.keys(global),
+ "\n" + sdk.colors.Red + "//-----------DataDump End-----------//");
+ }
+ break;
+ }
+ };
+
+ const 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.");
+ }
+ } else if (mode === 70) {
+ Messaging.sendToScript("D2BotSoloPlay.dbj", "event");
+ delay(100 + me.ping);
+ scriptBroadcast("quit");
+ } else if ([55, 60, 65].includes(mode)) {
+ // torch/anni sharing event - does this even still work? Haven't tested in awhile
+ me.emit("processProfileEvent", mode, msg);
+ }
+ };
+
+ // 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", scriptEvent);
+ addEventListener("copydata", copyDataEvent);
+
+ // AutoMule/TorchSystem/Gambling/Crafting handler
+ if (AutoMule.inGameCheck()
+ || TorchSystem.inGameCheck()
+ || Gambling.inGameCheck()
+ || CraftingSystem.inGameCheck()
+ || SoloEvents.inGameCheck()) {
+ return true;
+ }
+
+ me.maxgametime = Time.seconds(Config.MaxGameTime);
+ const 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.lastScript + "\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("libs/SoloPlay/Threads/ToolsThread.js");
+
+ require("./Workers/EventEmitter");
+ require("./Workers/EventHandler");
+ require("./Workers/TownChicken");
+ SoloEvents.filePath = "libs/SoloPlay/SoloPlay.js"; // hacky for now, don't want to mess up others running so we just broadcast to ourselves
+
+ // Load guard if we want to see the stack as it runs
+ if (Developer.debugging.showStack.enabled) {
+ // check in case we reloaded and guard was still running
+ let guard = getScript("libs/SoloPlay/Modules/Guard.js");
+ !!guard && guard.running && guard.stop();
+ Developer.debugging.showStack.profiles
+ .some(prof => String.isEqual(prof, me.profile) || String.isEqual(prof, "all"))
+ && require("../SoloPlay/Modules/Guard");
+ delay(1000);
+ }
+
+ if (Config.PublicMode) {
+ Config.PublicMode === true ? require("libs/modules/SimpleParty") : load("threads/Party.js");
+ }
+
+ // One time maintenance - check cursor, get corpse, clear leftover items, pick items in case anything important was dropped
+ 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();
+
+ me.automap = Config.AutoMap;
+
+ // Next game = drop keys
+ TorchSystem.keyCheck() && scriptBroadcast("torch");
+
+ // Auto skill and stat
+ if (Config.AutoSkill.Enabled && include("core/Auto/AutoSkill.js")) {
+ AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
+ }
+
+ if (Config.AutoStat.Enabled && include("core/Auto/AutoStat.js")) {
+ AutoStat.init(Config.AutoStat.Build, Config.AutoStat.Save, Config.AutoStat.BlockChance, Config.AutoStat.UseBulk);
+ }
+
+ // offline - ensure we didn't just reload the thread and are still in the same game
+ if (!me.realm && getTickCount() - me.gamestarttime < Time.minutes(1)) {
+ D2Bot.updateRuns();
+ }
+
+ // Start Running Script
+ includeIfNotIncluded("SoloPlay/Utils/Init.js");
+
+ // log threads - track memory use
+ if (Config.DebugMode.Memory) {
+ console.log("//~~~~~~~Current Threads~~~~~~~//");
+ getThreads()
+ .sort((a, b) => b.memory - a.memory)
+ .forEach(t => console.log(t));
+ console.log("//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//");
+ }
+
+ // Start Developer mode - this stops the script from progressing past this point and allows running specific scripts/functions through chat commands
+ if (Developer.developerMode.enabled) {
+ if (Developer.developerMode.profiles.some(prof => String.isEqual(prof, me.profile))) {
+ Developer.debugging.pathing && (me.automap = true);
+ Loader.runScript("developermode");
+ }
+ }
+
+ if (Check.brokeCheck()) return true;
+ if (Check.usePreviousSocketQuest()) return true; // Currently only supports going back to nightmare to socket a lidless if one is equipped.
+ myPrint("starting run");
+ Loader.run();
+ // we have scripts to retry so lets run them
+ if (SoloIndex.retryList.length) {
+ SoloIndex.scripts = SoloIndex.retryList.slice(0);
+ Loader.run();
+ }
+
+ if (Config.MinGameTime && getTickCount() - startTime < Time.seconds(Config.MinGameTime)) {
+ try {
+ Town.goToTown();
+
+ while (getTickCount() - startTime < Time.seconds(Config.MinGameTime)) {
+ me.overhead(
+ "Stalling for "
+ + Math.round(((startTime + Time.seconds(Config.MinGameTime)) - getTickCount()) / 1000) + " Seconds"
+ );
+ delay(1000);
+ }
+ } catch (e1) {
+ console.error(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) {
+ console.error(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;
+ }
+ }
+
+ 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");
+ }
+
+ scriptBroadcast("quit");
+
+ return true;
}
diff --git a/libs/SoloPlay/Threads/AutoBuildThread.js b/libs/SoloPlay/Threads/AutoBuildThread.js
deleted file mode 100644
index 035feede..00000000
--- a/libs/SoloPlay/Threads/AutoBuildThread.js
+++ /dev/null
@@ -1,296 +0,0 @@
-/**
-* @filename AutoBuildThread.js
-* @author alogwe, theBGuy
-* @desc modified AutoBuildThread for use with Kolbot-SoloPlay
-*
-*/
-js_strict(true);
-
-include("json2.js");
-include("NTItemParser.dbl");
-include("OOG.js");
-include("common/util.js");
-include("AutoMule.js");
-include("Gambling.js");
-include("CraftingSystem.js");
-include("TorchSystem.js");
-include("MuleLogger.js");
-include("common/AutoSkill.js");
-include("common/AutoStat.js");
-include("common/Attack.js");
-include("common/Common.js");
-include("common/Cubing.js");
-include("common/CollMap.js");
-include("common/Config.js");
-include("common/misc.js");
-include("common/Pickit.js");
-include("common/Pather.js");
-include("common/Precast.js");
-include("common/Prototypes.js");
-include("common/Runewords.js");
-include("common/Town.js");
-// Include SoloPlay's librarys
-include("SoloPlay/Tools/Developer.js");
-include("SoloPlay/Tools/Tracker.js");
-include("SoloPlay/Tools/CharData.js");
-include("SoloPlay/Tools/SoloIndex.js");
-include("SoloPlay/Functions/ConfigOverrides.js");
-include("SoloPlay/Functions/Globals.js");
-
-SetUp.include();
-Config.init(); // includes libs/SoloPlay/Functions/AutoBuildOverrides.js
-
-let debug = !!Config.AutoBuild.DebugMode, prevLevel = me.charlvl;
-const usingFinalBuild = !["Start", "Stepping", "Leveling"].includes(Config.AutoBuild.Template);
-const SPEND_POINTS = true; // For testing, it actually allows skill and stat point spending.
-const STAT_ID_TO_NAME = [
- getLocaleString(sdk.locale.text.Strength),
- getLocaleString(sdk.locale.text.Energy),
- getLocaleString(sdk.locale.text.Dexterity),
- getLocaleString(sdk.locale.text.Vitality)
-];
-let currAutoBuild;
-
-// Will check if value exists in an Array
-Array.prototype.contains = (val) => this.indexOf(val) > -1;
-
-function skillInValidRange (id) {
- switch (me.classid) {
- case sdk.player.class.Amazon:
- return sdk.skills.MagicArrow <= id && id <= sdk.skills.LightningFury;
- case sdk.player.class.Sorceress:
- return sdk.skills.FireBolt <= id && id <= sdk.skills.ColdMastery;
- case sdk.player.class.Necromancer:
- return sdk.skills.AmplifyDamage <= id && id <= sdk.skills.Revive;
- case sdk.player.class.Paladin:
- return sdk.skills.Sacrifice <= id && id <= sdk.skills.Salvation;
- case sdk.player.class.Barbarian:
- return sdk.skills.Bash <= id && id <= sdk.skills.BattleCommand;
- case sdk.player.class.Druid:
- return sdk.skills.Raven <= id && id <= sdk.skills.Hurricane;
- case sdk.player.class.Assassin:
- return sdk.skills.FireBlast <= id && id <= sdk.skills.PhoenixStrike;
- default:
- return false;
- }
-}
-
-const gainedLevels = () => me.charlvl - prevLevel;
-
-function canSpendPoints () {
- let unusedStatPoints = me.getStat(sdk.stats.StatPts);
- let haveUnusedStatpoints = unusedStatPoints >= 5; // We spend 5 stat points per level up
- let unusedSkillPoints = me.getStat(sdk.stats.NewSkills);
- let haveUnusedSkillpoints = unusedSkillPoints >= 1; // We spend 1 skill point per level up
- debug && AutoBuild.print("Stat points:", unusedStatPoints, " Skill points:", unusedSkillPoints);
- return haveUnusedStatpoints && haveUnusedSkillpoints;
-}
-
-function spendStatPoint (id) {
- let unusedStatPoints = me.getStat(sdk.stats.StatPts);
- if (SPEND_POINTS) {
- useStatPoint(id);
- AutoBuild.print("useStatPoint(" + id + "): " + STAT_ID_TO_NAME[id]);
- } else {
- AutoBuild.print("Fake useStatPoint(" + id + "): " + STAT_ID_TO_NAME[id]);
- }
- delay(100); // TODO: How long should we wait... if at all?
- return (unusedStatPoints - me.getStat(sdk.stats.StatPts) === 1); // Check if we spent one point
-}
-
-// TODO: What do we do if it fails? report/ignore/continue?
-function spendStatPoints () {
- if (currAutoBuild[me.charlvl] === undefined) return true;
- let stats = currAutoBuild[me.charlvl].StatPoints;
- let errorMessage = "\nInvalid stat point set in build template " + getTemplateFilename() + " at level " + me.charlvl;
- let spentEveryPoint = true;
- let unusedStatPoints = me.getStat(sdk.stats.StatPts);
- let len = stats.length;
-
- if (Config.AutoStat.Enabled) {
- return spentEveryPoint;
- }
-
- if (len > unusedStatPoints) {
- len = unusedStatPoints;
- AutoBuild.print("Warning: Number of stats specified in your build template at level " + me.charlvl + " exceeds the available unused stat points" +
- "\nOnly the first " + len + " stats " + stats.slice(0, len).join(", ") + " will be added");
- }
-
- // We silently ignore stats set to -1
- for (let i = 0; i < len; i++) {
- let id = stats[i];
- let statIsValid = (typeof id === "number") && (0 <= id && id <= 3);
-
- if (id === -1) {
- continue;
- } else if (statIsValid) {
- let preStatValue = me.getStat(id);
- let pointSpent = spendStatPoint(id);
- if (SPEND_POINTS) {
- if (!pointSpent) {
- spentEveryPoint = false;
- AutoBuild.print("Attempt to spend point " + (i + 1) + " in " + STAT_ID_TO_NAME[id] + " may have failed!");
- } else if (debug) {
- AutoBuild.print("Stat (" + (i + 1) + "/" + len + ") Increased " + STAT_ID_TO_NAME[id] + " from " + preStatValue + " to " + me.getStat(id));
- }
- }
- } else {
- throw new Error("Stat id must be one of the following:\n0:" + STAT_ID_TO_NAME[0] +
- ",\t1:" + STAT_ID_TO_NAME[1] + ",\t2:" + STAT_ID_TO_NAME[2] + ",\t3:" + STAT_ID_TO_NAME[3] + errorMessage);
- }
- }
-
- return spentEveryPoint;
-}
-
-function getTemplateFilename () {
- let buildType = Config.AutoBuild.Template;
- let className = sdk.player.class.nameOf(me.classid);
- let templateFilename = "SoloPlay/BuildFiles/" + className + "/" + className + "." + buildType + "Build.js";
- return templateFilename;
-}
-
-function getRequiredSkills (id) {
- function searchSkillTree (id) {
- let results = [];
- let skillTreeRight = getBaseStat("skills", id, sdk.stats.PreviousSkillRight);
- let skillTreeMiddle = getBaseStat("skills", id, sdk.stats.PreviousSkillMiddle);
- let skillTreeLeft = getBaseStat("skills", id, sdk.stats.PreviousSkillLeft);
-
- results.push(skillTreeRight);
- results.push(skillTreeMiddle);
- results.push(skillTreeLeft);
-
- for (let i = 0; i < results.length; i++) {
- let skill = results[i];
- let skillInValidRange = (sdk.skills.Attack < skill && skill <= sdk.skills.PhoenixStrike) && (![sdk.skills.IdentifyScroll, sdk.skills.BookofIdentify, sdk.skills.TownPortalScroll, sdk.skills.BookofTownPortal].contains(skill));
- let hardPointsInSkill = me.getSkill(skill, sdk.skills.subindex.HardPoints);
-
- if (skillInValidRange && !hardPointsInSkill) {
- requirements.push(skill);
- searchSkillTree(skill); // search children;
- }
- }
- }
-
- let requirements = [];
- searchSkillTree(id);
- const increasing = () => a - b;
- return requirements.sort(increasing);
-}
-
-function spendSkillPoint (id) {
- let unusedSkillPoints = me.getStat(sdk.stats.NewSkills);
- let skillName = getSkillById(id) + " (" + id + ")";
- if (SPEND_POINTS) {
- useSkillPoint(id);
- AutoBuild.print("useSkillPoint(): " + skillName);
- } else {
- AutoBuild.print("Fake useSkillPoint(): " + skillName);
- }
- delay(200); // TODO: How long should we wait... if at all?
- return (unusedSkillPoints - me.getStat(sdk.stats.NewSkills) === 1); // Check if we spent one point
-}
-
-function spendSkillPoints () {
- if (currAutoBuild[me.charlvl] === undefined) return true;
- let skills = currAutoBuild[me.charlvl].SkillPoints;
- let errInvalidSkill = "\nInvalid skill point set in build template " + getTemplateFilename() + " for level " + me.charlvl;
- let spentEveryPoint = true;
- let unusedSkillPoints = me.getStat(sdk.stats.NewSkills);
- let len = skills.length;
-
- if (Config.AutoSkill.Enabled) {
- return spentEveryPoint;
- }
-
- if (len > unusedSkillPoints) {
- len = unusedSkillPoints;
- AutoBuild.print("Warning: Number of skills specified in your build template at level " + me.charlvl + " exceeds the available unused skill points" +
- "\nOnly the first " + len + " skills " + skills.slice(0, len).join(", ") + " will be added");
- }
-
- // We silently ignore skills set to -1
- for (let i = 0; i < len; i++) {
- let id = skills[i];
-
- if (id === -1) {
- continue;
- } else if (!skillInValidRange(id)) {
- throw new Error("Skill id " + id + " is not a skill for your character class" + errInvalidSkill);
- }
-
- let skillName = getSkillById(id) + " (" + id + ")";
- let requiredSkills = getRequiredSkills(id);
- if (requiredSkills.length > 0) {
- throw new Error("You need prerequisite skills " + requiredSkills.join(", ") + " before adding " + skillName + errInvalidSkill);
- }
-
- let requiredLevel = getBaseStat("skills", id, sdk.stats.MinimumRequiredLevel);
- if (me.charlvl < requiredLevel) {
- throw new Error("You need to be at least level " + requiredLevel + " before you get " + skillName + errInvalidSkill);
- }
-
- let pointSpent = spendSkillPoint(id);
-
- if (SPEND_POINTS) {
- if (!pointSpent) {
- spentEveryPoint = false;
- AutoBuild.print("Attempt to spend skill point " + (i + 1) + " in " + skillName + " may have failed!");
- } else if (debug) {
- let actualSkillLevel = me.getSkill(id, sdk.skills.subindex.SoftPoints);
- AutoBuild.print("Skill (" + (i + 1) + "/" + len + ") Increased " + skillName + " by one (level: ", actualSkillLevel + ")");
- }
- }
-
- delay(200); // TODO: How long should we wait... if at all?
- }
-
- return spentEveryPoint;
-}
-
-/*
-* TODO: determine if changes need to be made for
-* the case of gaining multiple levels at once so as
-* not to bombard the d2bs event system
-*/
-
-function main () {
- try {
- AutoBuild.print("Loaded helper thread");
- console.log("ÿc8Kolbot-SoloPlayÿc0: Start AutoBuildThread");
- currAutoBuild = usingFinalBuild ? finalBuild.AutoBuildTemplate : build.AutoBuildTemplate;
-
- while (true) {
- let levels = gainedLevels();
-
- if (levels > 0 && (canSpendPoints() || Config.AutoSkill.Enabled || Config.AutoStat.Enabled)) {
- scriptBroadcast("toggleQuitlist");
- AutoBuild.print("Level up detected (", prevLevel, "-->", me.charlvl, ")");
- spendSkillPoints();
- spendStatPoints();
- Config.AutoSkill.Enabled && AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
- Config.AutoStat.Enabled && AutoStat.init(Config.AutoStat.Build, Config.AutoStat.Save, Config.AutoStat.BlockChance, Config.AutoStat.UseBulk);
- scriptBroadcast({event: "level up"});
- AutoBuild.applyConfigUpdates(); // scriptBroadcast() won't trigger listener on this thread.
-
- debug && AutoBuild.print("Incrementing cached character level to", prevLevel + 1);
-
- // prevLevel doesn't get set to me.charlvl because
- // we may have gained multiple levels at once
- prevLevel += 1;
-
- scriptBroadcast("toggleQuitlist");
- }
-
- delay(1e3);
- }
- } catch (err) {
- print("Something broke!");
- print("Error:" + err.toSource());
- print("Stack trace: \n" + err.stack);
-
- return;
- }
-}
diff --git a/libs/SoloPlay/Threads/EventThread.js b/libs/SoloPlay/Threads/EventThread.js
deleted file mode 100644
index 24373cc4..00000000
--- a/libs/SoloPlay/Threads/EventThread.js
+++ /dev/null
@@ -1,265 +0,0 @@
-/**
-* @filename EventThread.js
-* @author theBGuy
-* @desc thread to handle in game events for Kolbot-SoloPlay
-*
-*/
-js_strict(true);
-
-include("json2.js");
-include("NTItemParser.dbl");
-include("OOG.js");
-include("Gambling.js");
-include("CraftingSystem.js");
-include("common/util.js");
-include("common/Attack.js");
-include("common/Common.js");
-include("common/Cubing.js");
-include("common/Config.js");
-include("common/CollMap.js");
-include("common/misc.js");
-include("common/Pickit.js");
-include("common/Pather.js");
-include("common/Precast.js");
-include("common/Prototypes.js");
-include("common/Runewords.js");
-include("common/Town.js");
-// Include SoloPlay's librarys
-include("SoloPlay/Tools/Developer.js");
-include("SoloPlay/Tools/Tracker.js");
-include("SoloPlay/Tools/CharData.js");
-include("SoloPlay/Tools/SoloIndex.js");
-include("SoloPlay/Functions/ConfigOverrides.js");
-include("SoloPlay/Functions/Globals.js");
-
-SetUp.include();
-
-function main () {
- let tickDelay = 0;
- let townChicken = false;
- let [action, profiles] = [[], []];
- const threads = ["libs/SoloPlay/SoloPlay.js", "libs/SoloPlay/Threads/TownChicken.js", "tools/antihostile.js", "tools/party.js"];
-
- console.log("ÿc8Kolbot-SoloPlayÿc0: Start EventThread");
- D2Bot.init();
- Config.init(false);
- Pickit.init(false);
- Attack.init();
- Storage.Init();
- CraftingSystem.buildLists();
- Runewords.init();
- Cubing.init();
-
- this.pauseForEvent = function () {
- for (let i = 0; i < threads.length; i += 1) {
- let script = getScript(threads[i]);
-
- if (townChicken) {
- console.warn("ÿc8EventThread :: ÿc1Trying to townChicken, don't interfere.");
- return false;
- }
-
- if (script) {
- if (script.running) {
- threads[i] === "libs/SoloPlay/SoloPlay.js" && console.log("ÿc8EventThread :: ÿc1Pausing " + threads[i]);
- threads[i] === "libs/SoloPlay/Threads/TownChicken.js" && !SoloEvents.cloneWalked && console.log("ÿc8EventThread :: ÿc1Pausing " + threads[i]);
-
- // don't pause townchicken during clone walk
- if (threads[i] !== "libs/SoloPlay/Threads/TownChicken.js" || !SoloEvents.cloneWalked) {
- script.pause();
- }
- }
- }
- }
-
- return true;
- };
-
- this.resumeThreadsAfterEvent = function () {
- for (let i = 0; i < threads.length; i += 1) {
- let script = getScript(threads[i]);
-
- if (script) {
- if (!script.running) {
- threads[i] === "libs/SoloPlay/SoloPlay.js" && console.log("ÿc8EventThread :: ÿc2Resuming " + threads[i]);
- threads[i] === "libs/SoloPlay/Threads/TownChicken.js" && console.log("ÿc8EventThread :: ÿc2Resuming " + threads[i]);
-
- script.resume();
- }
- }
- }
-
- return true;
- };
-
- this.scriptEvent = function (msg) {
- let obj;
-
- if (msg && typeof msg === "string" && msg !== "") {
- switch (msg) {
- case "townchickenOn":
- townChicken = true;
-
- break;
- case "townchickenOff":
- townChicken = false;
-
- break;
- case "testing":
- case "finishDen":
- case "dodge":
- case "skip":
- case "killdclone":
- action.push(msg);
-
- break;
- case "addDiaEvent":
- console.log("Added dia lightning listener");
- addEventListener("gamepacket", SoloEvents.diaEvent);
-
- break;
- case "removeDiaEvent":
- console.log("Removed dia lightning listener");
- removeEventListener("gamepacket", SoloEvents.diaEvent);
-
- break;
- case "addBaalEvent":
- console.log("Added baal wave listener");
- addEventListener("gamepacket", SoloEvents.baalEvent);
-
- break;
- case "removeBaalEvent":
- console.log("Removed baal wave listener");
- removeEventListener("gamepacket", SoloEvents.baalEvent);
-
- break;
- // ignore common scriptBroadcast messages that aren't relevent to this thread
- case "mule":
- case "muleTorch":
- case "muleAnni":
- case "torch":
- case "crafting":
- case "getMuleMode":
- case "pingquit":
- return;
- }
-
- switch (true) {
- case msg.substring(0, 8) === "config--":
- console.debug("update config");
- Config = JSON.parse(msg.split("config--")[1]);
-
- break;
- case msg.substring(0, 7) === "skill--":
- console.debug("update skillData");
- obj = JSON.parse(msg.split("skill--")[1]);
- Misc.updateRecursively(CharData.skillData, obj);
-
- break;
- case msg.substring(0, 6) === "data--":
- console.debug("update myData");
- obj = JSON.parse(msg.split("data--")[1]);
- Misc.updateRecursively(myData, obj);
-
- break;
- case msg.toLowerCase() === "test":
- console.debug(sdk.colors.Green + "//-----------DataDump Start-----------//\nÿc8MainData ::\n",
- myData, "\nÿc8BuffData ::\n", CharData.buffData, "\nÿc8SkillData ::\n", CharData.skillData, "\n" + sdk.colors.Red + "//-----------DataDump End-----------//");
-
- break;
- }
- }
- };
-
- this.receiveCopyData = function (id, info) {
- // Torch
- if (id === 55) {
- let { profile, ladder, torchType } = JSON.parse(info);
- console.log("Mesage recived for torch...processing");
-
- if (profile !== me.profile && (me.hell || (me.nightmare && me.baal)) && me.ladder === ladder) {
- if (torchType === me.classid && !me.findItem(604, 0, null, 7)) {
- console.log("Sent Response");
- SoloEvents.sendToProfile(profile, {profile: me.profile, level: me.charlvl, event: 604});
- }
- }
-
- return;
- }
-
- // Annhilus
- if (id === 60) {
- let { profile, ladder } = JSON.parse(info);
- console.log("Mesage recived for Annhilus...processing");
-
- if (profile !== me.profile && (me.hell || (me.nightmare && me.baal)) && me.ladder === ladder) {
- if (!me.findItem(603, 0, null, 7)) {
- console.log("Sent Response");
- SoloEvents.sendToProfile(profile, {profile: me.profile, level: me.charlvl, event: 603});
- }
- }
-
- return;
- }
-
- if (id === 65) {
- let { profile, level, event } = JSON.parse(info);
-
- console.log("Sucess: profile that contacted me: " + profile + " level of char: " + level);
- SoloEvents.profileResponded = true;
- profiles.push({profile: profile, level: level, event: event});
- tickDelay += 1000;
- }
-
- if (id === 70) {
- Messaging.sendToScript("D2BotSoloPlay.dbj", "event");
- delay(100 + me.ping);
- scriptBroadcast("quit");
- }
- };
-
- addEventListener("scriptmsg", this.scriptEvent);
- addEventListener("copydata", this.receiveCopyData); // should this just be added to the starter? would remove needing 3 copydata event listeners (entry, default, and here)
-
- // Start
- // test for getUnit bug
- let test = Game.getMonster();
- test === null && console.warn("getUnit is bugged");
-
- while (true) {
- try {
- while (action.length) {
- if (this.pauseForEvent()) {
- try {
- SoloEvents[action.shift()]();
- } catch (e) {
- console.log(e);
- }
-
- this.resumeThreadsAfterEvent();
- } else {
- action.shift();
- }
- }
-
- if (profiles.length > 0) {
- let tick = getTickCount();
-
- while (getTickCount() - tick < tickDelay) {
- delay(500);
- }
-
- let lowestLevelProf = profiles.sort((a, b) => a.level - b.level).first();
-
- SoloEvents.sendToProfile(lowestLevelProf.profile, lowestLevelProf.event, 70);
- D2Bot.joinMe(lowestLevelProf.profile, me.gamename.toLowerCase(), "", me.gamepassword.toLowerCase(), true);
- profiles = [];
- }
- } catch (e) {
- D2Bot.printToConsole(JSON.stringify(e));
- console.log(e);
- }
-
- delay(20);
- }
-}
diff --git a/libs/SoloPlay/Threads/Reload.js b/libs/SoloPlay/Threads/Reload.js
new file mode 100644
index 00000000..473285b5
--- /dev/null
+++ b/libs/SoloPlay/Threads/Reload.js
@@ -0,0 +1,35 @@
+/**
+* @filename Reload.js
+* @author theBGuy
+* @desc Simple thread that when loaded will shutdown all other threads and reload the bot
+*
+*/
+js_strict(true);
+include("critical.js");
+
+function main () {
+ let tick = getTickCount();
+ let scripts = [
+ "default.dbj", "libs/SoloPlay/SoloPlay.js", "libs/SoloPlay/Threads/Toolsthread.js",
+ "libs/SoloPlay/Modules/Guard.js", "libs/SoloPlay/Modules/TownGuard.js"
+ ];
+
+ while (scripts.length) {
+ let script = getScript(scripts[0]);
+
+ if (script && script.running) {
+ script.stop();
+
+ while (script.running) {
+ delay(100);
+ }
+ }
+ scripts.shift();
+ }
+
+ while (getTickCount() - tick < 5000) {
+ delay(100);
+ }
+ console.log("All threads shutdown ~ Reloading bot");
+ load("default.dbj");
+}
diff --git a/libs/SoloPlay/Threads/ToolsThread.js b/libs/SoloPlay/Threads/ToolsThread.js
index 31d58a48..358cb427 100644
--- a/libs/SoloPlay/Threads/ToolsThread.js
+++ b/libs/SoloPlay/Threads/ToolsThread.js
@@ -5,28 +5,16 @@
*
*/
js_strict(true);
+include("critical.js");
+
+// globals needed for core gameplay
+includeCoreLibs({ exclude: ["Storage.js"] });
+include("core/Common/Tools.js");
+
+// system libs
+includeSystemLibs();
+include("systems/mulelogger/MuleLogger.js");
-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/Attack.js");
-include("common/Common.js");
-include("common/Cubing.js");
-include("common/CollMap.js");
-include("common/Config.js");
-include("common/misc.js");
-include("common/util.js");
-include("common/Pickit.js");
-include("common/Pather.js");
-include("common/Precast.js");
-include("common/Prototypes.js");
-include("common/Runewords.js");
-include("common/Town.js");
// Include SoloPlay's librarys
include("SoloPlay/Tools/Developer.js");
include("SoloPlay/Tools/Tracker.js");
@@ -35,662 +23,849 @@ include("SoloPlay/Tools/SoloIndex.js");
include("SoloPlay/Functions/ConfigOverrides.js");
include("SoloPlay/Functions/Globals.js");
+/**
+ * @todo trim the uneeded files/global variables from this file
+ */
+
function main () {
- let ironGolem, tick, quitListDelayTime;
- let canQuit = true;
- let timerLastDrink = [];
- let [quitFlag, restart] = [false, false];
- let debugInfo = { area: 0, currScript: "no entry" };
-
- new Overrides.Override(Attack, Attack.getNearestMonster, function (orignal) {
- let monster = orignal({skipBlocked: false, skipImmune: false});
- return (monster ? " to " + monster.name : "");
- }).apply();
-
- console.log("ÿc8Kolbot-SoloPlayÿc0: Start Custom ToolsThread script");
- D2Bot.init();
- SetUp.include();
- Config.init(false);
- Pickit.init(false);
- Attack.init();
- Storage.Init();
- CraftingSystem.buildLists();
- Runewords.init();
- Cubing.init();
-
- Developer.overlay && include("SoloPlay/Tools/Overlay.js");
-
- for (let i = 0; i < 5; i += 1) {
- timerLastDrink[i] = 0;
- }
-
- // Reset core chicken
- me.chickenhp = -1;
- me.chickenmp = -1;
-
- // General functions
- this.togglePause = function () {
- let scripts = ["libs/SoloPlay/SoloPlay.js", "libs/SoloPlay/Threads/TownChicken.js", "tools/antihostile.js", "tools/party.js"];
-
- for (let l = 0; l < scripts.length; l += 1) {
- let script = getScript(scripts[l]);
-
- if (script) {
- if (script.running) {
- scripts[l] === "libs/SoloPlay/SoloPlay.js" && console.log("ÿc8ToolsThread :: ÿc1Pausing " + scripts[l]);
- scripts[l] === "libs/SoloPlay/Threads/TownChicken.js" && !SoloEvents.cloneWalked && console.log("ÿc8ToolsThread :: ÿc1Pausing " + scripts[l]);
-
- // don't pause townchicken during clone walk
- if (scripts[l] !== "libs/SoloPlay/Threads/TownChicken.js" || !SoloEvents.cloneWalked) {
- script.pause();
- }
- } else {
- scripts[l] === "libs/SoloPlay/SoloPlay.js" && console.log("ÿc8ToolsThread :: ÿc2Resuming threads");
- script.resume();
- }
- }
- }
-
- return true;
- };
-
- this.stopDefault = function () {
- let scripts = [
- "libs/SoloPlay/SoloPlay.js", "libs/SoloPlay/Threads/TownChicken.js", "libs/SoloPlay/Threads/EventThread.js",
- "libs/SoloPlay/Threads/AutoBuildThread.js", "libs/SoloPlay/Modules/Guard.js", "libs/SoloPlay/Modules/TownGuard.js"
- ];
-
- for (let l = 0; l < scripts.length; l += 1) {
- let script = getScript(scripts[l]);
- !!script && script.running && script.stop();
- }
-
- return true;
- };
-
- this.exit = function (chickenExit = false) {
- chickenExit && D2Bot.updateChickens();
- Config.LogExperience && Experience.log();
- Developer.logPerformance && Tracker.update();
- console.log("ÿc8Run duration ÿc2" + Developer.formatTime(getTickCount() - me.gamestarttime));
- this.stopDefault();
- quit();
- };
-
- this.restart = function () {
- Config.LogExperience && Experience.log();
- Developer.logPerformance && Tracker.update();
- this.stopDefault();
- D2Bot.restart();
- };
-
- this.getPotion = function (pottype = -1, type = -1) {
- if (pottype === undefined) return false;
-
- let items = me.getItemsEx().filter(item => item.itemType === pottype && (type > Common.Toolsthread.pots.Rejuv ? item.isInBelt : true));
- if (items.length === 0) return false;
- let invoFirst = [Common.Toolsthread.pots.Health, Common.Toolsthread.pots.Mana].includes(type);
-
- if (invoFirst) {
- // sort by location (invo first, then classid)
- items.sort(function (a, b) {
- let [aLoc, bLoc] = [a.location, b.location];
- if (bLoc < aLoc) return -1;
- if (bLoc > aLoc) return 1;
- return b.classid - a.classid;
- });
- } else {
- // Get highest id = highest potion first
- items.sort(function (a, b) {
- return b.classid - a.classid;
- });
- }
-
- for (let k = 0; k < items.length; k += 1) {
- if (type < Common.Toolsthread.pots.MercHealth && items[k].isInInventory && items[k].itemType === pottype) {
- console.log("ÿc2Drinking " + items[k].name + " from inventory.");
- return items[k];
- }
-
- if (items[k].mode === sdk.items.mode.inBelt && items[k].itemType === pottype) {
- console.log("ÿc2" + (type > 2 ? "Giving Merc " : "Drinking ") + items[k].name + " from belt.");
- return items[k];
- }
- }
-
- return false;
- };
-
- this.drinkPotion = function (type) {
- if (type === undefined) return false;
- let tNow = getTickCount();
-
- switch (type) {
- case Common.Toolsthread.pots.Health:
- case Common.Toolsthread.pots.Mana:
- if ((timerLastDrink[type] && (tNow - timerLastDrink[type] < 1000)) || me.getState(type === 0 ? 100 : 106)) {
- return false;
- }
-
- break;
- case Common.Toolsthread.pots.Rejuv:
- // small delay for juvs just to prevent using more at once
- if (timerLastDrink[type] && (tNow - timerLastDrink[type] < 300)) {
- return false;
- }
-
- break;
- case Common.Toolsthread.pots.MercRejuv:
- // larger delay for juvs just to prevent using more at once, considering merc update rate
- if (timerLastDrink[type] && (tNow - timerLastDrink[type] < 2000)) {
- return false;
- }
-
- break;
- default:
- if (timerLastDrink[type] && (tNow - 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;
-
- let pottype = (() => {
- switch (type) {
- case Common.Toolsthread.pots.Health:
- case Common.Toolsthread.pots.MercHealth:
- return sdk.items.type.HealingPotion;
- case Common.Toolsthread.pots.Mana:
- return sdk.items.type.ManaPotion;
- default:
- return sdk.items.type.RejuvPotion;
- }
- })();
-
- let potion = this.getPotion(pottype, type);
-
- if (!!potion) {
- // mode 18 - can't drink while leaping/whirling etc.
- if (me.dead || me.mode === sdk.player.mode.SkillActionSequence) return false;
-
- try {
- type < Common.Toolsthread.pots.MercHealth ? potion.interact() : Packet.useBeltItemForMerc(potion);
- } catch (e) {
- console.error(e);
- }
-
- timerLastDrink[type] = getTickCount();
- delay(25);
-
- return true;
- }
-
- return false;
- };
-
- this.drinkSpecialPotion = function (type) {
- if (type === undefined) return false;
- let objID;
- let name = (() => {
- switch (type) {
- case sdk.items.ThawingPotion:
- return "thawing";
- case sdk.items.AntidotePotion:
- return "antidote";
- case sdk.items.StaminaPotion:
- return "stamina";
- default:
- return "";
- }
- })();
-
- // mode 18 - can't drink while leaping/whirling etc.
- // give at least a second delay between pots
- if (me.dead || me.mode === sdk.player.mode.SkillActionSequence || (getTickCount() - CharData.buffData[name].tick < 1000)) {
- return false;
- }
-
- let pot = me.getItemsEx(-1, sdk.items.mode.inStorage).filter((p) => p.isInInventory && p.classid === type).first();
- !!pot && (objID = pot.name.split(" ")[0].toLowerCase());
-
- if (objID) {
- try {
- pot.interact();
- if (!CharData.buffData[objID].active() || CharData.buffData[objID].timeLeft() <= 0) {
- CharData.buffData[objID].tick = getTickCount();
- CharData.buffData[objID].duration = 3e4;
- } else {
- CharData.buffData[objID].duration += 3e4 - (getTickCount() - CharData.buffData[objID].tick);
- }
-
- console.debug(CharData.buffData);
- } catch (e) {
- console.warn(e);
- }
-
- return true;
- }
-
- return false;
- };
-
- // Event functions
- this.keyEvent = function (key) {
- switch (key) {
- case sdk.keys.PauseBreak: // pause default.dbj
- this.togglePause();
-
- break;
- case sdk.keys.Numpad0: // stop profile without logging character
- Developer.logPerformance && Tracker.update();
- console.log("ÿc8Kolbot-SoloPlay: ÿc1Stopping profile");
- delay(rand(2e3, 5e3));
- D2Bot.stop(me.profile, true);
-
- break;
- case sdk.keys.End: // stop profile and log character
- Developer.logEquipped ? MuleLogger.logEquippedItems() : MuleLogger.logChar();
- Developer.logPerformance && Tracker.update();
-
- delay(rand(Config.QuitListDelay[0] * 1e3, Config.QuitListDelay[1] * 1e3));
- D2Bot.printToConsole(me.profile + " - end run " + me.gamename);
- D2Bot.stop(me.profile, true);
-
- break;
- case sdk.keys.Insert: // reveal level
- me.overhead("Revealing " + Pather.getAreaName(me.area));
- revealLevel(true);
-
- break;
- case sdk.keys.NumpadPlus: // log stats
- showConsole();
-
- console.log("ÿc8My stats :: " + Common.Toolsthread.getStatsString(me));
- let merc = me.getMerc();
- !!merc && console.log("ÿc8Merc stats :: " + Common.Toolsthread.getStatsString(merc));
- console.log("//------ÿc8SoloWants.needListÿc0-----//");
- console.log(SoloWants.needList);
-
- break;
- case sdk.keys.Numpad5: // force automule check
- if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("muleInfo")) {
- if (AutoMule.getMuleItems().length > 0) {
- console.log("ÿc2Mule triggered");
- scriptBroadcast("mule");
- this.exit();
-
- } else {
- me.overhead("No items to mule.");
- }
- } else {
- me.overhead("Profile not enabled for muling.");
- }
-
- break;
- case sdk.keys.Numpad6: // log character to char viewer
- Developer.logEquipped ? MuleLogger.logEquippedItems() : MuleLogger.logChar();
- me.overhead("Logged char: " + me.name);
-
- break;
- case sdk.keys.NumpadDash:
- {
- let itemToCheck = Game.getSelectedUnit();
- if (!!itemToCheck) {
- D2Bot.printToConsole("getTier: " + NTIP.GetTier(itemToCheck));
- D2Bot.printToConsole("tierscore: " + tierscore(itemToCheck));
- D2Bot.printToConsole("getSecondaryTier: " + NTIP.GetSecondaryTier(itemToCheck));
- D2Bot.printToConsole("secondarytierscore: " + secondaryscore(itemToCheck));
- D2Bot.printToConsole("charmTier: " + NTIP.GetCharmTier(itemToCheck));
- D2Bot.printToConsole("charmscore: " + charmscore(itemToCheck));
- D2Bot.printToConsole("getMercTier: " + NTIP.GetMercTier(itemToCheck));
- D2Bot.printToConsole("mercscore: " + mercscore(itemToCheck));
- console.log(itemToCheck.fname + " info printed to console");
- }
- }
-
- break;
- case sdk.keys.NumpadDecimal: // dump item info
- {
- let [itemString, charmString, generalString] = ["", "", ""];
- let itemToCheck = Game.getSelectedUnit();
- if (!!itemToCheck) {
- let special = "";
- if (itemToCheck.itemType === sdk.items.type.Ring) {
- special = (" | ÿc4TierLHS: ÿc0" + tierscore(itemToCheck, sdk.body.RingRight) + " | ÿc4TierRHS: ÿc0" + tierscore(itemToCheck, sdk.body.RingLeft));
- }
- itemString = "ÿc4MaxQuantity: ÿc0" + NTIP.getMaxQuantity(itemToCheck) + " | ÿc4ItemsOwned: ÿc0" + Item.getQuantityOwned(itemToCheck) + " | ÿc4Tier: ÿc0" + NTIP.GetTier(itemToCheck) + (special ? special : "")
- + " | ÿc4SecondaryTier: ÿc0" + NTIP.GetSecondaryTier(itemToCheck) + " | ÿc4MercTier: ÿc0" + NTIP.GetMercTier(itemToCheck) + "\n"
- + "ÿc4AutoEquipKeepCheck: ÿc0" + Item.autoEquipKeepCheck(itemToCheck) + " | ÿc4AutoEquipCheckSecondary: ÿc0" + Item.autoEquipCheckSecondary(itemToCheck)
- + " | ÿc4AutoEquipKeepCheckMerc: ÿc0" + Item.autoEquipKeepCheckMerc(itemToCheck) + "\nÿc4Cubing Item: ÿc0" + Cubing.keepItem(itemToCheck)
- + " | ÿc4Runeword Item: ÿc0" + Runewords.keepItem(itemToCheck) + " | ÿc4Crafting Item: ÿc0" + CraftingSystem.keepItem(itemToCheck) + " | ÿc4SoloWants Item: ÿc0" + SoloWants.keepItem(itemToCheck)
- + "\nÿc4ItemType: ÿc0" + itemToCheck.itemType + "| ÿc4Classid: ÿc0" + itemToCheck.classid + "| ÿc4Quality: ÿc0" + itemToCheck.quality;
- charmString = "ÿc4InvoQuantity: ÿc0" + NTIP.getInvoQuantity(itemToCheck) + " | ÿc4hasStats: ÿc0" + NTIP.hasStats(itemToCheck) + " | ÿc4FinalCharm: ÿc0" + Item.isFinalCharm(itemToCheck) + "\n"
- + "ÿc4CharmType: ÿc0" + Item.getCharmType(itemToCheck) + " | ÿc4AutoEquipCharmCheck: ÿc0" + Item.autoEquipCharmCheck(itemToCheck) + " | ÿc4CharmTier: ÿc0" + NTIP.GetCharmTier(itemToCheck);
- generalString = "ÿc4ItemName: ÿc0" + itemToCheck.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "")
- + "\nÿc4Pickit: ÿc0" + Pickit.checkItem(itemToCheck).result + " | ÿc4NTIP.CheckItem: ÿc0" + NTIP.CheckItem(itemToCheck, false, true).result + " | ÿc4NTIP.CheckItem No Tier: ÿc0" + NTIP.CheckItem(itemToCheck, NTIP_CheckListNoTier, true).result;
- }
-
- console.log("ÿc8Kolbot-SoloPlay: ÿc2Item Info Start");
- console.log(itemString);
- console.log("ÿc8Kolbot-SoloPlay: ÿc2Charm Info Start");
- console.log(charmString);
- console.log("ÿc8Kolbot-SoloPlay: ÿc2General Info Start");
- console.log(generalString);
- console.log("ÿc8Kolbot-SoloPlay: ÿc1****************Info End****************");
- }
-
- break;
- case sdk.keys.Numpad9: // get nearest preset unit id
- console.log(Common.Toolsthread.getNearestPreset());
-
- break;
- case sdk.keys.NumpadStar: // precast
- Precast.doPrecast(true);
-
- break;
- case sdk.keys.NumpadSlash: // re-load default
- console.log("ÿc8ToolsThread :: " + sdk.colors.Red + "Stopping threads and waiting 5 seconds to restart");
- this.stopDefault() && delay(5e3);
- console.log("Starting libs/SoloPlay/SoloPlay.js");
- load("libs/SoloPlay/SoloPlay.js");
-
- 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")
- || (Config.QuitList instanceof Array && Config.QuitList.indexOf (name1) > -1)) {
- console.log(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;
- }
-
- Config.AntiHostile && scriptBroadcast("remove " + name1);
-
- break;
- case 0x06:
- // "%Name1 was Slain by %Name2"
- if (Config.AntiHostile && param2 === 0x00 && name2 === me.name) {
- scriptBroadcast("mugshot " + name1);
- }
-
- break;
- case 0x07:
- // "%Player has declared hostility towards you."
- if (Config.AntiHostile && param2 === 0x03) {
- scriptBroadcast("findHostiles");
- }
-
- break;
- case 0x11: // "%Param1 Stones of Jordan Sold to Merchants"
- if (Config.DCloneQuit === 2) {
- D2Bot.printToConsole("SoJ sold in game. Leaving.");
- quitFlag = true;
-
- break;
- }
-
- // Only do this in expansion
- if (Config.SoJWaitTime && !me.classic) {
- !!me.gameserverip && D2Bot.printToConsole(param1 + " Stones of Jordan Sold to Merchants on IP " + me.gameserverip.split(".")[3], sdk.colors.D2Bot.DarkGold);
- Messaging.sendToScript("default.dbj", "soj");
- }
-
- break;
- case 0x12: // "Diablo Walks the Earth"
- if (Config.DCloneQuit > 0) {
- D2Bot.printToConsole("Diablo walked in game. Leaving.");
- quitFlag = true;
-
- break;
- }
-
- // Only do this in expansion
- if (Config.StopOnDClone && !me.classic && me.hell) {
- D2Bot.printToConsole("Diablo Walks the Earth", sdk.colors.D2Bot.DarkGold);
- SoloEvents.cloneWalked = true;
- this.togglePause();
- Town.goToTown();
- showConsole();
- myPrint("ÿc4Diablo Walks the Earth");
- me.maxgametime += (30 * 1000 * 60); // Add 30 minutes to current maxgametime
- Config.KillDclone && Messaging.sendToScript(SoloEvents.filePath, "killdclone");
- }
-
- break;
- }
- };
-
- this.scriptEvent = function (msg) {
- let obj;
-
- if (msg && typeof msg === "string" && msg !== "") {
- let updated = false;
- switch (true) {
- case msg.substring(0, 8) === "config--":
- console.debug("update config");
- Config = JSON.parse(msg.split("config--")[1]);
- updated = true;
-
- break;
- case msg.substring(0, 7) === "skill--":
- console.debug("update skillData");
- obj = JSON.parse(msg.split("skill--")[1]);
- Misc.updateRecursively(CharData.skillData, obj);
- updated = true;
-
- break;
- case msg.substring(0, 6) === "data--":
- console.debug("update myData");
- obj = JSON.parse(msg.split("data--")[1]);
- Misc.updateRecursively(myData, obj);
- updated = true;
-
- break;
- case msg.toLowerCase() === "test":
- console.debug(sdk.colors.Green + "//-----------DataDump Start-----------//\nÿc8MainData ::\n",
- myData, "\nÿc8BuffData ::\n", CharData.buffData, "\nÿc8SkillData ::\n", CharData.skillData, "\n" + sdk.colors.Red + "//-----------DataDump End-----------//");
- updated = true;
-
- break;
- }
-
- if (updated) return;
- }
-
- switch (msg) {
- case "toggleQuitlist":
- canQuit = !canQuit;
-
- break;
- case "quit":
- quitFlag = true;
-
- break;
- case "restart":
- restart = true;
-
- break;
- // ignore common scriptBroadcast messages that aren't relevent to this thread
- case "mule":
- case "muleTorch":
- case "muleAnni":
- case "torch":
- case "crafting":
- case "getMuleMode":
- case "pingquit":
- case "townCheck":
- break;
- default:
- try {
- obj = JSON.parse(msg);
- } catch (e) {
- return;
- }
-
- if (obj) {
- obj.hasOwnProperty("currScript") && (debugInfo.currScript = obj.currScript);
- obj.hasOwnProperty("lastAction") && (debugInfo.lastAction = obj.lastAction);
- //D2Bot.store(JSON.stringify(debugInfo));
- DataFile.updateStats("debugInfo", JSON.stringify(debugInfo));
- }
-
- break;
- }
- };
-
- // Cache variables to prevent a bug where d2bs loses the reference to Config object
- Config = Misc.copy(Config);
- tick = getTickCount();
-
- addEventListener("keyup", this.keyEvent);
- addEventListener("gameevent", this.gameEvent);
- addEventListener("scriptmsg", this.scriptEvent);
- addEventListener("scriptmsg", Tracker.logLeveling);
-
- // Load Fastmod - patched
- // Packet.changeStat(105, Config.FCR);
- // Packet.changeStat(99, Config.FHR);
- // Packet.changeStat(102, Config.FBR);
- // Packet.changeStat(93, Config.IAS);
-
- Config.QuitListMode > 0 && this.initQuitList();
-
- let myAct = me.act;
-
- if (Developer.overlay && !Developer.logPerformance) {
- console.warn("Without logPerformance set, the overlay will only show partial values");
- }
-
- // Start
- while (true) {
- try {
- if (me.gameReady && !me.inTown) {
- // todo - build potion list only once per iteration
- Config.UseHP > 0 && me.hpPercent < Config.UseHP && this.drinkPotion(Common.Toolsthread.pots.Health);
- Config.UseRejuvHP > 0 && me.hpPercent < Config.UseRejuvHP && this.drinkPotion(Common.Toolsthread.pots.Rejuv);
-
- if (Config.LifeChicken > 0 && me.hpPercent <= Config.LifeChicken && !me.inTown) {
- !Developer.hideChickens && D2Bot.printToConsole("Life Chicken (" + me.hp + "/" + me.hpmax + ")" + Attack.getNearestMonster() + " in " + Pather.getAreaName(me.area) + ". Ping: " + me.ping, sdk.colors.D2Bot.Red);
- this.exit(true);
-
- break;
- }
-
- Config.UseMP > 0 && me.mpPercent < Config.UseMP && this.drinkPotion(Common.Toolsthread.pots.Mana);
- Config.UseRejuvMP > 0 && me.mpPercent < Config.UseRejuvMP && this.drinkPotion(Common.Toolsthread.pots.Rejuv);
-
- (me.staminaPercent <= 20 || me.walking) && this.drinkSpecialPotion(sdk.items.StaminaPotion);
- me.getState(sdk.states.Poison) && this.drinkSpecialPotion(sdk.items.AntidotePotion);
- [sdk.states.Frozen, sdk.states.FrozenSolid].some(state => me.getState(state)) && this.drinkSpecialPotion(sdk.items.ThawingPotion);
-
- if (Config.ManaChicken > 0 && me.mpPercent <= Config.ManaChicken && !me.inTown) {
- !Developer.hideChickens && D2Bot.printToConsole("Mana Chicken: (" + me.mp + "/" + me.mpmax + ") in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red);
- this.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)) {
- !Developer.hideChickens && D2Bot.printToConsole("Irom Golem Chicken in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red);
- this.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) {
- !Developer.hideChickens && D2Bot.printToConsole("Merc Chicken in " + Pather.getAreaName(me.area), sdk.colors.D2Bot.Red);
- this.exit(true);
-
- break;
- }
-
- mercHP < Config.UseMercHP && this.drinkPotion(Common.Toolsthread.pots.MercHealth);
- mercHP < Config.UseMercRejuv && this.drinkPotion(Common.Toolsthread.pots.MercRejuv);
- }
- }
- }
-
- if (Config.ViperCheck && getTickCount() - tick >= 250) {
- if (Common.Toolsthread.checkVipers()) {
- D2Bot.printToConsole("Revived Tomb Vipers found. Leaving game.", sdk.colors.D2Bot.Red);
- quitFlag = true;
- }
-
- tick = getTickCount();
- }
-
- Common.Toolsthread.checkPing(true) && (quitFlag = true);
- }
- } catch (e) {
- Misc.errorReport(e, "ToolsThread");
-
- quitFlag = true;
- }
-
- if (me.maxgametime - (getTickCount() - me.gamestarttime) < 10e3) {
- console.log("Max game time reached");
- quitFlag = true;
- }
-
- // should overlay be moved to be a background worker?
- if (Developer.overlay) {
- if (me.ingame && me.gameReady && me.area) {
- Overlay.update(quitFlag);
-
- if (me.act !== myAct) {
- Overlay.flush();
- myAct = me.act;
- }
- }
- }
-
- if (quitFlag && canQuit && (typeof quitListDelayTime === "undefined" || getTickCount() >= quitListDelayTime)) {
- Common.Toolsthread.checkPing(false); // In case of quitlist triggering first
- this.exit();
-
- break;
- }
-
- !!restart && this.restart();
-
- if (debugInfo.area !== Pather.getAreaName(me.area)) {
- debugInfo.area = Pather.getAreaName(me.area);
- DataFile.updateStats("debugInfo", JSON.stringify(debugInfo));
- }
-
- delay(20);
- }
-
- return true;
+ let ironGolem, tick, quitListDelayTime;
+ let canQuit = true;
+ let [quitFlag, restart] = [false, false];
+ let debugInfo = { area: 0, currScript: "no entry" };
+
+ new Overrides.Override(Attack, Attack.getNearestMonster, function (orignal) {
+ let monster = orignal({ skipBlocked: false, skipImmune: false });
+ return (monster ? " to " + monster.name : "");
+ }).apply();
+
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Start Custom ToolsThread script");
+ D2Bot.init();
+ SetUp.include();
+ Config.init(false);
+ Pickit.init(false);
+ Attack.init();
+ Storage.Init();
+ CraftingSystem.buildLists();
+ Runewords.init();
+ Cubing.init();
+
+ Developer.overlay && include("SoloPlay/Tools/Overlay.js");
+
+ // Reset core chicken
+ me.chickenhp = -1;
+ me.chickenmp = -1;
+
+ const timerLastDrink = new Map([
+ [sdk.items.type.HealingPotion, 0],
+ [sdk.items.type.ManaPotion, 0],
+ [sdk.items.type.RejuvPotion, 0],
+ ]);
+ const timerMercDrink = new Map([
+ [sdk.items.type.HealingPotion, 0],
+ [sdk.items.type.RejuvPotion, 0],
+ ]);
+
+ // General functions
+ const togglePause = function () {
+ ["libs/SoloPlay/SoloPlay.js", "threads/party.js"]
+ .forEach(function (script) {
+ let thread = getScript(script);
+ if (thread) {
+ if (thread.running) {
+ script === "libs/SoloPlay/SoloPlay.js" && console.log("ÿc8ToolsThread :: ÿc1Pausing " + script);
+ thread.pause();
+ } else {
+ script === "libs/SoloPlay/SoloPlay.js" && console.log("ÿc8ToolsThread :: ÿc2Resuming threads");
+ thread.resume();
+ }
+ }
+ });
+ return true;
+ };
+
+ const stopDefault = function () {
+ ["libs/SoloPlay/SoloPlay.js", "libs/SoloPlay/Modules/Guard.js", "threads/party.js"]
+ .forEach(function (script) {
+ let thread = getScript(script);
+ if (thread && thread.running) {
+ thread.stop();
+ }
+ });
+ return true;
+ };
+
+ const exit = function (chickenExit = false) {
+ chickenExit && D2Bot.updateChickens();
+ Config.LogExperience && Experience.log();
+ Developer.logPerformance && Tracker.update();
+ console.log("ÿc8Run duration ÿc2" + Time.format(getTickCount() - me.gamestarttime));
+ stopDefault();
+ quit();
+ };
+
+ const restartGame = function () {
+ Config.LogExperience && Experience.log();
+ Developer.logPerformance && Tracker.update();
+ stopDefault();
+ D2Bot.restart();
+ };
+
+ /** @param {number} type */
+ const getPotion = function (type = -1) {
+ if (type === undefined) return false;
+
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item.itemType === type;
+ });
+ if (items.length === 0) return false;
+ const invoFirst = [
+ sdk.items.type.HealingPotion, sdk.items.type.ManaPotion
+ ].includes(type);
+
+ if (invoFirst) {
+ // sort by location (invo first, then classid)
+ items.sort(function (a, b) {
+ let [aLoc, bLoc] = [a.location, b.location];
+ if (bLoc < aLoc) return -1;
+ if (bLoc > aLoc) return 1;
+ return b.classid - a.classid;
+ });
+ } else {
+ // Get highest id = highest potion first
+ items.sort(function (a, b) {
+ return b.classid - a.classid;
+ });
+ }
+
+ for (let item of items) {
+ if (item.isInInventory && item.itemType === type) {
+ console.log("ÿc2Drinking " + item.name + " from inventory.");
+ return item;
+ }
+
+ if (item.mode === sdk.items.mode.inBelt && item.itemType === type) {
+ console.log("ÿc2Drinking " + item.name + " from belt.");
+ return item;
+ }
+ }
+
+ return false;
+ };
+
+ /** @param {number} type */
+ const drinkPotion = function (type) {
+ if (type === undefined) return false;
+ let tNow = getTickCount();
+
+ switch (type) {
+ case sdk.items.type.HealingPotion:
+ if (tNow - timerLastDrink.get(type) < 1000 || me.getState(sdk.states.HealthPot)) return false;
+ break;
+ case sdk.items.type.ManaPotion:
+ if (tNow - timerLastDrink.get(type) < 1000 || me.getState(sdk.states.ManaPot)) return false;
+ break;
+ case sdk.items.type.RejuvPotion:
+ if (tNow - timerLastDrink.get(type) < 300) return false;
+ break;
+ }
+
+ // mode 18 - can't drink while leaping/whirling etc.
+ if (me.dead || me.mode === sdk.player.mode.SkillActionSequence) return false;
+
+ let potion = getPotion(type);
+
+ if (potion) {
+ // mode 18 - can't drink while leaping/whirling etc.
+ if (me.dead || me.mode === sdk.player.mode.SkillActionSequence) return false;
+
+ try {
+ potion.interact();
+ } catch (e) {
+ console.error(e);
+ }
+
+ timerLastDrink.set(type, getTickCount());
+ delay(25);
+
+ return true;
+ }
+
+ return false;
+ };
+
+ /** @param {number} type */
+ const getMercPotion = function (type = -1) {
+ if (type === undefined) return false;
+
+ let items = me.getItemsEx()
+ .filter(function (item) {
+ return item.itemType === type && item.isInBelt;
+ })
+ .sort(function (a, b) {
+ return b.classid - a.classid;
+ });
+ if (items.length === 0) return false;
+
+ for (let item of items) {
+ if (item.itemType === type) {
+ console.log("ÿc2 Giving Merc " + item.name + " from belt.");
+ return item;
+ }
+ }
+
+ return false;
+ };
+
+ /** @param {number} type */
+ const giveMercPotion = function (type) {
+ if (type === undefined) return false;
+ let tNow = getTickCount();
+
+ switch (type) {
+ case sdk.items.type.HealingPotion:
+ if (tNow - timerMercDrink.get(type) < 8000) return false;
+ break;
+ case sdk.items.type.RejuvPotion:
+ if (tNow - timerMercDrink.get(type) < 2000) return false;
+ break;
+ }
+
+ // mode 18 - can't drink while leaping/whirling etc.
+ if (me.dead || me.mode === sdk.player.mode.SkillActionSequence) return false;
+
+ let potion = getMercPotion(type);
+
+ if (potion) {
+ // mode 18 - can't drink while leaping/whirling etc.
+ if (me.dead || me.mode === sdk.player.mode.SkillActionSequence) return false;
+
+ try {
+ Packet.useBeltItemForMerc(potion);
+ } catch (e) {
+ console.error(e);
+ }
+
+ timerMercDrink.set(type, getTickCount());
+ delay(25);
+
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Handles thawing/antidote/stamina potions
+ * @param {number} type
+ * @returns {boolean}
+ */
+ const drinkSpecialPotion = function (type) {
+ if (type === undefined) return false;
+ if (!CharData.pots.has(type)) return false;
+ // give at least a second delay between pots
+ if (CharData.pots.get(type).tick < 1000) return false;
+
+ // mode 18 - can't drink while leaping/whirling etc.
+ if (me.dead || me.mode === sdk.player.mode.SkillActionSequence) {
+ return false;
+ }
+
+ let pot = me.getItemsEx(-1, sdk.items.mode.inStorage)
+ .filter(function (p) {
+ return p.isInInventory && p.classid === type;
+ }).first();
+
+ if (pot) {
+ try {
+ pot.interact();
+
+ if (!CharData.pots.get(type).active() || CharData.pots.get(type).timeLeft() <= 0) {
+ CharData.pots.get(type).tick = getTickCount();
+ CharData.pots.get(type).duration = 3e4;
+ } else {
+ CharData.pots.get(type).duration += 3e4 - (getTickCount() - CharData.pots.get(type).tick);
+ }
+
+ console.debug(CharData.pots);
+ } catch (e) {
+ console.warn(e);
+ }
+
+ return true;
+ }
+
+ return false;
+ };
+
+ // ~~~~~~~~~~~~~~~ //
+ // Event functions //
+ // ~~~~~~~~~~~~~~~ //
+
+ /**
+ * Handle keyUp events
+ * @param {number} key
+ */
+ const keyEvent = function (key) {
+ switch (key) {
+ case sdk.keys.F10:
+ case sdk.keys.PauseBreak: // pause default.dbj
+ togglePause();
+
+ break;
+ case sdk.keys.Numpad0: // stop profile without logging character
+ Developer.logPerformance && Tracker.update();
+ console.log("ÿc8Kolbot-SoloPlay: ÿc1Stopping profile");
+ delay(rand(2e3, 5e3));
+ D2Bot.stop(me.profile, true);
+
+ break;
+ case sdk.keys.End: // stop profile and log character
+ Developer.logEquipped ? MuleLogger.logEquippedItems() : MuleLogger.logChar();
+ Developer.logPerformance && Tracker.update();
+
+ delay(rand(Config.QuitListDelay[0] * 1e3, Config.QuitListDelay[1] * 1e3));
+ D2Bot.printToConsole(me.profile + " - end run " + me.gamename);
+ D2Bot.stop(me.profile, true);
+
+ break;
+ case sdk.keys.Delete: // quit current game
+ exit();
+
+ break;
+ case sdk.keys.Insert: // reveal level
+ me.overhead("Revealing " + getAreaName(me.area));
+ revealLevel(true);
+
+ break;
+ case sdk.keys.NumpadPlus: // log stats
+ showConsole();
+
+ console.log("ÿc8My stats :: " + Common.Toolsthread.getStatsString(me));
+ let merc = me.getMerc();
+ !!merc && console.log("ÿc8Merc stats :: " + Common.Toolsthread.getStatsString(merc));
+ console.log("//------ÿc8SoloWants.needListÿc0-----//");
+ console.log(SoloWants.needList);
+
+ break;
+ case sdk.keys.Numpad5: // force automule check
+ if (AutoMule.getInfo() && AutoMule.getInfo().hasOwnProperty("muleInfo")) {
+ if (AutoMule.getMuleItems().length > 0) {
+ console.log("ÿc2Mule triggered");
+ scriptBroadcast("mule");
+ exit();
+ } else {
+ me.overhead("No items to mule.");
+ }
+ } else {
+ me.overhead("Profile not enabled for muling.");
+ }
+
+ break;
+ case sdk.keys.Numpad6: // log character to char viewer
+ Developer.logEquipped ? MuleLogger.logEquippedItems() : MuleLogger.logChar();
+ me.overhead("Logged char: " + me.name);
+
+ break;
+ case sdk.keys.NumpadDash:
+ {
+ let itemToCheck = Game.getSelectedUnit();
+ if (!!itemToCheck) {
+ D2Bot.printToConsole("getTier: " + NTIP.GetTier(itemToCheck));
+ D2Bot.printToConsole("tierscore: " + tierscore(itemToCheck));
+ D2Bot.printToConsole("getSecondaryTier: " + NTIP.GetSecondaryTier(itemToCheck));
+ D2Bot.printToConsole("secondarytierscore: " + secondaryscore(itemToCheck));
+ D2Bot.printToConsole("charmTier: " + NTIP.GetCharmTier(itemToCheck));
+ D2Bot.printToConsole("charmscore: " + charmscore(itemToCheck));
+ D2Bot.printToConsole("getMercTier: " + NTIP.GetMercTier(itemToCheck));
+ D2Bot.printToConsole("mercscore: " + mercscore(itemToCheck));
+ console.log(itemToCheck.fname + " info printed to console");
+ }
+ }
+
+ break;
+ case sdk.keys.NumpadDecimal: // dump item info
+ {
+ let [itemString, charmString, generalString] = ["", "", ""];
+ let itemToCheck = Game.getSelectedUnit();
+ if (!!itemToCheck) {
+ let special = "";
+ if (itemToCheck.itemType === sdk.items.type.Ring) {
+ special = (
+ " | ÿc4TierLHS: ÿc0" + tierscore(itemToCheck, 1, sdk.body.RingRight)
+ + " | ÿc4TierRHS: ÿc0" + tierscore(itemToCheck, 1, sdk.body.RingLeft)
+ );
+ }
+ itemString = (
+ "ÿc4MaxQuantity: ÿc0" + NTIP.getMaxQuantity(itemToCheck)
+ + " | ÿc4ItemsOwned: ÿc0" + me.getOwned(itemToCheck).length
+ + " | ÿc4Tier: ÿc0" + NTIP.GetTier(itemToCheck) + (special ? special : "")
+ + " | ÿc4SecondaryTier: ÿc0" + NTIP.GetSecondaryTier(itemToCheck)
+ + " | ÿc4MercTier: ÿc0" + NTIP.GetMercTier(itemToCheck) + "\n"
+ + "ÿc4AutoEquipKeepCheck: ÿc0" + Item.autoEquipCheck(itemToCheck, true)
+ + " | ÿc4AutoEquipCheckSecondary: ÿc0" + Item.autoEquipCheckSecondary(itemToCheck)
+ + " | ÿc4AutoEquipKeepCheckMerc: ÿc0" + Item.autoEquipCheckMerc(itemToCheck, true)
+ + "\nÿc4Cubing Item: ÿc0" + Cubing.keepItem(itemToCheck)
+ + " | ÿc4Runeword Item: ÿc0" + Runewords.keepItem(itemToCheck)
+ + " | ÿc4Crafting Item: ÿc0" + CraftingSystem.keepItem(itemToCheck)
+ + " | ÿc4SoloWants Item: ÿc0" + SoloWants.keepItem(itemToCheck)
+ + "\nÿc4ItemType: ÿc0" + itemToCheck.itemType
+ + "| ÿc4Classid: ÿc0" + itemToCheck.classid
+ + "| ÿc4Quality: ÿc0" + itemToCheck.quality
+ + "| ÿc4Ilvl: ÿc0" + itemToCheck.ilvl
+ + "| ÿc4Gid: ÿc0" + itemToCheck.gid
+ );
+ if (itemToCheck.isCharm) {
+ charmString = (
+ "ÿc4InvoQuantity: ÿc0" + NTIP.getInvoQuantity(itemToCheck)
+ + " | ÿc4hasStats: ÿc0" + NTIP.hasStats(itemToCheck)
+ + " | ÿc4FinalCharm: ÿc0" + CharmEquip.isFinalCharm(itemToCheck) + "\n"
+ + "ÿc4CharmType: ÿc0" + CharmEquip.getCharmType(itemToCheck)
+ + " | ÿc4AutoEquipCharmCheck: ÿc0" + CharmEquip.check(itemToCheck)
+ + " | ÿc4CharmTier: ÿc0" + NTIP.GetCharmTier(itemToCheck)
+ );
+ }
+ let pResult = Pickit.checkItem(itemToCheck);
+ let pString = "ÿc4Pickit: ÿc0" + pResult.result + " ÿc7Line: ÿc0" + pResult.line + "\n";
+ let sResult = NTIP.CheckItem(itemToCheck, NTIP.SoloList, true);
+ let sString = "ÿc4SoloWants: ÿc0" + sResult.result + " ÿc7Line: ÿc0" + sResult.line + "\n";
+ let nResult = NTIP.CheckItem(itemToCheck, NTIP.NoTier, true);
+ let nString = "ÿc4Solo-NoTier: ÿc0" + nResult.result + " ÿc7Line: ÿc0" + nResult.line + "\n";
+ let cResult = NTIP.CheckItem(itemToCheck, NTIP.CheckList, true);
+ let cString = "ÿc4Nip CheckList: ÿc0" + cResult.result + " ÿc7Line: ÿc0" + cResult.line + "\n";
+ generalString = (
+ "ÿc4ItemName: ÿc0" + itemToCheck.fname.split("\n").reverse().join(" ").replace(/ÿc[0-9!"+<;.*]/, "")
+ + "\n" + pString
+ + "\n" + sString
+ + "\n" + nString
+ + "\n" + cString
+ );
+ }
+
+ console.log("ÿc8Kolbot-SoloPlay: ÿc2Item Info Start");
+ console.log(itemString);
+ if (charmString) {
+ console.log("ÿc8Kolbot-SoloPlay: ÿc2Charm Info Start");
+ console.log(charmString);
+ }
+ console.log("ÿc8Kolbot-SoloPlay: ÿc2General Info Start");
+ console.log(generalString);
+ console.log("ÿc8Kolbot-SoloPlay: ÿc1****************Info End****************");
+ }
+
+ break;
+ case sdk.keys.Numpad9: // get nearest preset unit id
+ console.log(Common.Toolsthread.getNearestPreset());
+
+ break;
+ case sdk.keys.NumpadStar: // precast
+ Precast.doPrecast(true);
+
+ break;
+ case sdk.keys.NumpadSlash: // re-load default
+ console.log("ÿc8ToolsThread :: " + sdk.colors.Red + "Stopping threads and waiting 5 seconds to restart");
+ stopDefault() && delay(1e3);
+ load("libs/SoloPlay/Threads/Reload.js");
+
+ break;
+ }
+ };
+
+ /**
+ * Handle game events
+ * @param {number} mode
+ * @param {number} [param1]
+ * @param {number} [param2]
+ * @param {string} [name1]
+ * @param {string} [name2]
+ */
+ const gameEvent = function (mode, param1, param2, name1, name2) {
+ switch (mode) {
+ case 0x00: // "%Name1(%Name2) dropped due to time out."
+ case 0x01: // "%Name1(%Name2) dropped due to errors."
+ case 0x03: // "%Name1(%Name2) left our world. Diablo's minions weaken."
+ Config.DebugMode.Stack && mode === 0 && D2Bot.printToConsole(name1 + " timed out, check their logs");
+
+ if (Config.QuitList.includes(name1) || Config.QuitList.some(str => String.isEqual(str, "all"))) {
+ console.log(name1 + (mode === 0 ? " timed out" : " left"));
+
+ if (typeof quitListDelayTime === "undefined" && Config.QuitListDelay.length > 0) {
+ let [min, max] = Config.QuitListDelay.sort((a, b) => a - b).map(s => Time.seconds(s));
+
+ quitListDelayTime = getTickCount() + rand(min, max);
+ } else {
+ quitListDelayTime = getTickCount();
+ }
+
+ quitFlag = true;
+ }
+
+ Config.AntiHostile && scriptBroadcast("remove " + name1);
+
+ break;
+ case 0x06:
+ // "%Name1 was Slain by %Name2"
+ if (Config.AntiHostile && param2 === 0x00 && name2 === me.name) {
+ scriptBroadcast("mugshot " + name1);
+ }
+
+ break;
+ case 0x07:
+ // "%Player has declared hostility towards you."
+ if (Config.AntiHostile && param2 === 0x03) {
+ scriptBroadcast("findHostiles");
+ }
+
+ break;
+ case 0x11: // "%Param1 Stones of Jordan Sold to Merchants"
+ if (Config.DCloneQuit === 2) {
+ D2Bot.printToConsole("SoJ sold in game. Leaving.");
+ quitFlag = true;
+
+ break;
+ }
+
+ // Only do this in expansion
+ if (Config.SoJWaitTime && !me.classic) {
+ !!me.gameserverip && D2Bot.printToConsole(param1 + " Stones of Jordan Sold to Merchants on IP " + me.gameserverip.split(".")[3], sdk.colors.D2Bot.DarkGold);
+ Messaging.sendToScript("libs/SoloPlay/SoloPlay.js", "soj");
+ }
+
+ break;
+ case 0x12: // "Diablo Walks the Earth"
+ if (Config.DCloneQuit > 0) {
+ D2Bot.printToConsole("Diablo walked in game. Leaving.");
+ quitFlag = true;
+
+ break;
+ }
+
+ // Only do this in expansion
+ if (Config.StopOnDClone && !me.classic && me.hell) {
+ D2Bot.printToConsole("Diablo Walks the Earth", sdk.colors.D2Bot.DarkGold);
+ SoloEvents.cloneWalked = true;
+ togglePause();
+ Town.goToTown();
+ showConsole();
+ myPrint("ÿc4Diablo Walks the Earth");
+ me.maxgametime += (30 * 1000 * 60); // Add 30 minutes to current maxgametime
+ Config.KillDclone && Messaging.sendToScript(SoloEvents.filePath, "killdclone");
+ }
+
+ break;
+ case 0x0f: // "Realm going down in %Param1 minutes."
+ {
+ let realmDownStr = getLocaleString(sdk.locale.text.RealmGoingDownInXMinutes).replace("%d", param1);
+ D2Bot.printToConsole(realmDownStr, sdk.colors.D2Bot.DarkGold);
+ }
+ break;
+ }
+ };
+
+ /**
+ * Handle script/thread communications
+ * @param {string} msg
+ * @returns {void}
+ */
+ const scriptEvent = function (msg) {
+ if (!msg || typeof msg !== "string") return;
+
+ let obj;
+
+ if (msg.includes("--")) {
+ let sub = msg.match(/\w+?--/gm).first();
+
+ switch (sub) {
+ case "config--":
+ console.debug("update config");
+ Config = JSON.parse(msg.split("config--")[1]);
+
+ return;
+ case "skill--":
+ console.debug("update skillData");
+ obj = JSON.parse(msg.split("skill--")[1]);
+ Misc.updateRecursively(CharData.skillData, obj);
+
+ return;
+ case "data--":
+ console.debug("update me.data");
+ obj = JSON.parse(msg.split("data--")[1]);
+ Misc.updateRecursively(me.data, obj);
+
+ return;
+ }
+ }
+
+ switch (msg) {
+ case "remake":
+ Developer.testingMode.enabled && (quitFlag = true);
+
+ break;
+ case "toggleQuitlist":
+ canQuit = !canQuit;
+
+ break;
+ case "quit":
+ quitFlag = true;
+
+ break;
+ case "restart":
+ restart = true;
+
+ break;
+ case "test":
+ {
+ console.debug(sdk.colors.Green + "//-----------DataDump Start-----------//",
+ "\nÿc8ThreadData ::\n", getScript(true),
+ "\nÿc8MainData ::\n", me.data,
+ "\nÿc8BuffData ::\n", CharData.pots,
+ "\nÿc8SkillData ::\n", CharData.skillData,
+ "\nÿc8GlobalVariabls ::\n", Object.keys(global),
+ "\n" + sdk.colors.Red + "//-----------DataDump End-----------//");
+ }
+ break;
+ // ignore common scriptBroadcast messages that aren't relevent to this thread
+ case "mule":
+ case "muleTorch":
+ case "muleAnni":
+ case "torch":
+ case "crafting":
+ case "getMuleMode":
+ case "pingquit":
+ case "townCheck":
+ break;
+ default:
+ try {
+ obj = JSON.parse(msg);
+ } catch (e) {
+ return;
+ }
+
+ if (obj) {
+ obj.hasOwnProperty("currScript") && (debugInfo.currScript = obj.currScript);
+ obj.hasOwnProperty("lastAction") && (debugInfo.lastAction = obj.lastAction);
+ // D2Bot.store(JSON.stringify(debugInfo));
+ DataFile.updateStats("debugInfo", JSON.stringify(debugInfo));
+ }
+
+ break;
+ }
+ };
+
+ // Cache variables to prevent a bug where d2bs loses the reference to Config object
+ Config = copyObj(Config);
+ tick = getTickCount();
+
+ // getUnit test
+ getUnit(-1) === null && console.warn("getUnit bug detected");
+
+ addEventListener("keyup", keyEvent);
+ addEventListener("gameevent", gameEvent);
+ addEventListener("scriptmsg", scriptEvent);
+
+ Config.QuitListMode > 0 && Common.Toolsthread.initQuitList();
+ !Array.isArray(Config.QuitList) && (Config.QuitList = [Config.QuitList]);
+
+ let myAct = me.act;
+
+ if (Developer.overlay && !Developer.logPerformance) {
+ console.warn("Without logPerformance set, the overlay will only show partial values");
+ }
+
+ const Worker = require("../../modules/Worker");
+ const diffShort = ["Norm", "Night", "Hell"][me.diff];
+
+ // Start worker - handles overlay and d2bot# profile display
+ Worker.runInBackground.display = (new function () {
+ let _timeout = 0;
+ let gameTracker;
+
+ function timer () {
+ const currInGame = (getTickCount() - me.gamestarttime);
+ let timeStr = " (Time: " + Time.format(currInGame) + ") ";
+
+ if (Developer.displayClockInConsole && Developer.logPerformance) {
+ try {
+ gameTracker === undefined && (gameTracker = Tracker.readObj(Tracker.GTPath));
+ let [tTime, tInGame, tDays] = [
+ (gameTracker.Total + currInGame),
+ (gameTracker.InGame + currInGame),
+ (gameTracker.Total + currInGame)
+ ];
+ let [totalTime, totalInGame, totalDays] = [
+ Time.format(tTime),
+ Time.format(tInGame),
+ Tracker.totalDays(tDays)
+ ];
+ timeStr += ("(Days: " + totalDays + ") (Total: " + totalTime + ") (IG: " + totalInGame + ") (OOG: " + Time.format(gameTracker.OOG) + ")");
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ return timeStr;
+ }
+
+ this.run = function () {
+ if (getTickCount() - _timeout < 500) return true;
+ _timeout = getTickCount();
+
+ if (me.gameReady) {
+ // handle d2bot# profile display
+ let statusString = "";
+
+ try {
+ statusString = [
+ (me.name + " | "),
+ ("Lvl: " + me.charlvl),
+ (" (" + Experience.progress() + "%) "),
+ ("(Diff: " + diffShort + ") "),
+ ("(A: " + getAreaName(me.area) + ") "),
+ ("(G: " + me.gold + ") "),
+ ("(F: " + me.FR + "/C: " + me.CR + "/L: " + me.LR + "/P: " + me.PR + ")"),
+ ].join("");
+
+ D2Bot.updateStatus(statusString + timer());
+ } catch (e) {
+ console.error(e);
+ }
+
+ // handle overlay
+ if (Developer.overlay) {
+ try {
+ if (me.ingame && me.gameReady && me.area) {
+ Overlay.update(quitFlag);
+
+ if (me.act !== myAct) {
+ Overlay.flush();
+ myAct = me.act;
+ Overlay.update(quitFlag);
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ console.log("Overlay disabled");
+ D2Bot.printToConsole("Overlay disabled", sdk.colors.D2Bot.Red);
+ Developer.overlay = false;
+ }
+ }
+ }
+
+ return true;
+ };
+ }).run;
+
+ // Start ToolsThread
+ while (true) {
+ try {
+ if (me.gameReady && !me.inTown) {
+ // todo - build potion list only once per iteration
+ Config.UseHP > 0 && me.hpPercent < Config.UseHP && drinkPotion(sdk.items.type.HealingPotion);
+ Config.UseRejuvHP > 0 && me.hpPercent < Config.UseRejuvHP && drinkPotion(sdk.items.type.RejuvPotion);
+
+ if (Config.LifeChicken > 0 && me.hpPercent <= Config.LifeChicken && !me.inTown) {
+ if (!Developer.hideChickens) {
+ D2Bot.printToConsole("Life Chicken (" + me.hp + "/" + me.hpmax + ")" + Attack.getNearestMonster() + " in " + getAreaName(me.area) + ". Ping: " + me.ping, sdk.colors.D2Bot.Red);
+ }
+ exit(true);
+
+ return true;
+ }
+
+ Config.UseMP > 0 && me.mpPercent < Config.UseMP && drinkPotion(sdk.items.type.ManaPotion);
+ Config.UseRejuvMP > 0 && me.mpPercent < Config.UseRejuvMP && drinkPotion(sdk.items.type.RejuvPotion);
+
+ (me.staminaPercent <= 20 || me.walking) && drinkSpecialPotion(sdk.items.StaminaPotion);
+ me.getState(sdk.states.Poison) && drinkSpecialPotion(sdk.items.AntidotePotion);
+ if (me.getState(sdk.states.Frozen) || me.getState(sdk.states.FrozenSolid)) {
+ drinkSpecialPotion(sdk.items.ThawingPotion);
+ }
+
+ if (Config.ManaChicken > 0 && me.mpPercent <= Config.ManaChicken && !me.inTown) {
+ if (!Developer.hideChickens) {
+ D2Bot.printToConsole("Mana Chicken: (" + me.mp + "/" + me.mpmax + ") in " + getAreaName(me.area), sdk.colors.D2Bot.Red);
+ }
+ exit(true);
+
+ return true;
+ }
+
+ 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)) {
+ if (!Developer.hideChickens) {
+ D2Bot.printToConsole("Irom Golem Chicken in " + getAreaName(me.area), sdk.colors.D2Bot.Red);
+ }
+ exit(true);
+
+ return true;
+ }
+ }
+ }
+
+ 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) {
+ if (!Developer.hideChickens) {
+ D2Bot.printToConsole("Merc Chicken in " + getAreaName(me.area), sdk.colors.D2Bot.Red);
+ }
+ exit(true);
+
+ return true;
+ }
+
+ mercHP < Config.UseMercHP && giveMercPotion(sdk.items.type.HealingPotion);
+ mercHP < Config.UseMercRejuv && giveMercPotion(sdk.items.type.RejuvPotion);
+ }
+ }
+ }
+
+ if (Config.ViperCheck && getTickCount() - tick >= 250) {
+ if (Common.Toolsthread.checkVipers()) {
+ D2Bot.printToConsole("Revived Tomb Vipers found. Leaving game.", sdk.colors.D2Bot.Red);
+ quitFlag = true;
+ }
+
+ tick = getTickCount();
+ }
+
+ Common.Toolsthread.checkPing(true) && (quitFlag = true);
+ }
+ } catch (e) {
+ Misc.errorReport(e, "ToolsThread");
+
+ quitFlag = true;
+ }
+
+ if (me.maxgametime - (getTickCount() - me.gamestarttime) < 10e3) {
+ console.log("Max game time reached");
+ quitFlag = true;
+ }
+
+ if (quitFlag && canQuit && (typeof quitListDelayTime === "undefined" || getTickCount() >= quitListDelayTime)) {
+ Common.Toolsthread.checkPing(false); // In case of quitlist triggering first
+ exit();
+
+ break;
+ }
+
+ !!restart && restartGame();
+
+ if (debugInfo.area !== getAreaName(me.area)) {
+ debugInfo.area = getAreaName(me.area);
+ DataFile.updateStats("debugInfo", JSON.stringify(debugInfo));
+ }
+
+ delay(20);
+ }
+
+ return true;
}
diff --git a/libs/SoloPlay/Threads/TownChicken.js b/libs/SoloPlay/Threads/TownChicken.js
deleted file mode 100644
index 2b912176..00000000
--- a/libs/SoloPlay/Threads/TownChicken.js
+++ /dev/null
@@ -1,470 +0,0 @@
-/**
-* @filename TownChicken.js
-* @author kolton, theBGuy (modified for Kolbot-SoloPlay)
-* @desc modified TownChicken for use with Kolbot-SoloPlay
-*
-*/
-js_strict(true);
-
-include("json2.js");
-include("NTItemParser.dbl");
-include("OOG.js");
-include("Gambling.js");
-include("CraftingSystem.js");
-include("common/util.js");
-include("common/Attack.js");
-include("common/Common.js");
-include("common/Cubing.js");
-include("common/Config.js");
-include("common/CollMap.js");
-include("common/misc.js");
-include("common/Pickit.js");
-include("common/Pather.js");
-include("common/Precast.js");
-include("common/Prototypes.js");
-include("common/Runewords.js");
-include("common/Town.js");
-// Include SoloPlay's librarys
-include("SoloPlay/Tools/Developer.js");
-include("SoloPlay/Tools/Tracker.js");
-include("SoloPlay/Tools/CharData.js");
-include("SoloPlay/Tools/SoloIndex.js");
-include("SoloPlay/Functions/ConfigOverrides.js");
-include("SoloPlay/Functions/Globals.js");
-
-SetUp.include();
-
-function main() {
- let [townCheck, fastTown] = [false, false];
- console.log("ÿc8Kolbot-SoloPlayÿc0: Start TownChicken thread");
-
- // Init config and attacks
- D2Bot.init();
- Config.init();
- Pickit.init();
- Attack.init();
- Storage.Init();
- CraftingSystem.buildLists();
- Runewords.init();
- Cubing.init();
-
- new Overrides.Override(Attack, Attack.getNearestMonster, function (orignal, givenSettings = {}) {
- const settings = Object.assign({
- skipBlocked: false,
- skipImmune: false
- }, givenSettings);
- let monster = orignal(settings);
-
- if (monster) {
- console.log("ÿc9TownChickenÿc0 :: Closest monster to me: " + monster.name + " | Monster classid: " + monster.classid);
- return monster.classid;
- }
-
- return -1;
- }).apply();
-
- NodeAction.go = function () {
- return;
- };
-
- Pather.usePortal = function (targetArea, owner, unit, dummy) {
- if (targetArea && me.area === 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");
-
- let 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) {
- portal.distance > 21 && (me.inTown && me.act === 5 ? Town.move("portalspot") : Pather.moveNearUnit(portal, 20));
- if (Skill.cast(sdk.skills.Telekinesis, sdk.skills.hand.Right, portal)
- && Misc.poll(() => targetArea ? me.inArea(targetArea) : me.area !== preArea)) {
- Pather.lastPortalTick = getTickCount();
- delay(100);
-
- return true;
- }
- } else {
- portal.distance > 5 && (i < 3 ? Pather.moveNearUnit(portal, 4, false) : Pather.moveToUnit(portal));
-
- if (getTickCount() - this.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) {
- this.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(() => me.area !== preArea, 1000, 100)) {
- this.lastPortalTick = getTickCount();
- delay(100);
-
- return true;
- }
- }
- Packet.flash(me.gid);
- }
-
- delay(250);
- }
-
- return (targetArea ? me.inArea(targetArea) : me.area !== preArea);
- };
-
- Pather.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 (this.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((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);
- }
- } else {
- // check dummy
- let dummy = getUnits(sdk.unittype.Object, "portal").filter(p => p.name === "Dummy").first();
- if (dummy) {
- console.debug(dummy);
- if (use) return Pather.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;
- };
-
- Town.goToTown = function (act = 0, wpmenu = false) {
- if (!me.inTown) {
- const townArea = sdk.areas.townOf(me.act);
- try {
- !Pather.makePortal(true) && console.warn("Town.goToTown: Failed to make TP");
- if (!me.inTown && !Pather.usePortal(townArea, me.name)) {
- console.warn("Town.goToTown: Failed to take TP");
- if (!me.inTown && !Pather.usePortal(sdk.areas.townOf(me.area))) throw new Error("Town.goToTown: Failed to take TP");
- }
- } catch (e) {
- let tpTool = me.getTpTool();
- if (!tpTool && Misc.getPlayerCount() <= 1) {
- Misc.errorReport(new Error("Town.goToTown: Failed to go to town and no tps available. Restart."));
- scriptBroadcast("quit");
- } else {
- if (!Misc.poll(() => {
- if (me.inTown) 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;
- };
-
- Town.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");
- }
-
- // not an essential function -> handle thrown errors
- me.cancelUIFlags();
- try {
- Town.goToTown();
- } catch (e) {
- return false;
- }
-
- Town.doChores();
-
- console.debug("Current act: " + me.act + " Prev Act: " + preAct);
- me.act !== preAct && Town.goToTown(preAct);
- Town.move("portalspot");
-
- if (!Pather.usePortal(preArea, me.name)) {
- try {
- Pather.usePortal(null, me.name);
- } catch (e) {
- throw new Error("Town.visitTown: Failed to go back from town");
- }
- }
-
- console.log("ÿc8End ÿc0:: ÿc8visitTown - currentArea: " + Pather.getAreaName(me.area));
-
- return me.area === preArea;
- };
-
- this.togglePause = function () {
- let scripts = ["libs/SoloPlay/SoloPlay.js", "tools/antihostile.js"];
-
- for (let i = 0; i < scripts.length; i++) {
- let script = getScript(scripts[i]);
-
- if (script) {
- if (script.running) {
- scripts[i] === "libs/SoloPlay/SoloPlay.js" && console.log("ÿc8TownChicken:: ÿc1Pausing " + scripts[i]);
-
- script.pause();
- } else {
- if (scripts[i] === "libs/SoloPlay/SoloPlay.js") {
- // don't resume if dclone walked
- if (!SoloEvents.cloneWalked) {
- console.log("ÿc8TownChicken :: ÿc2Resuming threads");
- script.resume();
- }
- } else {
- script.resume();
- }
- }
- }
- }
-
- return true;
- };
-
- this.scriptEvent = function (msg) {
- let obj;
-
- if (msg && typeof msg === "string" && msg !== "") {
- switch (msg) {
- // ignore common scriptBroadcast messages that aren't relevent to this thread
- case "mule":
- case "muleTorch":
- case "muleAnni":
- case "torch":
- case "crafting":
- case "getMuleMode":
- case "pingquit":
- return;
- case "fastTown":
- fastTown = true;
-
- return;
- case "townCheck":
- switch (me.area) {
- case sdk.areas.ArreatSummit:
- case sdk.areas.UberTristram:
- console.warn("Don't tp from " + Pather.getAreaName(me.area));
- return;
- default:
- console.log("townCheck message recieved. First check passed.");
- townCheck = true;
-
- return;
- }
- case "quit":
- //quitFlag = true;
- // Maybe stop townChicken thread? Would that keep us from the crash that happens when we try to leave game while townChickening
-
- break;
- default:
- break;
- }
-
- switch (true) {
- case msg.substring(0, 8) === "config--":
- console.debug("update config");
- Config = JSON.parse(msg.split("config--")[1]);
-
- break;
- case msg.substring(0, 7) === "skill--":
- console.debug("update skillData");
- obj = JSON.parse(msg.split("skill--")[1]);
- Misc.updateRecursively(CharData.skillData, obj);
-
- break;
- case msg.substring(0, 6) === "data--":
- console.debug("update myData");
- obj = JSON.parse(msg.split("data--")[1]);
- Misc.updateRecursively(myData, obj);
-
- break;
- case msg.toLowerCase() === "test":
- console.debug(sdk.colors.Green + "//-----------DataDump Start-----------//\nÿc8MainData ::\n",
- myData, "\nÿc8BuffData ::\n", CharData.buffData, "\nÿc8SkillData ::\n", CharData.skillData, "\n" + sdk.colors.Red + "//-----------DataDump End-----------//");
-
- break;
- }
- }
- };
-
- addEventListener("scriptmsg", this.scriptEvent);
- let tGuard = getScript("libs/SoloPlay/Modules/TownGuard.js");
- !!tGuard && tGuard.running && tGuard.stop();
- Developer.debugging.showStack.profiles.some(profile => profile.toLowerCase() === "all" || profile.toLowerCase() === me.profile.toLowerCase()) && require("../Modules/TownGuard");
-
- // START
- // test for getUnit bug
- let test = Game.getMonster();
- test === null && console.warn("getUnit is bugged");
-
- const useHowl = Skill.canUse(sdk.skills.Howl);
- const useTerror = Skill.canUse(sdk.skills.Terror);
-
- Config.DebugMode = true;
-
- while (true) {
- if (!me.inTown && (townCheck || fastTown
- || ((Config.TownHP > 0 && me.hpPercent < Config.TownHP)
- || (Config.TownMP > 0 && me.mpPercent < Config.TownMP)))) {
- // should we exit if we can't tp to town?
- if (townCheck && !me.canTpToTown()) {
- townCheck = false;
-
- continue;
- }
- this.togglePause();
-
- while (!me.gameReady) {
- if (me.dead) {
- scriptBroadcast("quit");
- return false;
- }
- delay(40);
- }
-
- let t4 = getTickCount();
- try {
- myPrint("ÿc8TownChicken :: ÿc0Going to town");
- Messaging.sendToScript("libs/SoloPlay/Threads/EventThread.js", "townchickenOn");
- [Attack.stopClear, SoloEvents.townChicken] = [true, true];
-
- // determine if this is really worth it
- if (useHowl || useTerror) {
- if ([156, 211, 242, 243, 544, 571, 345].indexOf(Attack.getNearestMonster()) === -1) {
- if (useHowl && Skill.getManaCost(130) < me.mp) {
- Skill.cast(130, sdk.skills.hand.Right);
- }
-
- if (useTerror && Skill.getManaCost(77) < me.mp) {
- Skill.cast(77, sdk.skills.hand.Right, Attack.getNearestMonster({skipImmune: false}));
- }
- }
- }
-
- Town.visitTown();
- } catch (e) {
- Misc.errorReport(e, "TownChicken.js");
- scriptBroadcast("quit");
-
- return false;
- } finally {
- Packet.flash(me.gid, 100);
- console.log("ÿc8TownChicken :: Took: " + Time.format(getTickCount() - t4) + " to visit town");
- this.togglePause();
- Messaging.sendToScript("libs/SoloPlay/Threads/EventThread.js", "townchickenOff");
- [Attack.stopClear, SoloEvents.townChicken, townCheck, fastTown] = [false, false, false, false];
- }
- }
-
- delay(50);
- }
-}
diff --git a/libs/SoloPlay/Tools/CharData.js b/libs/SoloPlay/Tools/CharData.js
index 127c7359..83b5769d 100644
--- a/libs/SoloPlay/Tools/CharData.js
+++ b/libs/SoloPlay/Tools/CharData.js
@@ -7,341 +7,342 @@
includeIfNotIncluded("SoloPlay/Tools/Tracker.js");
-const CharData = {
- filePath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-CharData.json",
- threads: ["libs/SoloPlay/SoloPlay.js", "libs/SoloPlay/Threads/TownChicken.js", "libs/SoloPlay/Threads/ToolsThread.js", "libs/SoloPlay/Threads/EventThread.js"],
- default: {
- initialized: false,
- normal: {
- respecUsed: false,
- imbueUsed: false,
- socketUsed: false,
- },
- nightmare: {
- respecUsed: false,
- imbueUsed: false,
- socketUsed: false,
- },
- hell: {
- respecUsed: false,
- imbueUsed: false,
- socketUsed: false,
- },
- me: {
- task: "",
- startTime: 0,
- charName: "",
- classid: -1,
- level: 1,
- strength: 0,
- dexterity: 0,
- currentBuild: "Start",
- finalBuild: "",
- highestDifficulty: "Normal",
- setDifficulty: "Normal",
- charms: {},
- charmGids: [],
- },
- merc: {
- act: 1,
- classid: 271,
- difficulty: 0,
- strength: 0,
- dexterity: 0,
- type: "",
- gear: [],
- }
- },
-
- loginData: {
- filePath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-LoginData.json",
- default: {Acc: "", Pass: "", Char: "", existing: false},
-
- create: function () {
- let obj = Object.assign({}, this.default);
- let string = JSON.stringify(obj, null, 2);
-
- if (!FileTools.exists("libs/SoloPlay/Data/" + me.profile)) {
- let folder = dopen("libs/SoloPlay/Data");
- folder && folder.create(me.profile);
- }
-
- Misc.fileAction(this.filePath, 1, string);
-
- return obj;
- },
-
- getObj: function () {
- if (!FileTools.exists(this.filePath)) return CharData.loginData.create();
-
- let obj;
- let string = Misc.fileAction(this.filePath, 0);
-
- try {
- obj = JSON.parse(string);
- } catch (e) {
- // If we failed, file might be corrupted, so create a new one
- obj = this.create();
- }
-
- return obj ? obj : this.default;
- },
-
- getStats: function () {
- let obj = this.getObj();
- return Misc.clone(obj);
- },
-
- updateData: function (arg, property, value) {
- let obj = this.getObj();
- typeof arg !== "string" && (arg = arg.toString());
- typeof arg === "string" && (arg = arg.toLowerCase());
-
- if (typeof property === "object") {
- obj = Object.assign(obj, property);
- return Misc.fileAction(this.filePath, 1, JSON.stringify(obj, null, 2));
- }
-
- if (!!obj[arg] && obj[arg].hasOwnProperty(property)) {
- obj[arg][property] = value;
- return Misc.fileAction(this.filePath, 1, JSON.stringify(obj, null, 2));
- }
-
- return false;
- },
- },
-
- charmData: {
- getCountInfo: function () {
- const finalCharmKeys = Object.keys(myData.me.charms);
- let [curr, max] = [0, 0];
-
- for (let i = 0; i < finalCharmKeys.length; i++) {
- let cKey = finalCharmKeys[i];
- if (myData.me.charms[cKey].classid === this.id) {
- curr += myData.me.charms[cKey].have.length;
- max += myData.me.charms[cKey].max;
- }
- }
-
- return {
- curr: curr,
- max: max
- };
- },
- small: {
- id: sdk.items.SmallCharm,
- },
- large: {
- id: sdk.items.LargeCharm,
- },
- grand: {
- id: sdk.items.GrandCharm,
- }
- },
-
- buffData: {
- stamina: {
- tick: 0,
- duration: 0,
- active: function () {
- return me.getState(sdk.states.StaminaPot);
- },
- timeLeft: function () {
- return this.duration > 0 ? this.duration - (getTickCount() - this.tick) : 0;
- },
- need: function () {
- return (!this.active() || this.timeLeft() < Time.minutes(5));
- },
- },
-
- thawing: {
- tick: 0,
- duration: 0,
- active: function () {
- return me.getState(sdk.states.Thawing);
- },
- timeLeft: function () {
- return this.duration > 0 ? this.duration - (getTickCount() - this.tick) : 0;
- },
- need: function () {
- return (me.coldRes < 75 && (!this.active() || this.timeLeft() < Time.minutes(5)));
- },
- },
-
- antidote: {
- tick: 0,
- duration: 0,
- active: function () {
- return me.getState(sdk.states.Antidote);
- },
- timeLeft: function () {
- return this.duration > 0 ? this.duration - (getTickCount() - this.tick) : 0;
- },
- need: function () {
- // don't really like the hardcoded time value of 5 minutes, its okay but feel like it should be more dynamic
- return (me.poisonRes < 75 && (!this.active() || this.timeLeft() < Time.minutes(5)));
- },
- },
-
- update: function () {
- const obj = JSON.stringify(Misc.copy(this));
- const myThread = getScript(true).name;
- CharData.threads.forEach(function (script) {
- let curr = getScript(script);
- if (curr && myThread !== curr.name) {
- curr.send("buff--" + obj);
- }
- });
- },
- },
-
- skillData: {
- skills: [],
- currentChargedSkills: [],
- chargedSkills: [],
- chargedSkillsOnSwitch: [],
- bowData: {
- initialized: false,
- bowOnSwitch: false,
- bowGid: 0,
- bowType: 0,
- arrows: 0,
- quiverType: 0,
- setBowInfo: function (bow, init = false) {
- if (bow === undefined) return;
- this.bowGid = bow.gid;
- this.bowType = bow.itemType;
- this.bowOnSwitch = bow.isOnSwap;
- SetUp.bowQuiver();
- init && (this.initialized = true);
- !init && CharData.skillData.update();
- },
- setArrowInfo: function (quiver) {
- if (quiver === undefined) return;
- this.arrows = Math.floor((quiver.getStat(sdk.stats.Quantity) * 100) / getBaseStat("items", quiver.classid, "maxstack"));
- this.quiverType = quiver.itemType;
- },
- resetBowData: function () {
- this.bowOnSwitch = false;
- [this.bowGid, this.bowType, this.arrows, this.quiverType] = [0, 0, 0, 0];
- NTIP.resetRuntimeList();
- CharData.skillData.update();
- },
- },
-
- init: function (skillIds, mainSkills, switchSkills) {
- this.currentChargedSkills = skillIds.slice(0);
- this.chargedSkills = mainSkills.slice(0);
- this.chargedSkillsOnSwitch = switchSkills.slice(0);
- this.skills = me.getSkill(4).map((skill) => skill[0]);
- },
-
- update: function () {
- let obj = JSON.stringify(Misc.copy(this));
- let myThread = getScript(true).name;
- CharData.threads.forEach(function (script) {
- let curr = getScript(script);
- if (curr && myThread !== curr.name) {
- curr.send("skill--" + obj);
- }
- });
- },
-
- haveChargedSkill: function (skillid = []) {
- // convert to array if not one
- !Array.isArray(skillid) && (skillid = [skillid]);
- return this.currentChargedSkills.some(s => skillid.includes(s));
- },
-
- haveChargedSkillOnSwitch: function (skillid = 0) {
- return this.chargedSkillsOnSwitch.some(chargeSkill => chargeSkill.skill === skillid);
- }
- },
-
- // updates config obj across all threads - excluding our current
- updateConfig: function () {
- let obj = JSON.stringify(Misc.copy(Config));
- let myThread = getScript(true).name;
- CharData.threads.forEach(function (script) {
- let curr = getScript(script);
- if (curr && myThread !== curr.name) {
- curr.send("config--" + obj);
- }
- });
- },
-
- create: function () {
- let obj = Object.assign({}, this.default);
- let string = JSON.stringify(obj, null, 2);
-
- if (!FileTools.exists("libs/SoloPlay/Data/" + me.profile)) {
- let folder = dopen("libs/SoloPlay/Data");
- folder && folder.create(me.profile);
- }
-
- Misc.fileAction(this.filePath, 1, string);
-
- return obj;
- },
-
- getObj: function () {
- if (!FileTools.exists(this.filePath)) return CharData.create();
-
- let obj;
- let string = Misc.fileAction(this.filePath, 0);
-
- try {
- obj = JSON.parse(string);
- } catch (e) {
- // If we failed, file might be corrupted, so create a new one
- obj = this.create();
- }
-
- return obj ? obj : this.default;
- },
-
- getStats: function () {
- let obj = this.getObj();
- return Misc.clone(obj);
- },
-
- updateData: function (arg, property, value) {
- while (me.ingame && !me.gameReady) {
- delay(100);
- }
-
- console.trace();
-
- let obj = this.getObj();
- typeof arg !== "string" && (arg = arg.toString());
- typeof arg === "string" && (arg = arg.toLowerCase());
-
- if (typeof property === "object") {
- obj = Object.assign(obj, property);
- return Misc.fileAction(this.filePath, 1, JSON.stringify(obj, null, 2));
- }
-
- if (!!obj[arg] && obj[arg].hasOwnProperty(property)) {
- obj[arg][property] = value;
- return Misc.fileAction(this.filePath, 1, JSON.stringify(obj, null, 2));
- }
-
- return false;
- },
-
- delete: function (deleteMain = false) {
- if (deleteMain && FileTools.exists("data/" + me.profile + ".json")) {
- FileTools.remove("data/" + me.profile + ".json");
- }
-
- FileTools.exists(this.filePath) && FileTools.remove(this.filePath);
- FileTools.exists(Tracker.GTPath) && FileTools.remove(Tracker.GTPath);
-
- return !(FileTools.exists(this.filePath) && FileTools.exists("libs/SoloPlay/Data/" + me.profile + ".GameTime" + ".json"));
- },
-};
-
-CharData.charmData.small.getCountInfo = CharData.charmData.getCountInfo.bind(CharData.charmData.small);
-CharData.charmData.large.getCountInfo = CharData.charmData.getCountInfo.bind(CharData.charmData.large);
-CharData.charmData.grand.getCountInfo = CharData.charmData.getCountInfo.bind(CharData.charmData.grand);
+const CharData = (function () {
+ const _create = function () {
+ let obj = Object.assign({}, this._default);
+ let string = JSON.stringify(obj, null, 2);
+
+ if (!FileTools.exists("libs/SoloPlay/Data/" + me.profile)) {
+ let folder = dopen("libs/SoloPlay/Data");
+ folder && folder.create(me.profile);
+ }
+
+ FileAction.write(this.filePath, string);
+
+ return obj;
+ };
+
+ const _getObj = function () {
+ if (!FileTools.exists(this.filePath)) return this.create();
+
+ let obj;
+ let string = FileAction.read(this.filePath);
+
+ try {
+ obj = JSON.parse(string);
+ } catch (e) {
+ // If we failed, file might be corrupted, so create a new one
+ obj = this.create();
+ }
+
+ return obj ? obj : this._default;
+ };
+
+ const _getStats = function () {
+ let obj = this.getObj();
+ return clone(obj);
+ };
+
+ const _updateData = function (arg, property, value) {
+ let obj = this.getObj();
+ typeof arg !== "string" && (arg = arg.toString());
+ typeof arg === "string" && (arg = arg.toLowerCase());
+
+ if (typeof property === "object") {
+ obj = Object.assign(obj, property);
+ return FileAction.write(this.filePath, JSON.stringify(obj, null, 2));
+ }
+
+ if (obj.hasOwnProperty(property)) {
+ obj[property] = value;
+ return FileAction.write(this.filePath, JSON.stringify(obj, null, 2));
+ } else if (obj.hasOwnProperty(arg) && obj[arg].hasOwnProperty(property)) {
+ obj[arg][property] = value;
+ return FileAction.write(this.filePath, JSON.stringify(obj, null, 2));
+ }
+
+ return false;
+ };
+
+ return {
+ filePath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-CharData.json",
+ threads: [
+ "libs/SoloPlay/SoloPlay.js", "libs/SoloPlay/Threads/TownChicken.js",
+ "libs/SoloPlay/Threads/ToolsThread.js", "libs/SoloPlay/Threads/EventThread.js"
+ ],
+ _default: (function () {
+ let diffObj = { respecUsed: false, imbueUsed: false, socketUsed: false };
+ return {
+ initialized: false,
+ normal: Object.assign({}, diffObj),
+ nightmare: Object.assign({}, diffObj),
+ hell: Object.assign({}, diffObj),
+ task: "",
+ startTime: 0,
+ charName: "",
+ classid: -1,
+ level: 1,
+ strength: 0,
+ dexterity: 0,
+ currentBuild: "Start",
+ finalBuild: "",
+ highestDifficulty: "Normal",
+ setDifficulty: "Normal",
+ charms: {},
+ charmGids: [],
+ merc: {
+ act: 1,
+ classid: sdk.mercs.Rogue,
+ difficulty: sdk.difficulty.Normal,
+ level: 1,
+ strength: 0,
+ dexterity: 0,
+ skill: 0,
+ skillName: "",
+ gear: [],
+ }
+ };
+ })(),
+
+ login: (function () {
+ return {
+ filePath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-LoginData.json",
+ _default: { account: "", pass: "", currentChar: "", tag: "", charCount: 0, existing: false },
+
+ create: function () {
+ return _create.call(this);
+ },
+ getObj: function () {
+ return _getObj.call(this);
+ },
+
+ getStats: function () {
+ return _getStats.call(this);
+ },
+
+ updateData: function (arg, property, value) {
+ return _updateData.call(this, arg, property, value);
+ },
+ };
+ })(),
+
+ charms: (function () {
+ /**
+ * @constructor
+ * @param {number} classid
+ */
+ function Charm (classid) {
+ this.classid = classid;
+ }
+
+ Charm.prototype.count = function () {
+ let [curr, max] = [0, 0];
+ Object.keys(me.data.charms).forEach(function (cKey) {
+ if (me.data.charms[cKey].classid === this.classid) {
+ curr += me.data.charms[cKey].have.length;
+ max += me.data.charms[cKey].max;
+ }
+ });
+
+ return {
+ curr: curr,
+ max: max
+ };
+ };
+ /** @type {Map true);
+ this.tick = 0;
+ this.duration = 0;
+ }
+
+ BuffPot.prototype.active = function () {
+ return me.getState(this.state);
+ };
+
+ BuffPot.prototype.timeLeft = function () {
+ return this.duration > 0 ? this.duration - (getTickCount() - this.tick) : 0;
+ };
+
+ BuffPot.prototype.need = function () {
+ return (this.check() && (!this.active() || this.timeLeft() < Time.minutes(5)));
+ };
+
+ /** @type {Map} */
+ const _buffPots = new Map([
+ [
+ sdk.items.StaminaPotion,
+ new BuffPot(sdk.states.StaminaPot,
+ function () {
+ return Skill.canUse(sdk.skills.Vigor) || Pather.canTeleport();
+ })
+ ],
+ [
+ sdk.items.ThawingPotion,
+ new BuffPot(sdk.states.Thawing,
+ function () {
+ return me.coldRes < 75;
+ })
+ ],
+ [
+ sdk.items.AntidotePotion,
+ new BuffPot(sdk.states.Antidote,
+ function () {
+ return me.poisonRes < 75;
+ })
+ ],
+ ]);
+ // hacky for now - just to handle the old way of accessing buff pots
+ _buffPots.set("stamina", _buffPots.get(sdk.items.StaminaPotion));
+ _buffPots.set("thawing", _buffPots.get(sdk.items.ThawingPotion));
+ _buffPots.set("antidote", _buffPots.get(sdk.items.AntidotePotion));
+
+ return _buffPots;
+ }()),
+
+ skillData: {
+ skills: [],
+ currentChargedSkills: [],
+ chargedSkills: [],
+ chargedSkillsOnSwitch: [],
+ /**
+ * @todo fix this, it's ugly
+ */
+ bow: {
+ initialized: false,
+ onSwitch: false,
+ bowGid: 0,
+ bowType: 0,
+ arrows: 0,
+ quiverType: 0,
+ setBowInfo: function (bow, init = false) {
+ if (bow === undefined) return;
+ this.bowGid = bow.gid;
+ this.bowType = bow.itemType;
+ this.bowOnSwitch = bow.isOnSwap;
+ SetUp.bowQuiver();
+ init && (this.initialized = true);
+ !init && CharData.skillData.update();
+ },
+ setArrowInfo: function (quiver) {
+ if (quiver === undefined) return;
+ this.arrows = Math.floor((quiver.getStat(sdk.stats.Quantity) * 100) / getBaseStat("items", quiver.classid, "maxstack"));
+ this.quiverType = quiver.itemType;
+ },
+ resetBowData: function () {
+ this.bowOnSwitch = false;
+ [this.bowGid, this.bowType, this.arrows, this.quiverType] = [0, 0, 0, 0];
+ NTIP.Runtime.clear();
+ CharData.skillData.update();
+ },
+ },
+
+ init: function (skillIds, mainSkills, switchSkills) {
+ this.currentChargedSkills = skillIds.slice(0);
+ this.chargedSkills = mainSkills.slice(0);
+ this.chargedSkillsOnSwitch = switchSkills.slice(0);
+ this.skills = me.getSkill(4).map((skill) => skill[0]);
+ },
+
+ update: function () {
+ let obj = JSON.stringify(copyObj(this));
+ let myThread = getScript(true).name;
+ CharData.threads.forEach(function (script) {
+ let curr = getScript(script);
+ if (curr && myThread !== curr.name) {
+ curr.send("skill--" + obj);
+ }
+ });
+ },
+
+ haveChargedSkill: function (skillid = []) {
+ // convert to array if not one
+ !Array.isArray(skillid) && (skillid = [skillid]);
+ return this.currentChargedSkills
+ .some(function (s) {
+ return skillid.includes(s);
+ });
+ },
+
+ haveChargedSkillOnSwitch: function (skillid = 0) {
+ return this.chargedSkillsOnSwitch
+ .some(function (chargeSkill) {
+ return chargeSkill.skill === skillid;
+ });
+ }
+ },
+
+ // updates config obj across all threads - excluding our current
+ updateConfig: function () {
+ let obj = JSON.stringify(copyObj(Config));
+ let myThread = getScript(true).name;
+ CharData.threads.forEach(function (script) {
+ let curr = getScript(script);
+ if (curr && myThread !== curr.name) {
+ curr.send("config--" + obj);
+ }
+ });
+ },
+
+ /**
+ * @returns {MyData}
+ */
+ create: function () {
+ return _create.call(this);
+ },
+
+ /**
+ * @returns {MyData}
+ */
+ getObj: function () {
+ return _getObj.call(this);
+ },
+
+ /**
+ * @returns {MyData}
+ */
+ getStats: function () {
+ return _getStats.call(this);
+ },
+
+ updateData: function (arg, property, value) {
+ while (me.ingame && !me.gameReady) {
+ delay(100);
+ }
+
+ console.trace();
+
+ return _updateData.call(this, arg, property, value);
+ },
+
+ delete: function (deleteMain = false) {
+ if (deleteMain && FileTools.exists("data/" + me.profile + ".json")) {
+ FileTools.remove("data/" + me.profile + ".json");
+ }
+
+ FileTools.exists(this.filePath) && FileTools.remove(this.filePath);
+ FileTools.exists(Tracker.GTPath) && FileTools.remove(Tracker.GTPath);
+
+ return !(FileTools.exists(this.filePath) && FileTools.exists("libs/SoloPlay/Data/" + me.profile + ".GameTime" + ".json"));
+ },
+ };
+})();
diff --git a/libs/SoloPlay/Tools/Developer.js b/libs/SoloPlay/Tools/Developer.js
index 980508ab..16180e93 100644
--- a/libs/SoloPlay/Tools/Developer.js
+++ b/libs/SoloPlay/Tools/Developer.js
@@ -5,146 +5,91 @@
*
*/
+/**
+ * @todo
+ * - add override to GlobalAccount here to allow per profile options
+ * - add name choices in similar manner, would have to experiment with max lengths allowed as a prefix
+ */
const Developer = {
- // @desc - set to true if using the PlugY mod - allows use of larger stash
- plugyMode: false,
- // @desc - log game/bot statistics to .csv files located at SoloPlay/Data/
- logPerformance: true,
- // @desc - show in game overlay (see bottom of README.md for example)
- overlay: true,
- // @desc - show Total, InGame, and OOG (out of game) time in the D2bot# status window
- displayClockInConsole: false,
- // @desc - log currently equipped items to D2Bot# charviewer tab
- logEquipped: false,
- // @desc - disable printing chicken info in D2Bot console
- hideChickens: true,
- // @desc - enable ladder runewords in single player mode ONLY WORKS IF RUNEWORDS.TXT IS INSTALLED AND D2BS PROFILE IS CONFIGURED
- // or patch.json has been updated (see Single Player Additions in README.md)
- addLadderRW: !me.profile.toLowerCase().contains("nl"),
- // @desc - hide casting animations for better stability (reduce d2bs crashes)
- forcePacketCasting: {
- enabled: true,
- // @desc - allow specific profiles to show casting animations without disabling it for every profile running (helpful when debugging)
- excludeProfiles: [""],
- },
- // @desc - set to true in use with tag Bumper, Socketmule, or Imbuemule to make next character after reaching goal until account is full
- fillAccount: {
- bumpers: false,
- socketMules: false,
- imbueMule: false,
- },
- // @desc - set level for imbueMule to stop at
- imbueStopLevel: 30,
- // @desc - stop a profile once it reaches a certain level
- stopAtLevel: {
- enabled: false,
- profiles: [
- // ["scl-example-001", 60],
- // ["hcl-example-001", 40]
- ],
- },
- // @desc - allows a profile to loaded without starting any of the scripts. enables chat commands for testing. See Scripts/developermode.js for more info.
- developerMode: {
- enabled: false,
- // Enter in the profiles that you wish to start in developermode, i.e "scl-sorc"
- profiles: [""],
- },
- // @desc [experimental don't use] - set email during account creation
- setEmail: {
- enabled: false,
- //email: "",
- //domain: "",
- profiles: [],
- realms: ["asia"]
- },
- // @desc - enable/disable logging debug info to the console
- debugging: {
- smallCharm: false,
- largeCharm: false,
- grandCharm: false,
- baseCheck: false,
- junkCheck: false,
- autoEquip: false,
- crafting: false,
- pathing: false,
- skills: false,
- showStack: {
- enabled: false,
- // @desc - Enter in the profiles that you wish to see the stack walk for, this loads up guard.js and displays on the overlay
- profiles: [""],
- },
- },
-
- /* Developer tools */
- getObj: function (path) {
- let obj, OBJstring = Misc.fileAction(path, 0);
-
- try {
- obj = JSON.parse(OBJstring);
- } catch (e) {
- // If we failed, file might be corrupted, so create a new one
- Misc.errorReport(e, "Developer");
- FileTools.remove(path);
- Tracker.initialize();
- OBJstring = Misc.fileAction(path, 0);
- obj = JSON.parse(OBJstring);
- }
-
- if (obj) {
- return obj;
- }
-
- console.error("ÿc8Kolbot-SoloPlayÿc0: Failed to read Obj. (Developer.parseObj)");
-
- return false;
- },
-
- readObj: function (jsonPath) {
- let obj = this.getObj(jsonPath);
- return Misc.clone(obj);
- },
-
- writeObj: function (obj, path) {
- let string;
- try {
- string = JSON.stringify(obj, null, 2);
- // try to parse the string to ensure it converted correctly
- JSON.parse(string);
- // JSON.parse throws an error if it fails so if we are here now we are good
- Misc.fileAction(path, 1, string);
- } catch (e) {
- console.warn("Malformed JSON object");
- Misc.errorReport(e, "Developer");
- return false;
- }
-
- return true;
- },
-
- timer: function (tick) {
- return getTickCount() - tick;
- },
-
- formatTime: function (milliseconds) {
- let seconds = milliseconds / 1000;
- let sec = (seconds % 60).toFixed(0);
- let minutes = (Math.floor(seconds / 60) % 60).toFixed(0);
- let hours = Math.floor(seconds / 3600).toFixed(0);
- let timeString = hours.toString().padStart(2, "0") + ":" + minutes.toString().padStart(2, "0") + ":" + sec.toString().padStart(2, "0");
-
- return timeString;
- },
-
- totalDays: function (milliseconds) {
- let days = Math.floor(milliseconds / 86.4e6).toFixed(0);
- return days.toString().padStart(1, "0");
- },
+ // @desc - set to true if using the PlugY mod - allows use of larger stash
+ plugyMode: false,
+ // @desc - log game/bot statistics to .csv files located at SoloPlay/Data/
+ logPerformance: true,
+ // @desc - show in game overlay (see bottom of README.md for example)
+ overlay: true,
+ // @desc - show Total, InGame, and OOG (out of game) time in the D2bot# status window
+ displayClockInConsole: false,
+ // @desc - log currently equipped items to D2Bot# charviewer tab
+ logEquipped: false,
+ // @desc - disable printing chicken info in D2Bot console
+ hideChickens: true,
+ // @desc - enable ladder runewords in single player mode ONLY WORKS IF RUNEWORDS.TXT IS INSTALLED AND D2BS PROFILE IS CONFIGURED
+ // or patch.json has been updated (see Single Player Additions in README.md)
+ addLadderRW: !me.profile.toLowerCase().contains("nl"),
+ // @desc - hide casting animations for better stability (reduce d2bs crashes)
+ forcePacketCasting: {
+ enabled: true,
+ // @desc - allow specific profiles to show casting animations without disabling it for every profile running (helpful when debugging)
+ excludeProfiles: [""],
+ },
+ // @desc - set to true in use with tag Bumper, Socketmule, or Imbuemule to make next character after reaching goal until account is full
+ fillAccount: {
+ bumpers: false,
+ socketMules: false,
+ imbueMule: false,
+ },
+ // @desc - set level for imbueMule to stop at
+ imbueStopLevel: 30,
+ // @desc - stop a profile once it reaches a certain level
+ stopAtLevel: {
+ enabled: false,
+ profiles: [
+ // ["scl-example-001", 60],
+ // ["hcl-example-001", 40]
+ ],
+ },
+ // @desc - allows a profile to loaded without starting any of the scripts. enables chat commands for testing. See Scripts/developermode.js for more info.
+ developerMode: {
+ enabled: false,
+ // Enter in the profiles that you wish to start in developermode, i.e "scl-sorc"
+ profiles: [""],
+ },
+ testingMode: {
+ enabled: false,
+ // Enter in the profiles that you wish to start in testing mode, i.e "scl-sorc"
+ profiles: [""],
+ },
+ // @desc [experimental don't use] - set email during account creation
+ setEmail: {
+ enabled: false,
+ //email: "",
+ //domain: "",
+ profiles: [],
+ realms: ["asia"]
+ },
+ // @desc - enable/disable logging debug info to the console
+ debugging: {
+ smallCharm: false,
+ largeCharm: false,
+ grandCharm: false,
+ baseCheck: false,
+ junkCheck: false,
+ autoEquip: false,
+ crafting: false,
+ pathing: false,
+ skills: false,
+ showStack: {
+ enabled: false,
+ // @desc - Enter in the profiles that you wish to see the stack walk for, this loads up guard.js and displays on the overlay
+ profiles: [""],
+ },
+ },
};
// Set after Developer has been initialized - always load guard in developer mode
if (Developer.developerMode.enabled && Developer.developerMode.profiles.some(profile => profile.toLowerCase() === me.profile.toLowerCase())) {
- Developer.debugging.pathing = true;
- //Developer.debugging.skills = true;
- Developer.debugging.showStack.enabled = true;
- Developer.debugging.showStack.profiles.push(me.profile.toLowerCase());
+ Developer.debugging.pathing = true;
+ //Developer.debugging.skills = true;
+ Developer.debugging.showStack.enabled = true;
+ Developer.debugging.showStack.profiles.push(me.profile.toLowerCase());
}
diff --git a/libs/SoloPlay/Tools/NameGen.js b/libs/SoloPlay/Tools/NameGen.js
index 12fd0156..81f433f9 100644
--- a/libs/SoloPlay/Tools/NameGen.js
+++ b/libs/SoloPlay/Tools/NameGen.js
@@ -5,244 +5,248 @@
*
*/
-const NameGen = function () {
- const adjectives = [
- "Ancient", "Angry", "Artful", "Able", "Abundant", "Accepting", "Acclaimed", "Active", "Addictive", "Adept", "Adequate", "Admired", "Adorable",
- "Adored", "Agile", "Amazing", "Amiable", "Amicable", "Amusing", "Anxious", "Anxious", "Apathetic", "Aquatic", "Arrogant", "Artistic",
- "Attentive", "Awesome", "Azure", "Barren", "Bitter", "Black", "Blue", "Blasted", "Bold", "Bonding", "Boorish", "Bountiful", "Braggart",
- "Brave", "Bright", "Brilliant", "Broken", "Burning", "Busy", "Buzzing", "Callous", "Captious", "Caring", "Cautious", "Celestial",
- "Changing", "Charming", "Chaste", "Cheating", "Cheerful", "Churlish", "Civil", "Clean", "Clever", "Coastal", "Cold", "Colossal",
- "Composed", "Concerned", "Concrete", "Complex", "Cheap", "Compact", "Confident", "Congenial", "Cordial", "Courteous", "Covetous", "Crazy",
- "Crazed", "Creative", "Crimson", "Critical", "Crossing", "Crucial", "Crude", "Crushing", "Culpable", "Curious", "Current", "Curt", "Cynical",
- "Dancing", "Dark", "Decent", "Decorous", "Defensive", "Deft", "Dejected", "Delirious", "Demanding", "Demeaning", "Demise", "Depressed",
- "Devious", "Devoted", "Diligent", "Discreet", "Diving", "Dishonest", "Docile", "Downcast", "Doubting", "Drunken", "Dry", "Dull", "Dutiful",
- "Dynamic", "Eager", "Earnest", "Earthy", "East", "Efficient", "Elegant", "Elitist", "Emerald", "Endemic", "Energetic", "Enigmatic", "Esteemed",
- "Estimable", "Ethical", "Euphoric", "Evergreen", "Exclusive", "Expectant", "Explosive", "Exquisite", "Exuberant", "Endless", "Fair", "Faithful",
- "False", "Famous", "Fancy", "Fat", "Fatal", "Festive", "Feral", "Ferocious", "Fertile", "Fervent", "Funky", "Fibrous", "Fierce", "Firm",
- "Flawless", "Flexible", "Flowing", "Focused", "Forgiving", "Forlorn", "Frail", "Fierce", "Flustered", "Flying", "Foolish", "Friendly",
- "Generous", "Genial", "Genteel", "Gentle", "Genuine", "Gifted", "Gigantic", "Glib", "Gloomy", "Golden", "Good", "Gorgeous", "Graceful",
- "Gracious", "Grand", "Grateful", "Gravity", "Green", "Grouchy", "Guilty", "Guilty", "Gusty", "Grim", "Green", "Greedy", "Handsome", "Handy",
- "Hard", "Happy", "Haunting", "Healing", "Headless", "Heavenly", "Heroic", "Hidden", "High", "Honest", "Honorable", "Hopeful", "Hostile",
- "Humane", "Humble", "Humorous", "Hungry", "Hygienic", "Idolize", "Ignoble", "Ignorant", "Impartial", "Impolite", "Improper", "Imprudent",
- "Impudent", "Indecent", "Infinite", "Ingenuous", "Innocent", "Insolent", "Insulting", "Intense", "Introvert", "Intuitive", "Inventive",
- "Irascible", "Intrepid", "Jade", "Janky", "Jaundiced", "Jealous", "Jealous", "Jocular", "Jolly", "Jovial", "Juicy", "Joyful", "Jubilant",
- "Just", "Juvenile", "Kingly", "Keen", "Kind", "Kindred", "Kooky", "Liberal", "Listening", "Loathsome", "Loving", "LOYAL", "Limp", "Lord",
- "Loud", "Light", "Little", "Lanky", "Lazy", "Long", "Lucky", "Last", "Leaping", "Lone", "Lonely", "Lost", "Magical", "Majestic", "Malicious",
- "Mammoth", "Marine", "Masterful", "Meddling", "Migratory", "Minuscule", "Miserable", "Misty", "Modest", "Moral", "Mediocre", "Mellow", "Mute",
- "Miserable", "Naive", "Nascent", "Native", "Natural", "Natures", "Needy", "Nefarious", "Negative", "Neglected", "Negligent", "Nice", "Noble",
- "Northern", "Notorious", "Obedient", "Observant", "Open", "Orderly", "Original", "Outspoken", "Organic", "Ornate", "Ordinary", "Orange",
- "Parasitic", "Partial", "Patient", "Personal", "Petulant", "Pleasant", "Poise", "Polite", "Pollutant", "Popular", "Pouncing", "Powerful",
- "Prideful", "Primal", "Prime", "Pristine", "Prompt", "Proper", "Punctual", "Pure", "Purple", "Putrid", "Practical", "Precious", "Puzzled",
- "Quaint", "Quick", "Quiet", "Quirky", "Radiant", "Raging", "Rancorous", "Regular", "Red", "Rancid", "Rough", "Rational", "Reckless", "Refined",
- "Regal", "Renewable", "Repugnant", "Resilient", "Resolute", "Reverent", "Rotting", "Ruby", "Rude", "Ruthless", "Sad", "Safe", "Savage",
- "Scorching", "Scornful", "Secret", "Selfish", "Sensible", "Sensitive", "Sharing", "Silver", "Simple", "Sober", "Solar", "Solemn", "Solitary",
- "Southern", "Sour", "Spatial", "Special", "Splendid", "Staunch", "Singing", "Stern", "Stunning", "Subtle", "Sullen", "Superb", "Superior",
- "Surly", "Sweet", "Strong", "Smart", "Short", "Skinny", "Stupid", "Salty", "Soft", "Smooth", "Sharp", "Sneaky", "Stinky", "Tactful", "Tainted",
- "Temperate", "Temperate", "Tenacious", "Terrible", "Terrific", "Testy", "Tolerant", "Towering", "Toxic", "Tropical", "True", "Truthful", "Tasty",
- "Tricky", "Ultimate", "Ultimate", "Uncivil", "Uncouth", "Unethical", "Unfair", "Unique", "United", "Unfit", "Unrefined", "Unsavory", "Unworthy",
- "Uplifting", "Upright", "Uprooted", "Valiant", "Veracious", "Versatile", "Vicious", "Vigilant", "Vigilant", "Vigorous", "Vile", "Virtuous",
- "Visible", "Vivacious", "Vocal", "Volatile", "Violent", "Violet", "Void", "Weak", "West", "White", "Willful", "Wet", "Warm", "Wary", "Watchful",
- "Weeping", "Wicked", "Wild", "Willing", "Winning", "Winsome", "Wise", "Wistful", "Witty", "Woeful", "Wonderful", "Worldwide", "Wretched",
- "Worthy", "Yellow", "Yearning", "Yielding", "Yielding", "Yourself", "Youthful", "Zany", "Zealot", "Zealous", "Zealous", "Zero",
- ];
+(function (module) {
+ const adjectives = [
+ "Ancient", "Angry", "Artful", "Able", "Abundant", "Accepting", "Acclaimed", "Active", "Addictive", "Adept", "Adequate", "Admired", "Adorable",
+ "Adored", "Agile", "Amazing", "Amiable", "Amicable", "Amusing", "Anxious", "Anxious", "Apathetic", "Aquatic", "Arrogant", "Artistic",
+ "Attentive", "Awesome", "Azure", "Barren", "Bitter", "Black", "Blue", "Blasted", "Bold", "Bonding", "Boorish", "Bountiful", "Braggart",
+ "Brave", "Bright", "Brilliant", "Broken", "Burning", "Busy", "Buzzing", "Callous", "Captious", "Caring", "Cautious", "Celestial",
+ "Changing", "Charming", "Chaste", "Cheating", "Cheerful", "Churlish", "Civil", "Clean", "Clever", "Coastal", "Cold", "Colossal",
+ "Composed", "Concerned", "Concrete", "Complex", "Cheap", "Compact", "Confident", "Congenial", "Cordial", "Courteous", "Covetous", "Crazy",
+ "Crazed", "Creative", "Crimson", "Critical", "Crossing", "Crucial", "Crude", "Crushing", "Culpable", "Curious", "Current", "Curt", "Cynical",
+ "Dancing", "Dark", "Decent", "Decorous", "Defensive", "Deft", "Dejected", "Delirious", "Demanding", "Demeaning", "Demise", "Depressed",
+ "Devious", "Devoted", "Diligent", "Discreet", "Diving", "Dishonest", "Docile", "Downcast", "Doubting", "Drunken", "Dry", "Dull", "Dutiful",
+ "Dynamic", "Eager", "Earnest", "Earthy", "East", "Efficient", "Elegant", "Elitist", "Emerald", "Endemic", "Energetic", "Enigmatic", "Esteemed",
+ "Estimable", "Ethical", "Euphoric", "Evergreen", "Exclusive", "Expectant", "Explosive", "Exquisite", "Exuberant", "Endless", "Fair", "Faithful",
+ "False", "Famous", "Fancy", "Fat", "Fatal", "Festive", "Feral", "Ferocious", "Fertile", "Fervent", "Funky", "Fibrous", "Fierce", "Firm",
+ "Flawless", "Flexible", "Flowing", "Focused", "Forgiving", "Forlorn", "Frail", "Fierce", "Flustered", "Flying", "Foolish", "Friendly",
+ "Generous", "Genial", "Genteel", "Gentle", "Genuine", "Gifted", "Gigantic", "Glib", "Gloomy", "Golden", "Good", "Gorgeous", "Graceful",
+ "Gracious", "Grand", "Grateful", "Gravity", "Green", "Grouchy", "Guilty", "Guilty", "Gusty", "Grim", "Green", "Greedy", "Handsome", "Handy",
+ "Hard", "Happy", "Haunting", "Healing", "Headless", "Heavenly", "Heroic", "Hidden", "High", "Honest", "Honorable", "Hopeful", "Hostile",
+ "Humane", "Humble", "Humorous", "Hungry", "Hygienic", "Idolize", "Ignoble", "Ignorant", "Impartial", "Impolite", "Improper", "Imprudent",
+ "Impudent", "Indecent", "Infinite", "Ingenuous", "Innocent", "Insolent", "Insulting", "Intense", "Introvert", "Intuitive", "Inventive",
+ "Irascible", "Intrepid", "Jade", "Janky", "Jaundiced", "Jealous", "Jealous", "Jocular", "Jolly", "Jovial", "Juicy", "Joyful", "Jubilant",
+ "Just", "Juvenile", "Kingly", "Keen", "Kind", "Kindred", "Kooky", "Liberal", "Listening", "Loathsome", "Loving", "LOYAL", "Limp", "Lord",
+ "Loud", "Light", "Little", "Lanky", "Lazy", "Long", "Lucky", "Last", "Leaping", "Lone", "Lonely", "Lost", "Magical", "Majestic", "Malicious",
+ "Mammoth", "Marine", "Masterful", "Meddling", "Migratory", "Minuscule", "Miserable", "Misty", "Modest", "Moral", "Mediocre", "Mellow", "Mute",
+ "Miserable", "Naive", "Nascent", "Native", "Natural", "Natures", "Needy", "Nefarious", "Negative", "Neglected", "Negligent", "Nice", "Noble",
+ "Northern", "Notorious", "Obedient", "Observant", "Open", "Orderly", "Original", "Outspoken", "Organic", "Ornate", "Ordinary", "Orange",
+ "Parasitic", "Partial", "Patient", "Personal", "Petulant", "Pleasant", "Poise", "Polite", "Pollutant", "Popular", "Pouncing", "Powerful",
+ "Prideful", "Primal", "Prime", "Pristine", "Prompt", "Proper", "Punctual", "Pure", "Purple", "Putrid", "Practical", "Precious", "Puzzled",
+ "Quaint", "Quick", "Quiet", "Quirky", "Radiant", "Raging", "Rancorous", "Regular", "Red", "Rancid", "Rough", "Rational", "Reckless", "Refined",
+ "Regal", "Renewable", "Repugnant", "Resilient", "Resolute", "Reverent", "Rotting", "Ruby", "Rude", "Ruthless", "Sad", "Safe", "Savage",
+ "Scorching", "Scornful", "Secret", "Selfish", "Sensible", "Sensitive", "Sharing", "Silver", "Simple", "Sober", "Solar", "Solemn", "Solitary",
+ "Southern", "Sour", "Spatial", "Special", "Splendid", "Staunch", "Singing", "Stern", "Stunning", "Subtle", "Sullen", "Superb", "Superior",
+ "Surly", "Sweet", "Strong", "Smart", "Short", "Skinny", "Stupid", "Salty", "Soft", "Smooth", "Sharp", "Sneaky", "Stinky", "Tactful", "Tainted",
+ "Temperate", "Temperate", "Tenacious", "Terrible", "Terrific", "Testy", "Tolerant", "Towering", "Toxic", "Tropical", "True", "Truthful", "Tasty",
+ "Tricky", "Ultimate", "Ultimate", "Uncivil", "Uncouth", "Unethical", "Unfair", "Unique", "United", "Unfit", "Unrefined", "Unsavory", "Unworthy",
+ "Uplifting", "Upright", "Uprooted", "Valiant", "Veracious", "Versatile", "Vicious", "Vigilant", "Vigilant", "Vigorous", "Vile", "Virtuous",
+ "Visible", "Vivacious", "Vocal", "Volatile", "Violent", "Violet", "Void", "Weak", "West", "White", "Willful", "Wet", "Warm", "Wary", "Watchful",
+ "Weeping", "Wicked", "Wild", "Willing", "Winning", "Winsome", "Wise", "Wistful", "Witty", "Woeful", "Wonderful", "Worldwide", "Wretched",
+ "Worthy", "Yellow", "Yearning", "Yielding", "Yielding", "Yourself", "Youthful", "Zany", "Zealot", "Zealous", "Zealous", "Zero",
+ ];
- const nouns = [
- "glue", "riot", "boom", "veil", "poet", "hype", "cafe", "gene", "fame", "sin", "zon", "barb", "core", "dust", "bite", "maid", "scar",
- "wing", "horn", "crew", "lake", "duke", "mask", "dawn", "seed", "tank", "flag", "jazz", "tart", "brew", "meow", "boot", "shoe", "sage", "drum",
- "babe", "cash", "luck", "lime", "eyes", "boat", "milk", "tuna", "cube", "oreo", "worm", "rage", "itch", "four", "bomb", "pear", "ship", "oven",
- "fear", "hate", "leaf", "hero", "wife", "bean", "hope", "girl", "baby", "meme", "wish", "one", "nine", "work", "cake", "lady", "fire", "pain",
- "rain", "fool", "soul", "tree", "five", "fish", "love", "life", "elk", "dad", "hog", "elf", "mop", "rod", "bat", "bug", "bot", "pus", "ufo",
- "zen", "ark", "rag", "egg", "bed", "car", "boy", "man", "cricket", "aura", "moon", "hippo", "vortex", "palm", "panther", "meteor", "deer",
- "vein", "plan", "atom", "hole", "weed", "boss", "army", "meat", "lock", "song", "rat", "rose", "blossom", "twin", "comet", "fist", "crow",
- "star", "starlight", "axe", "fury", "mouse", "blow", "swan", "bee", "asp", "viper", "feather", "bird", "bolt", "sun", "mind", "beaver", "frog",
- "mist", "day", "night", "falcon", "blood", "poison", "lily", "inferno", "kiss", "lotus", "giant", "monarch", "lord", "autumn", "spring",
- "summer", "winter", "paragon", "vulture", "condor", "coil", "chain", "spell", "dove", "peach", "petal", "droplet", "eruption", "heaven", "fog",
- "boa", "needle", "shield", "rock", "turtle", "ghost", "death", "cobra", "bane", "princess", "king", "fingers", "toes", "hand", "foot", "ear",
- "eye", "skull", "cat", "dog", "pig", "piggy", "cow", "snake", "horse", "rabbit", "goat", "wolf", "sheep", "duck", "eagle", "crab", "baboon", "basilisk",
- "fox", "badger", "beetle", "butterfly", "shark", "clownfish", "crane", "cicada", "dingo", "elephant", "jackal", "jaguar", "lion", "mandrill",
- "lungfish", "heart", "spleen", "liver", "guts", "brains", "bones", "chocolate", "candy", "surprise", "cheese", "furball", "salami", "beef",
- "supreme", "taco", "burger", "hotdog", "carrot", "onion", "fungus", "brick", "rock", "banana", "killer", "demon", "angel", "saint", "bamboo",
- "panda", "broom", "hammer", "snow", "cur", "toad", "raven", "claw", "pine", "rice", "sushi", "bread", "toast", "cereal", "smoke", "fart",
- "beer", "bear", "faucet", "pipe", "iron", "dork", "genius", "hunter", "farmer", "wiz", "witch", "churro", "donut", "shrimp", "sand", "pagoda",
- "eel", "ant", "pants", "jeans", "socks", "sword", "fork", "pizza", "trap", "pork", "wort", "sack", "hawk", "rite", "tire", "dirt", "plum",
- "ATM", "CD", "SUV", "TV", "abacus", "abbey", "abdomen", "ability", "absence", "abuse", "academy", "accent", "access", "accord", "account", "acetate", "acid", "acorn",
- "acre", "acrylic", "act", "action", "actor", "actress", "ad", "adapter", "address", "admin", "admire", "adobe", "adult", "advance", "advent", "adverb", "advice", "adviser",
- "affair", "affect", "afoul", "age", "agency", "agenda", "agent", "aglet", "agony", "aid", "aide", "aim", "air", "airbag", "airbus", "airfare", "airline", "airmail", "airman",
- "airport", "airship", "alarm", "alb", "album", "alcohol", "alcove", "alder", "ale", "alert", "alfalfa", "algebra", "alias", "alibi", "alien", "alley", "alloy", "almanac", "almond",
- "alpaca", "alpha", "altar", "alto", "amazon", "amber", "amenity", "amnesty", "amount", "anagram", "analog", "analogy", "analyst", "anarchy", "anatomy", "anchovy", "android", "angel",
- "anger", "angina", "angle", "angora", "anguish", "animal", "anime", "anise", "ankle", "anklet", "annual", "anorak", "answer", "ant", "antigen", "antique", "antler", "antling", "anxiety",
- "anybody", "anyone", "ape", "apology", "app", "apparel", "appeal", "apple", "apricot", "apron", "apse", "aquifer", "arcade", "arch", "archer", "area", "arena", "ark", "arm", "armoire", "armor",
- "armour", "armpit", "armrest", "army", "array", "arrest", "arrival", "arrow", "art", "artery", "arthur", "article", "artist", "ascend", "ascent", "ascot", "ash", "ashram", "ashtray", "aside",
- "aspect", "asphalt", "aspic", "assault", "asset", "assist", "atelier", "athlete", "atom", "atrium", "attack", "attempt", "attic", "auction", "audit", "aunt", "author", "auto",
- "autumn", "avenue", "average", "avocado", "award", "awe", "axis", "azimuth", "babe", "baboon", "baby", "back", "back-up", "backup", "bacon", "badge", "badger", "bag", "bagel", "baggage", "baggie",
- "baggy", "bagpipe", "bail", "bait", "bake", "baker", "bakery", "balance", "balcony", "ball", "ballet", "balloon", "ballot", "bamboo", "ban", "banana", "band", "bandana", "bangle", "banjo", "bank",
- "banker", "banking", "banner", "banyan", "baobab", "bar", "barber", "bargain", "barge", "barium", "bark", "barley", "barn", "barrage", "barrel", "barrier", "base", "basics", "basil", "basin", "basis",
- "basket", "bass", "bassoon", "bat", "bath", "bather", "bathtub", "batter", "battery", "batting", "battle", "bay", "bayou", "beach", "bead", "beak", "beam", "bean", "beanie", "bear", "beard", "beast",
- "beastie", "beat", "beating", "beauty", "beaver", "beck", "bed", "bedrock", "bedroom", "bee", "beech", "beef", "beer", "beet", "beetle", "beggar", "begonia", "behalf", "behest", "behold", "being",
- "belfry", "belief", "bell", "bellows", "belly", "belt", "bench", "bend", "benefit", "beret", "berry", "bet", "beyond", "bias", "bicycle", "bid", "bidder", "bidding", "bidet", "bijou", "bike", "bikini",
- "bill", "billing", "billion", "bin", "biology", "biopsy", "biplane", "birch", "bird", "birth", "biscuit", "bit", "bite", "bitten", "bitter", "black", "bladder", "blade", "blame", "blank", "blanket",
- "blast", "blazer", "blend", "blight", "blind", "blinker", "blister", "block", "blocker", "blog", "blogger", "blood", "bloom", "bloomer", "blossom", "blouse", "blow", "blowgun", "blue", "blush", "boar",
- "board", "boat", "bob", "bobcat", "body", "bog", "bolero", "bolt", "bomb", "bomber", "bombing", "bond", "bonding", "bone", "bonfire", "bongo", "bonnet", "bonsai", "bonus", "book", "bookend", "booking",
- "booklet", "boolean", "boom", "boon", "boost", "booster", "boot", "bootee", "bootie", "booty", "border", "bore", "bosom", "boss", "botany", "bother", "bottle", "bottom", "boudoir", "bough", "boulder",
- "bouquet", "bout", "bow", "bower", "bowl", "bowler", "bowling", "bowtie", "box", "boxer", "boy", "boycott", "boyhood", "bra", "brace", "bracket", "brain", "brake", "bran", "branch", "brand", "brandy",
- "brass", "bread", "break", "breast", "breath", "breeze", "brewer", "bribery", "brick", "bride", "bridge", "brief", "briefly", "briefs", "brink", "brisket", "broad", "broiler", "broker", "bronco",
- "bronze", "brooch", "brood", "brook", "broom", "brother", "brow", "brown", "brownie", "browser", "brunch", "brush", "bubble", "buck", "bucket", "buckle", "bud", "buddy", "budget", "buffalo", "buffer",
- "buffet", "bug", "buggy", "bugle", "builder", "bulb", "bulk", "bull", "bullet", "bump", "bumper", "bun", "bunch", "burden", "bureau", "burglar", "burial", "burn", "burning", "burrito", "burro", "burrow",
- "burst", "bus", "bush", "bust", "bustle", "butane", "butcher", "butler", "butter", "button", "buy", "buyer", "buying", "buzz", "buzzard", "c-clamp", "cabana", "cabbage", "cabin", "cabinet", "cable",
- "caboose", "cacao", "cactus", "caddy", "cadet", "cafe", "caftan", "cage", "cake", "calf", "caliber", "calibre", "calico", "call", "calm", "calorie", "camel", "cameo", "camera", "camp", "camper", "campus",
- "can", "canal", "cancer", "candle", "candy", "cane", "cannon", "canoe", "canon", "canopy", "canteen", "canvas", "cap", "cape", "caper", "capital", "capon", "captain", "caption", "captor", "car", "carabao",
- "caramel", "caravan", "carbon", "card", "care", "career", "cargo", "caribou", "carload", "carol", "carp", "carpet", "carport", "carrier", "carrot", "carry", "cart", "cartel", "carter", "cartoon", "carving",
- "cascade", "case", "cash", "cashew", "cashier", "casino", "casket", "cassava", "cassock", "cast", "castle", "cat", "catch", "catcher", "cation", "catsup", "cattle", "causal", "cause", "caution", "cave",
- "caviar", "cayenne", "ceiling", "celery", "cell", "cellar", "cello", "celsius", "cement", "census", "cent", "center", "centre", "century", "ceramic", "cereal", "chafe", "chain", "chair", "chaise", "chalet",
- "chalice", "chalk", "chamber", "chance", "change", "channel", "chaos", "chap", "chapel", "chapter", "chard", "charge", "charger", "charity", "charm", "charset", "chart", "charter", "chasm", "chassis",
- "chateau", "chatter", "check", "cheddar", "cheek", "cheer", "cheese", "cheetah", "chef", "chem", "cheque", "cherry", "chess", "chest", "chick", "chicken", "chicory", "chief", "child", "chili", "chill",
- "chime", "chin", "chino", "chip", "chive", "chives", "choice", "choir", "choker", "chop", "chops", "chord", "chorus", "chow", "chowder", "chrome", "chub", "chuck", "chug", "church", "churn", "chutney",
- "cicada", "cinder", "cinema", "circle", "circuit", "cirrus", "citizen", "citron", "citrus", "city", "claim", "clam", "clamp", "clan", "clank", "clarity", "clasp", "class", "classic", "clause", "clave",
- "clavier", "claw", "clay", "cleaner", "cleat", "clef", "cleft", "cleric", "clerk", "click", "client", "cliff", "climate", "climb", "clinic", "clip", "clipper", "cloak", "clock", "clogs", "clone", "close",
- "closet", "closing", "closure", "cloth", "clothes", "cloud", "clove", "clover", "cloves", "club", "clue", "cluster", "clutch", "coach", "coal", "coast", "coaster", "coat", "cob", "cobbler", "cobweb", "cock",
- "cockpit", "cocoa", "coconut", "cod", "code", "codling", "codon", "coffee", "coffin", "cohort", "coil", "coin", "coke", "cold", "collar", "collard", "college", "colon", "colony", "color", "colt", "column",
- "comb", "combat", "combine", "comedy", "comfort", "comic", "comics", "comma", "command", "comment", "common", "company", "compass", "complex", "compost", "con", "concept", "concern", "concert", "condor",
- "conduct", "cone", "conga", "congo", "conifer", "consent", "consist", "console", "consul", "contact", "contact", "lens", "content", "contest", "context", "contour", "control", "convert", "cook", "cookie",
- "cooking", "cop", "cop-out", "cope", "copper", "copy", "copying", "coral", "cord", "core", "cork", "corn", "corner", "cornet", "corps", "corral", "corsage", "cosset", "cost", "costume", "cot", "cottage",
- "cotton", "couch", "cougar", "cough", "council", "counsel", "count", "counter", "country", "county", "couple", "coupon", "courage", "course", "court", "cousin", "cover", "cow", "cowbell", "cowboy", "coyote",
- "crab", "crack", "cracker", "cradle", "craft", "crane", "cranky", "crap", "crash", "crate", "cravat", "craw", "crawdad", "crayon", "crazy", "cream", "creator", "creche", "credit", "creek", "creme", "brulee", "crepe",
- "crest", "crew", "crewman", "crewmen", "cria", "crib", "cricket", "crime", "crisis", "crisp", "critic", "crocus", "crook", "crop", "cross", "crotch", "croup", "crow", "crowd", "crown", "crude", "cruelty", "cruise",
- "crumb", "crunch", "crush", "crust", "cry", "crystal", "cub", "cube", "cuckoo", "cue", "cuisine", "culture", "culvert", "cup", "cupcake", "cupola", "curd", "cure", "curio", "curl", "curler", "currant", "current",
- "curry", "curse", "cursor", "curtain", "curve", "cushion", "custard", "custody", "custom", "cut", "cuticle", "cutlet", "cutover", "cutting", "cycle", "cyclone", "cygnet", "cymbal", "cynic", "cyst", "dad", "daddy",
- "dagger", "dahlia", "daikon", "daily", "dairy", "daisy", "dam", "damage", "dame", "damn", "dance", "dancer", "dancing", "danger", "dare", "dark", "darn", "dart", "dash", "data", "date", "dawn", "day", "daybed", "dead",
- "deal", "dealer", "dealing", "dearest", "death", "debate", "debris", "debt", "debtor", "decade", "decency", "decimal", "deck", "decline", "decoder", "deduce", "deed", "deep", "deer", "default", "defeat", "defense",
- "deficit", "degree", "delay", "delight", "demand", "demon", "demur", "den", "denim", "density", "dentist", "deposit", "depot", "depth", "deputy", "derby", "derrick", "descent", "desert", "design", "desire", "desk",
- "desktop", "dessert", "destiny", "detail", "detour", "device", "devil", "dew", "dhow", "diadem", "diagram", "dial", "dialect", "diam", "diamond", "diaper", "diarist", "diary", "dibble", "dick", "dickey", "diction",
- "die", "diesel", "diet", "diffuse", "dig", "digger", "digging", "digit", "dignity", "dill", "dime", "dimple", "diner", "dinghy", "dining", "dinner", "dioxide", "dip", "diploma", "dirndl", "dirt", "disco", "disdain",
- "disease", "disgust", "dish", "disk", "display", "dispute", "divan", "diver", "divide", "divider", "divine", "diving", "divorce", "doc", "dock", "doctor", "doe", "dog", "doggie", "dogsled", "dogwood", "doing", "doll",
- "dollar", "dollop", "dolman", "dolor", "dolphin", "domain", "dome", "donkey", "donor", "donut", "door", "doorway", "dory", "dose", "dot", "double", "doubt", "doubter", "dough", "down", "dozen", "draft", "drag", "dragon",
- "drain", "drake", "drama", "drapes", "draw", "drawer", "drawing", "dream", "dreamer", "dredger", "dress", "dresser", "drill", "drink", "drive", "driver", "driving", "drizzle", "drop", "drug", "drum", "drummer", "drunk",
- "dryer", "duck", "dud", "dude", "due", "duel", "dueling", "duffel", "dugout", "dump", "dump", "truck", "dune", "dune", "buggy", "dungeon", "durian", "dusk", "dust", "dust", "storm", "duster", "duty", "dwarf", "dwell",
- "dynamo", "dynasty", "e-book", "e-mail", "eagle", "eaglet", "ear", "eardrum", "earplug", "earring", "earth", "ease", "easel", "east", "eating", "eaves", "echidna", "eclipse", "ecology", "economy", "eddy", "edge",
- "edger", "edible", "editing", "edition", "editor", "eel", "effect", "effort", "egg", "egghead", "eggnog", "ego", "ejector", "elbow", "element", "elf", "elicit", "elite", "elixir", "elk", "ellipse", "elm", "elver",
- "email", "emanate", "embassy", "embryo", "emerald", "emery", "emitter", "emotion", "empire", "employ", "emu", "enclave", "end", "endive", "enemy", "energy", "engine", "enigma", "enquiry", "entity", "entree", "entry",
- "envy", "enzyme", "epee", "ephyra", "epic", "episode", "epoch", "eponym", "epoxy", "equal", "equinox", "equity", "era", "eraser", "erosion", "error", "escape", "escort", "essay", "essence", "estate", "estuary", "ethics",
- "ethyl", "eve", "evening", "event", "evil", "ex-wife", "exam", "example", "excerpt", "excess", "excuse", "exhaust", "exhibit", "exile", "exit", "expense", "expert", "export", "expose", "extent", "extreme", "eye", "eyeball",
- "eyebrow", "eyelash", "eyelid", "eyelids", "eyrie", "fabric", "face", "facet", "fact", "factor", "factory", "faculty", "fail", "failure", "fairy", "faith", "fall", "fallacy", "fame", "family", "fan", "fang", "fanny", "fantasy",
- "farm", "farmer", "farming", "farrow", "fascia", "fashion", "fat", "fate", "father", "fatigue", "faucet", "fault", "fav", "fava", "favor", "fawn", "fax", "fear", "feast", "feather", "feature", "fedora", "fee", "feed", "feeding",
- "feel", "feeling", "fellow", "felony", "female", "fen", "fence", "fencing", "fender", "feng", "fennel", "ferret", "ferry", "fetus", "few", "fiber", "fibre", "ficlet", "fiction", "fiddle", "field", "fiery", "fiesta", "fifth", "fig",
- "fight", "fighter", "figure", "file", "filing", "fill", "fillet", "filly", "film", "filter", "filth", "final", "finance", "finding", "fine", "finer", "finger", "finish", "fir", "fire", "fireman", "firm", "first", "fish", "fishery",
- "fishing", "fishnet", "fisting", "fit", "fitness", "fix", "fixture", "flag", "flair", "flame", "flan", "flanker", "flare", "flash", "flat", "flavor", "flax", "fleck", "fleece", "flesh", "flick", "flicker", "flight", "flint", "flock",
- "flood", "floor", "floozie", "flour", "flow", "flower", "flu", "fluke", "flume", "flung", "flute", "fly", "flytrap", "foal", "foam", "fob", "focus", "fog", "fold", "folder", "folk", "fondue", "font", "food", "fool", "foot", "footage",
- "forage", "forager", "foray", "force", "ford", "forearm", "forest", "forever", "forgery", "fork", "form", "formal", "format", "former", "formula", "fort", "forte", "fortune", "forum", "founder", "fourths", "fowl", "fox", "frame",
- "fraud", "freak", "freckle", "freedom", "freezer", "freight", "frenzy", "freon", "fresco", "fridge", "friend", "fries", "frigate", "fright", "fringe", "fritter", "frock", "frog", "front", "frost", "frown", "fruit", "fry", "fvck",
- "fuel", "fugato", "full", "fun", "fund", "funding", "funeral", "fur", "furnace", "furry", "futon", "future", "gadget", "gaffe", "gaffer", "gain", "gaiters", "gale", "gallery", "galley", "gallon", "game", "gaming", "gander", "gang",
- "gap", "garage", "garb", "garbage", "garden", "garlic", "garment", "garter", "gas", "gasket", "gasp", "gate", "gateway", "gather", "gator", "gauge", "gavel", "gazebo", "gazelle", "gear", "geek", "gel", "gelatin", "gelding", "gem",
- "gemsbok", "gender", "gene", "general", "genie", "genius", "genre", "geology", "gerbil", "gesture", "geyser", "gherkin", "ghost", "giant", "gift", "gig", "giggle", "ginger", "ginseng", "giraffe", "girdle", "girl", "git", "glacier",
- "glance", "gland", "glass", "glasses", "glee", "glen", "glider", "gliding", "glimpse", "globe", "gloom", "glory", "glove", "glow", "glucose", "glue", "glut", "gnat", "gnu", "go-kart", "goal", "goat", "gobbler", "god", "goddess", "goggles",
- "going", "gold", "golf", "gondola", "gong", "good", "goodbye", "goodie", "goose", "gopher", "gorilla", "gosling", "gossip", "gown", "grace", "grade", "graft", "grain", "gram", "grammar", "gran", "grand", "grandma", "grandpa", "granny",
- "granola", "grant", "grape", "graph", "graphic", "grasp", "grass", "gravel", "gravity", "gravy", "gray", "grease", "greed", "green", "greens", "grenade", "grey", "grid", "grief", "grill", "grin", "grip", "gripper", "grit", "grocery",
- "ground", "group", "grouper", "grouse", "grove", "growth", "grub", "guard", "guava", "guess", "guest", "guide", "guilder", "guilt", "guilty", "guinea", "guitar", "gum", "gumshoe", "gun", "gutter", "guy", "gym", "gymnast", "gyro", "habit",
- "habitat", "hacksaw", "hail", "hair", "haircut", "hake", "half", "halibut", "hall", "hallway", "halt", "ham", "hammer", "hammock", "hamster", "hand", "handful", "handgun", "handle", "handsaw", "hanger", "harald", "harbor", "harbour",
- "hardhat", "hare", "harm", "harmony", "harp", "harvest", "hash", "hashtag", "hassock", "haste", "hat", "hatbox", "hatchet", "hate", "hatred", "haunt", "haven", "havoc", "hawk", "hay", "haze", "hazel", "head", "health", "hearing", "hearsay",
- "heart", "hearth", "heat", "heater", "heating", "heaven", "heavy", "hectare", "hedge", "heel", "heifer", "height", "heir", "helium", "hell", "hellcat", "hello", "helmet", "helo", "help", "hemp", "hen", "herb", "herbs", "hermit", "hero",
- "heroine", "heron", "herring", "hexagon", "heyday", "hiccups", "hide", "high", "highway", "hike", "hiking", "hill", "hint", "hip", "hire", "hiring", "history", "hit", "hive", "hobbit", "hobby", "hockey", "hoe", "hog", "hold", "holder",
- "hole", "holiday", "home", "homonym", "honesty", "honey", "honor", "honoree", "hood", "hoof", "hook", "hop", "hope", "hops", "horde", "horizon", "hormone", "horn", "hornet", "horror", "horse", "horst", "hose", "hosiery", "hospice",
- "host", "hostel", "hostess", "hotdog", "hotel", "hound", "hour", "house", "housing", "hovel", "howard", "hub", "hubcap", "hubris", "hug", "hugger", "hull", "human", "hummus", "humor", "humour", "hundred", "hunger", "hunt", "hunter",
- "hunting", "hurdle", "hurdler", "hurry", "hurt", "husband", "hut", "hutch", "hydrant", "hyena", "hype", "ice", "iceberg", "icicle", "icing", "icon", "icy", "id", "idea", "ideal", "idiom", "idiot", "igloo", "ikebana", "illegal", "illness",
- "image", "impact", "impala", "import", "impress", "impulse", "in-joke", "in-laws", "inbox", "incense", "inch", "income", "index", "infancy", "infant", "infix", "influx", "info", "ingrate", "initial", "injury", "ink", "inlay", "inn", "input",
- "inquiry", "insect", "insert", "inside", "insight", "instant", "integer", "intent", "invader", "inverse", "invite", "invoice", "iris", "iron", "irony", "island", "issue", "item", "ivory", "jack", "jackal", "jacket", "jade", "jaguar", "jail",
- "jam", "jar", "jasmine", "jaw", "jazz", "jeans", "jeep", "jelly", "jerk", "jet", "jewel", "jewelry", "jicama", "jiffy", "job", "jockey", "joey", "jogging", "joint", "joke", "jot", "journal", "journey", "joy", "judge", "judo", "jug", "juice",
- "jumbo", "jump", "jumper", "jungle", "junior", "junk", "junker", "junket", "jury", "justice", "jute", "kale", "karate", "kayak", "kazoo", "kebab", "keep", "keeper", "kendo", "kennel", "ketch", "ketchup", "kettle", "key", "kick", "kid", "kidney",
- "kill", "killer", "killing", "kilt", "kimono", "kinase", "kind", "king", "kingdom", "kiosk", "kiss", "kit", "kitchen", "kite", "kitsch", "kitten", "kitty", "kiwi", "knee", "knife", "knight", "knock", "knot", "knuckle", "koala", "kumquat", "lab",
- "label", "labor", "laborer", "labour", "lace", "lack", "lad", "ladder", "ladle", "lady", "ladybug", "lag", "lake", "lamb", "lambkin", "lament", "lamp", "lanai", "land", "landing", "lane", "lantern", "lap", "lapdog", "laptop", "larch", "lard",
- "larder", "lark", "larva", "lasagna", "lashes", "last", "latency", "latex", "lathe", "latte", "latter", "laugh", "laundry", "lava", "law", "lawn", "lawsuit", "lawyer", "lay", "layer", "layout", "lead", "leader", "leading", "leaf", "league",
- "leaker", "leap", "leash", "leather", "leave", "leaver", "lecture", "leek", "leeway", "left", "leg", "legacy", "legal", "legend", "legging", "legume", "leisure", "lemon", "lemur", "lender", "lending", "length", "lens", "lentil", "leopard",
- "leprosy", "lesbian", "lesson", "letter", "lettuce", "level", "lever", "leveret", "liar", "liberty", "libido", "library", "licence", "license", "lid", "lie", "lieu", "life", "lift", "ligand", "light", "ligula", "lilac", "lily", "limb", "lime",
- "limit", "limo", "line", "linen", "liner", "lining", "link", "linkage", "linseed", "lion", "lip", "lipid", "liquid", "liquor", "list", "listing", "litmus", "litter", "liver", "living", "lizard", "llama", "load", "loading", "loaf", "loafer", "loan",
- "lobby", "lobster", "local", "lock", "locker", "locket", "locust", "lode", "loft", "log", "loggia", "logic", "login", "logo", "look", "lookout", "loop", "loquat", "lord", "loss", "lot", "lotion", "lottery", "lounge", "louse", "lout", "love", "lover",
- "lox", "loyalty", "luck", "luggage", "lumber", "lunch", "lung", "lunge", "lust", "lute", "luxury", "lychee", "lycra", "lye", "lynx", "lyocell", "lyre", "lyrics", "lysine", "mRNA", "macaw", "machine", "macrame", "macro", "madam", "maestro", "maggot",
- "magic", "magnet", "maid", "maiden", "mail", "mailbox", "mailer", "mailing", "mailman", "main", "maize", "major", "maker", "makeup", "making", "male", "malice", "mall", "mallard", "mallet", "mama", "mambo", "mammoth", "man", "manacle", "manager",
- "manatee", "mandate", "mangle", "mango", "manhunt", "maniac", "mankind", "manner", "manor", "mansard", "mansion", "mantel", "mantle", "mantua", "many", "map", "maple", "mapping", "maracas", "marble", "march", "mare", "margin", "marimba", "marines",
- "mark", "marker", "market", "markup", "marsh", "marten", "marxism", "mascara", "mask", "masonry", "mass", "massage", "mast", "master", "mastoid", "mat", "match", "mate", "math", "matrix", "matter", "mattock", "max", "maximum", "maybe", "mayor",
- "meadow", "meal", "mean", "meander", "meaning", "means", "measles", "measure", "meat", "mecca", "med", "medal", "media", "median", "medium", "meet", "meeting", "melody", "melon", "member", "meme", "memo", "memory", "men", "menorah", "mention", "mentor",
- "menu", "mercury", "merit", "mess", "message", "messy", "metal", "meteor", "meter", "methane", "method", "metric", "metro", "midden", "middle", "midline", "midwife", "might", "migrant", "mile", "mileage", "milk", "mill", "millet", "million", "mime", "mimosa",
- "min", "mind", "mine", "mineral", "mini", "minibus", "minimum", "mining", "minion", "mink", "minnow", "minor", "mint", "minute", "miracle", "mirror", "misfit", "miss", "missile", "mission", "mist", "mistake", "mister", "miter", "mitten", "mix", "mixer",
- "mixture", "moai", "moat", "mob", "mobile", "mobster", "mocha", "mochi", "mode", "model", "modem", "molar", "molding", "mole", "mom", "moment", "money", "monger", "monitor", "monk", "monkey", "monocle", "monsoon", "monster", "month", "mood", "moody", "moon",
- "moose", "mop", "morale", "morbid", "morning", "moron", "morsel", "mortal", "mortise", "mosque", "most", "motel", "moth", "mother", "motion", "motive", "motor", "mound", "mouse", "mouser", "mousse", "mouth", "mouton", "mover", "movie", "mower", "mud", "muffin",
- "mug", "mukluk", "mule", "murder", "muscat", "muscle", "museum", "music", "muskrat", "mussel", "mustard", "mutt", "mutton", "mystery", "myth", "nail", "name", "naming", "napkin", "nasal", "nation", "native", "nature", "neck", "necktie", "nectar", "need",
- "needle", "neglect", "neon", "neonate", "nephew", "nerve", "nest", "net", "netball", "netbook", "netsuke", "network", "neuron", "news", "nexus", "nibble", "nicety", "niche", "nick", "nickel", "niece", "night", "ninja", "nit", "nobody", "nod", "node", "noir",
- "noise", "noodle", "noodles", "noon", "norm", "normal", "north", "nose", "note", "notepad", "nothing", "notice", "notion", "nougat", "noun", "novel", "nudge", "nuke", "number", "numeric", "nun", "nurse", "nursery", "nursing", "nurture", "nut", "nutmeg",
- "nylon", "nymph", "oak", "oar", "oasis", "oat", "oatmeal", "oats", "obesity", "obi", "object", "oboe", "ocean", "ocelot", "octagon", "octave", "octavo", "octet", "octopus", "odyssey", "oeuvre", "offence", "offense", "offer", "office", "officer", "offset",
- "oil", "okra", "oldie", "oleo", "olive", "omega", "omelet", "onion", "online", "onset", "opening", "opera", "opinion", "opium", "opossum", "optimal", "option", "orange", "orator", "orchard", "orchid", "order", "ore", "oregano", "organ", "orient", "origin",
- "osmosis", "osprey", "ostrich", "other", "otter", "ottoman", "ounce", "outback", "outcome", "outfit", "outlaw", "outlay", "outlet", "outline", "outlook", "output", "outrage", "outrun", "outset", "outside", "oval", "ovary", "oven", "owl", "owner", "ox",
- "oxford", "oxygen", "oyster", "ozone", "pace", "pack", "package", "packet", "pad", "paddle", "paddock", "pagan", "page", "pagoda", "pail", "pain", "paint", "painter", "pair", "pajamas", "palace", "palate", "palm", "pan", "pancake", "panda", "panel", "panic",
- "pannier", "panpipe", "pansy", "panther", "panties", "pantry", "pants", "panty", "papa", "papaya", "paper", "parable", "parade", "parcel", "pard", "pardon", "parent", "park", "parka", "parking", "parole", "parrot", "parser", "parsley", "parsnip", "part",
- "partner", "party", "pass", "passage", "passing", "passion", "passive", "past", "pasta", "paste", "pastor", "pastry", "pasture", "pat", "patch", "pate", "patent", "path", "pathway", "patient", "patina", "patio", "patriot", "patrol", "patron", "pattern",
- "patty", "pause", "paw", "pay", "payee", "payment", "payoff", "pea", "peace", "peach", "peacoat", "peacock", "peak", "peanut", "pear", "pearl", "peasant", "pecan", "pecker", "pedal", "peek", "peen", "peer", "pelican", "pelt", "pen", "penalty", "pence",
- "pencil", "pendant", "penguin", "penis", "pennant", "penny", "pension", "peony", "people", "pepper", "percent", "perch", "perfume", "period", "permit", "perp", "person", "pest", "pet", "petal", "pew", "phase", "phone", "photo", "phrase", "physics",
- "pianist", "piano", "piccolo", "pick", "pickax", "pickaxe", "picket", "pickle", "pickup", "picnic", "picture", "pie", "piece", "pier", "piety", "pig", "pigeon", "piglet", "pigpen", "pigsty", "pike", "pilaf", "pile", "pilgrim", "pill", "pillar", "pillbox",
- "pillow", "pilot", "pimp", "pimple", "pin", "pine", "ping", "pink", "pinkie", "pinot", "pint", "pinto", "pinworm", "pioneer", "pipe", "piracy", "pirate", "piss", "pistol", "pit", "pita", "pitch", "pitcher", "pith", "pizza", "place", "placebo", "placode",
- "plain", "plan", "plane", "planet", "plant", "planter", "planula", "plaster", "plastic", "plate", "platter", "play", "player", "plea", "pleat", "pledge", "plenty", "plier", "pliers", "plight", "plot", "plough", "plover", "plow", "plowman", "plug", "plugin",
- "plum", "plumber", "plume", "plunger", "plywood", "pocket", "pod", "podcast", "poem", "poet", "poetry", "point", "poison", "poker", "pole", "polenta", "police", "policy", "polish", "poll", "polo", "polyp", "pomelo", "pompom", "poncho", "pond", "pony", "pool",
- "poor", "pop", "popcorn", "poppy", "porch", "pork", "port", "porter", "portion", "post", "postage", "postbox", "poster", "postfix", "pot", "potato", "pottery", "potty", "pouch", "poultry", "pound", "poverty", "powder", "power", "prairie", "praise", "pray",
- "prayer", "preface", "prefix", "prelude", "premier", "premise", "premium", "present", "press", "presume", "pretzel", "prey", "price", "pricing", "pride", "priest", "primary", "primate", "prince", "print", "printer", "prior", "prison", "privacy", "private",
- "prize", "probe", "problem", "process", "proctor", "produce", "product", "profile", "profit", "program", "project", "promise", "prompt", "pronoun", "proof", "propane", "prophet", "prose", "protein", "protest", "prow", "prune", "pruner", "pub", "public",
- "pudding", "puddle", "puffin", "pug", "puggle", "pulley", "pulse", "puma", "pump", "pumpkin", "pun", "punch", "pup", "pupa", "pupil", "puppet", "puppy", "puritan", "purity", "purple", "purpose", "purr", "purse", "pursuit", "push", "pusher", "put", "puzzle",
- "pyramid", "quail", "quality", "quart", "quarter", "quartet", "quartz", "queen", "query", "quest", "quiche", "quiet", "quill", "quilt", "quince", "quinoa", "quit", "quiver", "quota", "quote", "rabbi", "rabbit", "raccoon", "race", "racer", "racing", "racism",
- "racist", "rack", "radar", "radio", "radish", "raffle", "raft", "rag", "rage", "raid", "rail", "railing", "railway", "raiment", "rain", "rainbow", "rainy", "raise", "raisin", "rake", "rally", "ram", "rambler", "ramen", "ramie", "ranch", "rancher", "range",
- "ranger", "rank", "rap", "rape", "rat", "rate", "rating", "ratio", "rations", "raven", "ravioli", "rawhide", "ray", "rayon", "razor", "reach", "read", "reader", "reading", "real", "reality", "realm", "reamer", "rear", "reason", "rebel", "reboot", "recall",
- "receipt", "recess", "recipe", "record", "recruit", "red", "redhead", "reef", "reform", "refuge", "refund", "refusal", "refuse", "regard", "regime", "region", "regret", "reject", "relay", "release", "relief", "relish", "remains", "remark", "remnant",
- "remote", "removal", "rent", "repair", "repeat", "replica", "reply", "report", "request", "resale", "rescue", "reserve", "reset", "residue", "resist", "resolve", "resort", "respect", "respite", "rest", "result", "resume", "retina", "retreat", "return",
- "reunion", "reveal", "revenge", "revenue", "reverse", "review", "revival", "reward", "rhubarb", "rhyme", "rhythm", "rib", "ribbon", "rice", "riddle", "ride", "rider", "ridge", "riding", "rifle", "right", "rim", "ring", "riot", "rip", "ripple", "rise", "riser",
- "risk", "rite", "ritual", "river", "rivulet", "road", "roadway", "roar", "roast", "robe", "robin", "robot", "rock", "rocker", "rocket", "rod", "role", "roll", "roller", "romaine", "romance", "roof", "room", "rooster", "root", "rope", "rose", "roster", "rostrum",
- "round", "route", "router", "routine", "row", "rowboat", "rowing", "rubber", "rubbish", "rubric", "ruby", "ruckus", "ruffle", "rug", "rugby", "ruin", "rule", "ruler", "ruling", "rum", "rumor", "run", "runaway", "runner", "running", "runway", "rush", "rust",
- "rye", "sabre", "sac", "sack", "saddle", "sadness", "safari", "safe", "safety", "saffron", "sage", "sail", "sailing", "sailor", "saint", "sake", "salad", "salami", "salary", "sale", "salmon", "salon", "saloon", "salsa", "salt", "salute", "samovar", "sampan",
- "sample", "samurai", "sand", "sandal", "sandbar", "sanity", "sardine", "sari", "sarong", "sash", "satin", "satire", "sauce", "saucer", "sausage", "savage", "saving", "savings", "savior", "saviour", "savory", "saw", "scale", "scalp", "scam", "scanner", "scarf",
- "scene", "scenery", "scent", "schema", "scheme", "scholar", "school", "science", "scooter", "scope", "score", "scorn", "scotch", "scout", "scow", "scrap", "scraper", "scratch", "screen", "screw", "scrim", "scrip", "script", "sea", "seabass", "seafood",
- "seagull", "seal", "search", "seaside", "season", "seat", "seaweed", "second", "secrecy", "secret", "section", "sector", "seed", "seeder", "seeker", "seep", "segment", "seizure", "self", "seller", "selling", "seminar", "senate", "senator", "sender", "senior",
- "sense", "sensor", "sepal", "sequel", "serial", "series", "sermon", "serum", "serval", "servant", "server", "service", "sesame", "session", "set", "setback", "setting", "settler", "sewer", "sex", "shack", "shackle", "shade", "shadow", "shaker", "shallot",
- "shame", "shampoo", "shanty", "shape", "share", "shark", "shaw", "shawl", "shear", "sheath", "shed", "sheep", "sheet", "shelf", "shell", "shelter", "sherbet", "sherry", "shield", "shift", "shin", "shine", "shingle", "ship", "shipper", "shirt", "shjt", "shoat",
- "shock", "shoe", "shoes", "shofar", "shoot", "shop", "shopper", "shore", "short", "shorts", "shot", "shout", "shovel", "show", "shower", "shred", "shrimp", "shrine", "sibling", "sick", "side", "sidecar", "siding", "siege", "sigh", "sight", "sign", "signal",
- "signet", "signify", "signup", "silence", "silica", "silicon", "silk", "sill", "silly", "silo", "silver", "simple", "sin", "singer", "singing", "sink", "sip", "sir", "sister", "sitar", "site", "size", "skate", "skating", "skean", "ski", "skiing", "skill",
- "skin", "skirt", "skull", "skunk", "sky", "skyline", "skywalk", "slang", "slash", "slate", "slave", "slavery", "slaw", "sled", "sledge", "sleep", "sleet", "sleuth", "slice", "slide", "slider", "slime", "slip", "slipper", "slope", "slot", "sloth", "slump",
- "smell", "smile", "smith", "smock", "smog", "smoke", "smoking", "smolt", "snack", "snail", "snake", "snap", "snarl", "sneaker", "sneeze", "sniffle", "snob", "snorer", "snow", "snowman", "snuck", "snug", "snuggle", "soap", "soccer", "society", "sock", "socks",
- "soda", "sofa", "soil", "soldier", "sole", "someone", "son", "sonar", "sonata", "song", "sonnet", "soot", "soprano", "sorbet", "sorghum", "sorrel", "sorrow", "sort", "soul", "sound", "soup", "source", "south", "sow", "soy", "soybean", "space", "spacing",
- "spade", "span", "spandex", "spank", "spark", "sparrow", "spasm", "spat", "spatula", "spawn", "speaker", "spear", "spec", "special", "species", "speech", "speed", "spell", "spelt", "sphere", "sphynx", "spice", "spider", "spike", "spill", "spinach", "spine",
- "spiral", "spirit", "spit", "spite", "spleen", "split", "sponge", "sponsor", "spool", "spoon", "spork", "sport", "spot", "spouse", "sprag", "sprat", "spray", "spread", "spree", "spring", "sprout", "spruce", "spud", "spume", "spur", "spy", "square", "squash",
- "squid", "stab", "stable", "stack", "stadium", "staff", "stag", "stage", "stain", "stair", "stake", "stalk", "stall", "stamen", "stamina", "stamp", "stance", "stand", "star", "start", "starter", "state", "statin", "station", "statue", "status", "statute",
- "stay", "steak", "stealth", "steam", "steel", "steeple", "stem", "stench", "stencil", "step", "stepson", "stereo", "stew", "steward", "stick", "sticker", "still", "sting", "stinger", "stitch", "stock", "stole", "stomach", "stone", "stool", "stop", "storage",
- "store", "storey", "storm", "story", "stot", "stove", "strait", "strand", "strap", "straw", "stream", "street", "stress", "stretch", "strife", "strike", "string", "strip", "stripe", "strobe", "stroke", "strudel", "stucco", "stud", "student", "studio", "study",
- "stuff", "stump", "sty", "style", "styling", "stylus", "sub", "subject", "subset", "subsidy", "suburb", "subway", "success", "suck", "sucker", "suede", "suet", "sugar", "suicide", "suit", "suite", "sulfur", "sultan", "sum", "summary", "summer", "summit", "sun",
- "sunbeam", "sundae", "sunday", "sundial", "sunlamp", "sunrise", "sunroom", "sunset", "supper", "supply", "support", "supreme", "surface", "surge", "surgeon", "surgery", "surname", "surplus", "survey", "sushi", "suspect", "swallow", "swamp", "swan", "swath",
- "sweat", "sweater", "sweets", "swell", "swim", "swine", "swing", "switch", "swivel", "sword", "symbol", "symptom", "synergy", "synod", "synonym", "syrup", "system", "t-shirt", "tab", "tabby", "table", "tablet", "tackle", "taco", "tactics", "tactile",
- "tadpole", "tag", "tail", "tailbud", "tailor", "tale", "talent", "talk", "talking", "tamale", "tambour", "tan", "tandem", "tank", "tanker", "tankful", "tap", "tape", "tapioca", "target", "taro", "tart", "task", "tassel", "taste", "tatami", "tattler", "tattoo",
- "tavern", "tax", "taxi", "taxicab", "tea", "teacher", "team", "teapot", "tear", "tech", "teen", "teepee", "tell", "teller", "temp", "temper", "temple", "tempo", "tenant", "tender", "tenet", "tennis", "tenor", "tension", "tensor", "tent", "tenth", "tepee",
- "term", "termite", "terrace", "terror", "test", "testing", "text", "textual", "texture", "thanks", "thaw", "theater", "theft", "theism", "theme", "theory", "therapy", "thesis", "thief", "thigh", "thing", "thirst", "thistle", "thong", "thongs", "thorn", "thought",
- "thread", "threat", "thrift", "thrill", "throat", "throne", "thrush", "thrust", "thug", "thumb", "thump", "thunder", "thyme", "tiara", "tic", "tick", "ticket", "tide", "tie", "tiger", "tights", "tile", "till", "tilt", "timbale", "timber", "time", "timeout",
- "timer", "timing", "timpani", "tin", "tinkle", "tintype", "tip", "tire", "tissue", "title", "toad", "toast", "toaster", "tobacco", "today", "toe", "toenail", "toffee", "tofu", "tog", "toga", "toilet", "toll", "tom-tom", "tomato", "tomb", "ton", "tone", "tongue",
- "tonic", "tonight", "tool", "toot", "tooth", "top", "top-hat", "topic", "topsail", "toque", "tornado", "torso", "torte", "tosser", "total", "tote", "touch", "tour", "tourism", "tourist", "towel", "tower", "town", "toy", "trace", "track", "tract", "tractor",
- "trade", "trader", "trading", "traffic", "tragedy", "trail", "trailer", "train", "trainer", "trait", "tram", "tramp", "trance", "transit", "transom", "trap", "trash", "travel", "tray", "treat", "treaty", "tree", "trek", "trellis", "tremor", "trench", "trend",
- "triad", "trial", "tribe", "trick", "trigger", "trim", "trinket", "trip", "tripod", "tritone", "triumph", "trolley", "troop", "trooper", "trophy", "trouble", "trout", "trove", "trowel", "truck", "trumpet", "trunk", "trust", "trustee", "truth", "try",
- "tsunami", "tub", "tuba", "tube", "tuber", "tug", "tugboat", "tuition", "tulip", "tumbler", "tummy", "tuna", "tune", "tune-up", "tunic", "tunnel", "turban", "turf", "turkey", "turn", "turning", "turnip", "turret", "turtle", "tusk", "tussle", "tutu",
- "tuxedo", "tweet", "twig", "twine", "twins", "twist", "twister", "twitter", "type", "typhoon", "ukulele", "uncle", "unibody", "uniform", "union", "unique", "unit", "unity", "update", "upgrade", "uplift", "upper", "upward", "urge", "urgency", "urn", "usage",
- "use", "user", "usher", "usual", "utensil", "utility", "vaccine", "vacuum", "vagrant", "valance", "valley", "value", "vampire", "van", "vanadyl", "vane", "vanilla", "vanity", "variant", "variety", "vase", "vault", "veal", "vector", "vehicle", "veil", "vein",
- "veldt", "vellum", "velvet", "vendor", "veneer", "venison", "venom", "venti", "venture", "venue", "veranda", "verb", "verdict", "verse", "version", "vertigo", "verve", "vessel", "vest", "vet", "veteran", "veto", "vibe", "vice", "victim", "victory", "video",
- "view", "viewer", "villa", "village", "vine", "vinegar", "vintage", "vintner", "vinyl", "viola", "violet", "violin", "virtue", "virus", "visa", "viscose", "vise", "vision", "visit", "visitor", "visor", "vista", "visual", "vitamin", "vitro", "vivo", "vixen",
- "vodka", "vogue", "voice", "void", "vol", "volcano", "volume", "vomit", "vote", "voter", "voting", "voyage", "vulture", "wad", "wafer", "waffle", "wage", "wagon", "waist", "wait", "waiter", "waiting", "waiver", "wake", "walk", "walker", "walking", "walkway",
- "wall", "wallaby", "wallet", "walnut", "walrus", "wampum", "wannabe", "want", "war", "warden", "warfare", "warlock", "warlord", "warm-up", "warming", "warmth", "warning", "warrant", "warren", "warrior", "wasabi", "wash", "washer", "washtub", "wasp", "waste",
- "wasting", "watch", "watcher", "water", "wave", "wax", "way", "wealth", "weapon", "wear", "weasel", "weather", "web", "webinar", "webmail", "webpage", "website", "wedding", "wedge", "weed", "weeder", "week", "weekend", "weight", "weird", "welcome", "welfare",
- "well", "west", "western", "wet-bar", "wetland", "wetsuit", "whack", "whale", "wharf", "wheat", "wheel", "whelp", "whey", "whip", "whisker", "whiskey", "whisper", "whistle", "white", "whole", "whorl", "wick", "widget", "widow", "width", "wife", "wifi", "wild",
- "will", "willow", "win", "wind", "windage", "window", "wine", "winery", "wing", "wingman", "wingtip", "wink", "winner", "winter", "wire", "wiretap", "wiring", "wisdom", "wiseguy", "wish", "wit", "witch", "witness", "wok", "wolf", "woman", "wombat", "wonder",
- "wont", "wood", "wool", "woolens", "word", "wording", "work", "worker", "working", "workout", "world", "worm", "worry", "worship", "worth", "wound", "wrap", "wrapper", "wreck", "wrecker", "wren", "wrench", "wrinkle", "wrist", "writer", "writing", "wrong",
- "yacht", "yahoo", "yak", "yam", "yang", "yard", "yarn", "yawl", "year", "yeast", "yellow", "yew", "yin", "yoga", "yogurt", "yoke", "yolk", "young", "youth", "yoyo", "yurt", "zampone", "zebra", "zen", "zephyr", "zero", "zinc", "zipper", "zither", "zombie", "zone",
- "zoo", "zoology",
- ];
+ const nouns = [
+ "glue", "riot", "boom", "veil", "poet", "hype", "cafe", "gene", "fame", "sin", "zon", "barb", "core", "dust", "bite", "maid", "scar",
+ "wing", "horn", "crew", "lake", "duke", "mask", "dawn", "seed", "tank", "flag", "jazz", "tart", "brew", "meow", "boot", "shoe", "sage", "drum",
+ "babe", "cash", "luck", "lime", "eyes", "boat", "milk", "tuna", "cube", "oreo", "worm", "rage", "itch", "four", "bomb", "pear", "ship", "oven",
+ "fear", "hate", "leaf", "hero", "wife", "bean", "hope", "girl", "baby", "meme", "wish", "one", "nine", "work", "cake", "lady", "fire", "pain",
+ "rain", "fool", "soul", "tree", "five", "fish", "love", "life", "elk", "dad", "hog", "elf", "mop", "rod", "bat", "bug", "bot", "pus", "ufo",
+ "zen", "ark", "rag", "egg", "bed", "car", "boy", "man", "cricket", "aura", "moon", "hippo", "vortex", "palm", "panther", "meteor", "deer",
+ "vein", "plan", "atom", "hole", "weed", "boss", "army", "meat", "lock", "song", "rat", "rose", "blossom", "twin", "comet", "fist", "crow",
+ "star", "starlight", "axe", "fury", "mouse", "blow", "swan", "bee", "asp", "viper", "feather", "bird", "bolt", "sun", "mind", "beaver", "frog",
+ "mist", "day", "night", "falcon", "blood", "poison", "lily", "inferno", "kiss", "lotus", "giant", "monarch", "lord", "autumn", "spring",
+ "summer", "winter", "paragon", "vulture", "condor", "coil", "chain", "spell", "dove", "peach", "petal", "droplet", "eruption", "heaven", "fog",
+ "boa", "needle", "shield", "rock", "turtle", "ghost", "death", "cobra", "bane", "princess", "king", "fingers", "toes", "hand", "foot", "ear",
+ "eye", "skull", "cat", "dog", "pig", "piggy", "cow", "snake", "horse", "rabbit", "goat", "wolf", "sheep", "duck", "eagle", "crab", "baboon", "basilisk",
+ "fox", "badger", "beetle", "butterfly", "shark", "clownfish", "crane", "cicada", "dingo", "elephant", "jackal", "jaguar", "lion", "mandrill",
+ "lungfish", "heart", "spleen", "liver", "guts", "brains", "bones", "chocolate", "candy", "surprise", "cheese", "furball", "salami", "beef",
+ "supreme", "taco", "burger", "hotdog", "carrot", "onion", "fungus", "brick", "rock", "banana", "killer", "demon", "angel", "saint", "bamboo",
+ "panda", "broom", "hammer", "snow", "cur", "toad", "raven", "claw", "pine", "rice", "sushi", "bread", "toast", "cereal", "smoke", "fart",
+ "beer", "bear", "faucet", "pipe", "iron", "dork", "genius", "hunter", "farmer", "wiz", "witch", "churro", "donut", "shrimp", "sand", "pagoda",
+ "eel", "ant", "pants", "jeans", "socks", "sword", "fork", "pizza", "trap", "pork", "wort", "sack", "hawk", "rite", "tire", "dirt", "plum",
+ "ATM", "CD", "SUV", "TV", "abacus", "abbey", "abdomen", "ability", "absence", "abuse", "academy", "accent", "access", "accord", "account", "acetate", "acid", "acorn",
+ "acre", "acrylic", "act", "action", "actor", "actress", "ad", "adapter", "address", "admin", "admire", "adobe", "adult", "advance", "advent", "adverb", "advice", "adviser",
+ "affair", "affect", "afoul", "age", "agency", "agenda", "agent", "aglet", "agony", "aid", "aide", "aim", "air", "airbag", "airbus", "airfare", "airline", "airmail", "airman",
+ "airport", "airship", "alarm", "alb", "album", "alcohol", "alcove", "alder", "ale", "alert", "alfalfa", "algebra", "alias", "alibi", "alien", "alley", "alloy", "almanac", "almond",
+ "alpaca", "alpha", "altar", "alto", "amazon", "amber", "amenity", "amnesty", "amount", "anagram", "analog", "analogy", "analyst", "anarchy", "anatomy", "anchovy", "android", "angel",
+ "anger", "angina", "angle", "angora", "anguish", "animal", "anime", "anise", "ankle", "anklet", "annual", "anorak", "answer", "ant", "antigen", "antique", "antler", "antling", "anxiety",
+ "anybody", "anyone", "ape", "apology", "app", "apparel", "appeal", "apple", "apricot", "apron", "apse", "aquifer", "arcade", "arch", "archer", "area", "arena", "ark", "arm", "armoire", "armor",
+ "armour", "armpit", "armrest", "army", "array", "arrest", "arrival", "arrow", "art", "artery", "arthur", "article", "artist", "ascend", "ascent", "ascot", "ash", "ashram", "ashtray", "aside",
+ "aspect", "asphalt", "aspic", "assault", "asset", "assist", "atelier", "athlete", "atom", "atrium", "attack", "attempt", "attic", "auction", "audit", "aunt", "author", "auto",
+ "autumn", "avenue", "average", "avocado", "award", "awe", "axis", "azimuth", "babe", "baboon", "baby", "back", "back-up", "backup", "bacon", "badge", "badger", "bag", "bagel", "baggage", "baggie",
+ "baggy", "bagpipe", "bail", "bait", "bake", "baker", "bakery", "balance", "balcony", "ball", "ballet", "balloon", "ballot", "bamboo", "ban", "banana", "band", "bandana", "bangle", "banjo", "bank",
+ "banker", "banking", "banner", "banyan", "baobab", "bar", "barber", "bargain", "barge", "barium", "bark", "barley", "barn", "barrage", "barrel", "barrier", "base", "basics", "basil", "basin", "basis",
+ "basket", "bass", "bassoon", "bat", "bath", "bather", "bathtub", "batter", "battery", "batting", "battle", "bay", "bayou", "beach", "bead", "beak", "beam", "bean", "beanie", "bear", "beard", "beast",
+ "beastie", "beat", "beating", "beauty", "beaver", "beck", "bed", "bedrock", "bedroom", "bee", "beech", "beef", "beer", "beet", "beetle", "beggar", "begonia", "behalf", "behest", "behold", "being",
+ "belfry", "belief", "bell", "bellows", "belly", "belt", "bench", "bend", "benefit", "beret", "berry", "bet", "beyond", "bias", "bicycle", "bid", "bidder", "bidding", "bidet", "bijou", "bike", "bikini",
+ "bill", "billing", "billion", "bin", "biology", "biopsy", "biplane", "birch", "bird", "birth", "biscuit", "bit", "bite", "bitten", "bitter", "black", "bladder", "blade", "blame", "blank", "blanket",
+ "blast", "blazer", "blend", "blight", "blind", "blinker", "blister", "block", "blocker", "blog", "blogger", "blood", "bloom", "bloomer", "blossom", "blouse", "blow", "blowgun", "blue", "blush", "boar",
+ "board", "boat", "bob", "bobcat", "body", "bog", "bolero", "bolt", "bomb", "bomber", "bombing", "bond", "bonding", "bone", "bonfire", "bongo", "bonnet", "bonsai", "bonus", "book", "bookend", "booking",
+ "booklet", "boolean", "boom", "boon", "boost", "booster", "boot", "bootee", "bootie", "booty", "border", "bore", "bosom", "boss", "botany", "bother", "bottle", "bottom", "boudoir", "bough", "boulder",
+ "bouquet", "bout", "bow", "bower", "bowl", "bowler", "bowling", "bowtie", "box", "boxer", "boy", "boycott", "boyhood", "bra", "brace", "bracket", "brain", "brake", "bran", "branch", "brand", "brandy",
+ "brass", "bread", "break", "breast", "breath", "breeze", "brewer", "bribery", "brick", "bride", "bridge", "brief", "briefly", "briefs", "brink", "brisket", "broad", "broiler", "broker", "bronco",
+ "bronze", "brooch", "brood", "brook", "broom", "brother", "brow", "brown", "brownie", "browser", "brunch", "brush", "bubble", "buck", "bucket", "buckle", "bud", "buddy", "budget", "buffalo", "buffer",
+ "buffet", "bug", "buggy", "bugle", "builder", "bulb", "bulk", "bull", "bullet", "bump", "bumper", "bun", "bunch", "burden", "bureau", "burglar", "burial", "burn", "burning", "burrito", "burro", "burrow",
+ "burst", "bus", "bush", "bust", "bustle", "butane", "butcher", "butler", "butter", "button", "buy", "buyer", "buying", "buzz", "buzzard", "c-clamp", "cabana", "cabbage", "cabin", "cabinet", "cable",
+ "caboose", "cacao", "cactus", "caddy", "cadet", "cafe", "caftan", "cage", "cake", "calf", "caliber", "calibre", "calico", "call", "calm", "calorie", "camel", "cameo", "camera", "camp", "camper", "campus",
+ "can", "canal", "cancer", "candle", "candy", "cane", "cannon", "canoe", "canon", "canopy", "canteen", "canvas", "cap", "cape", "caper", "capital", "capon", "captain", "caption", "captor", "car", "carabao",
+ "caramel", "caravan", "carbon", "card", "care", "career", "cargo", "caribou", "carload", "carol", "carp", "carpet", "carport", "carrier", "carrot", "carry", "cart", "cartel", "carter", "cartoon", "carving",
+ "cascade", "case", "cash", "cashew", "cashier", "casino", "casket", "cassava", "cassock", "cast", "castle", "cat", "catch", "catcher", "cation", "catsup", "cattle", "causal", "cause", "caution", "cave",
+ "caviar", "cayenne", "ceiling", "celery", "cell", "cellar", "cello", "celsius", "cement", "census", "cent", "center", "centre", "century", "ceramic", "cereal", "chafe", "chain", "chair", "chaise", "chalet",
+ "chalice", "chalk", "chamber", "chance", "change", "channel", "chaos", "chap", "chapel", "chapter", "chard", "charge", "charger", "charity", "charm", "charset", "chart", "charter", "chasm", "chassis",
+ "chateau", "chatter", "check", "cheddar", "cheek", "cheer", "cheese", "cheetah", "chef", "chem", "cheque", "cherry", "chess", "chest", "chick", "chicken", "chicory", "chief", "child", "chili", "chill",
+ "chime", "chin", "chino", "chip", "chive", "chives", "choice", "choir", "choker", "chop", "chops", "chord", "chorus", "chow", "chowder", "chrome", "chub", "chuck", "chug", "church", "churn", "chutney",
+ "cicada", "cinder", "cinema", "circle", "circuit", "cirrus", "citizen", "citron", "citrus", "city", "claim", "clam", "clamp", "clan", "clank", "clarity", "clasp", "class", "classic", "clause", "clave",
+ "clavier", "claw", "clay", "cleaner", "cleat", "clef", "cleft", "cleric", "clerk", "click", "client", "cliff", "climate", "climb", "clinic", "clip", "clipper", "cloak", "clock", "clogs", "clone", "close",
+ "closet", "closing", "closure", "cloth", "clothes", "cloud", "clove", "clover", "cloves", "club", "clue", "cluster", "clutch", "coach", "coal", "coast", "coaster", "coat", "cob", "cobbler", "cobweb", "cock",
+ "cockpit", "cocoa", "coconut", "cod", "code", "codling", "codon", "coffee", "coffin", "cohort", "coil", "coin", "coke", "cold", "collar", "collard", "college", "colon", "colony", "color", "colt", "column",
+ "comb", "combat", "combine", "comedy", "comfort", "comic", "comics", "comma", "command", "comment", "common", "company", "compass", "complex", "compost", "con", "concept", "concern", "concert", "condor",
+ "conduct", "cone", "conga", "congo", "conifer", "consent", "consist", "console", "consul", "contact", "contact", "lens", "content", "contest", "context", "contour", "control", "convert", "cook", "cookie",
+ "cooking", "cop", "cop-out", "cope", "copper", "copy", "copying", "coral", "cord", "core", "cork", "corn", "corner", "cornet", "corps", "corral", "corsage", "cosset", "cost", "costume", "cot", "cottage",
+ "cotton", "couch", "cougar", "cough", "council", "counsel", "count", "counter", "country", "county", "couple", "coupon", "courage", "course", "court", "cousin", "cover", "cow", "cowbell", "cowboy", "coyote",
+ "crab", "crack", "cracker", "cradle", "craft", "crane", "cranky", "crap", "crash", "crate", "cravat", "craw", "crawdad", "crayon", "crazy", "cream", "creator", "creche", "credit", "creek", "creme", "brulee", "crepe",
+ "crest", "crew", "crewman", "crewmen", "cria", "crib", "cricket", "crime", "crisis", "crisp", "critic", "crocus", "crook", "crop", "cross", "crotch", "croup", "crow", "crowd", "crown", "crude", "cruelty", "cruise",
+ "crumb", "crunch", "crush", "crust", "cry", "crystal", "cub", "cube", "cuckoo", "cue", "cuisine", "culture", "culvert", "cup", "cupcake", "cupola", "curd", "cure", "curio", "curl", "curler", "currant", "current",
+ "curry", "curse", "cursor", "curtain", "curve", "cushion", "custard", "custody", "custom", "cut", "cuticle", "cutlet", "cutover", "cutting", "cycle", "cyclone", "cygnet", "cymbal", "cynic", "cyst", "dad", "daddy",
+ "dagger", "dahlia", "daikon", "daily", "dairy", "daisy", "dam", "damage", "dame", "damn", "dance", "dancer", "dancing", "danger", "dare", "dark", "darn", "dart", "dash", "data", "date", "dawn", "day", "daybed", "dead",
+ "deal", "dealer", "dealing", "dearest", "death", "debate", "debris", "debt", "debtor", "decade", "decency", "decimal", "deck", "decline", "decoder", "deduce", "deed", "deep", "deer", "default", "defeat", "defense",
+ "deficit", "degree", "delay", "delight", "demand", "demon", "demur", "den", "denim", "density", "dentist", "deposit", "depot", "depth", "deputy", "derby", "derrick", "descent", "desert", "design", "desire", "desk",
+ "desktop", "dessert", "destiny", "detail", "detour", "device", "devil", "dew", "dhow", "diadem", "diagram", "dial", "dialect", "diam", "diamond", "diaper", "diarist", "diary", "dibble", "dick", "dickey", "diction",
+ "die", "diesel", "diet", "diffuse", "dig", "digger", "digging", "digit", "dignity", "dill", "dime", "dimple", "diner", "dinghy", "dining", "dinner", "dioxide", "dip", "diploma", "dirndl", "dirt", "disco", "disdain",
+ "disease", "disgust", "dish", "disk", "display", "dispute", "divan", "diver", "divide", "divider", "divine", "diving", "divorce", "doc", "dock", "doctor", "doe", "dog", "doggie", "dogsled", "dogwood", "doing", "doll",
+ "dollar", "dollop", "dolman", "dolor", "dolphin", "domain", "dome", "donkey", "donor", "donut", "door", "doorway", "dory", "dose", "dot", "double", "doubt", "doubter", "dough", "down", "dozen", "draft", "drag", "dragon",
+ "drain", "drake", "drama", "drapes", "draw", "drawer", "drawing", "dream", "dreamer", "dredger", "dress", "dresser", "drill", "drink", "drive", "driver", "driving", "drizzle", "drop", "drug", "drum", "drummer", "drunk",
+ "dryer", "duck", "dud", "dude", "due", "duel", "dueling", "duffel", "dugout", "dump", "dump", "truck", "dune", "dune", "buggy", "dungeon", "durian", "dusk", "dust", "dust", "storm", "duster", "duty", "dwarf", "dwell",
+ "dynamo", "dynasty", "e-book", "e-mail", "eagle", "eaglet", "ear", "eardrum", "earplug", "earring", "earth", "ease", "easel", "east", "eating", "eaves", "echidna", "eclipse", "ecology", "economy", "eddy", "edge",
+ "edger", "edible", "editing", "edition", "editor", "eel", "effect", "effort", "egg", "egghead", "eggnog", "ego", "ejector", "elbow", "element", "elf", "elicit", "elite", "elixir", "elk", "ellipse", "elm", "elver",
+ "email", "emanate", "embassy", "embryo", "emerald", "emery", "emitter", "emotion", "empire", "employ", "emu", "enclave", "end", "endive", "enemy", "energy", "engine", "enigma", "enquiry", "entity", "entree", "entry",
+ "envy", "enzyme", "epee", "ephyra", "epic", "episode", "epoch", "eponym", "epoxy", "equal", "equinox", "equity", "era", "eraser", "erosion", "error", "escape", "escort", "essay", "essence", "estate", "estuary", "ethics",
+ "ethyl", "eve", "evening", "event", "evil", "ex-wife", "exam", "example", "excerpt", "excess", "excuse", "exhaust", "exhibit", "exile", "exit", "expense", "expert", "export", "expose", "extent", "extreme", "eye", "eyeball",
+ "eyebrow", "eyelash", "eyelid", "eyelids", "eyrie", "fabric", "face", "facet", "fact", "factor", "factory", "faculty", "fail", "failure", "fairy", "faith", "fall", "fallacy", "fame", "family", "fan", "fang", "fanny", "fantasy",
+ "farm", "farmer", "farming", "farrow", "fascia", "fashion", "fat", "fate", "father", "fatigue", "faucet", "fault", "fav", "fava", "favor", "fawn", "fax", "fear", "feast", "feather", "feature", "fedora", "fee", "feed", "feeding",
+ "feel", "feeling", "fellow", "felony", "female", "fen", "fence", "fencing", "fender", "feng", "fennel", "ferret", "ferry", "fetus", "few", "fiber", "fibre", "ficlet", "fiction", "fiddle", "field", "fiery", "fiesta", "fifth", "fig",
+ "fight", "fighter", "figure", "file", "filing", "fill", "fillet", "filly", "film", "filter", "filth", "final", "finance", "finding", "fine", "finer", "finger", "finish", "fir", "fire", "fireman", "firm", "first", "fish", "fishery",
+ "fishing", "fishnet", "fisting", "fit", "fitness", "fix", "fixture", "flag", "flair", "flame", "flan", "flanker", "flare", "flash", "flat", "flavor", "flax", "fleck", "fleece", "flesh", "flick", "flicker", "flight", "flint", "flock",
+ "flood", "floor", "floozie", "flour", "flow", "flower", "flu", "fluke", "flume", "flung", "flute", "fly", "flytrap", "foal", "foam", "fob", "focus", "fog", "fold", "folder", "folk", "fondue", "font", "food", "fool", "foot", "footage",
+ "forage", "forager", "foray", "force", "ford", "forearm", "forest", "forever", "forgery", "fork", "form", "formal", "format", "former", "formula", "fort", "forte", "fortune", "forum", "founder", "fourths", "fowl", "fox", "frame",
+ "fraud", "freak", "freckle", "freedom", "freezer", "freight", "frenzy", "freon", "fresco", "fridge", "friend", "fries", "frigate", "fright", "fringe", "fritter", "frock", "frog", "front", "frost", "frown", "fruit", "fry", "fvck",
+ "fuel", "fugato", "full", "fun", "fund", "funding", "funeral", "fur", "furnace", "furry", "futon", "future", "gadget", "gaffe", "gaffer", "gain", "gaiters", "gale", "gallery", "galley", "gallon", "game", "gaming", "gander", "gang",
+ "gap", "garage", "garb", "garbage", "garden", "garlic", "garment", "garter", "gas", "gasket", "gasp", "gate", "gateway", "gather", "gator", "gauge", "gavel", "gazebo", "gazelle", "gear", "geek", "gel", "gelatin", "gelding", "gem",
+ "gemsbok", "gender", "gene", "general", "genie", "genius", "genre", "geology", "gerbil", "gesture", "geyser", "gherkin", "ghost", "giant", "gift", "gig", "giggle", "ginger", "ginseng", "giraffe", "girdle", "girl", "git", "glacier",
+ "glance", "gland", "glass", "glasses", "glee", "glen", "glider", "gliding", "glimpse", "globe", "gloom", "glory", "glove", "glow", "glucose", "glue", "glut", "gnat", "gnu", "go-kart", "goal", "goat", "gobbler", "god", "goddess", "goggles",
+ "going", "gold", "golf", "gondola", "gong", "good", "goodbye", "goodie", "goose", "gopher", "gorilla", "gosling", "gossip", "gown", "grace", "grade", "graft", "grain", "gram", "grammar", "gran", "grand", "grandma", "grandpa", "granny",
+ "granola", "grant", "grape", "graph", "graphic", "grasp", "grass", "gravel", "gravity", "gravy", "gray", "grease", "greed", "green", "greens", "grenade", "grey", "grid", "grief", "grill", "grin", "grip", "gripper", "grit", "grocery",
+ "ground", "group", "grouper", "grouse", "grove", "growth", "grub", "guard", "guava", "guess", "guest", "guide", "guilder", "guilt", "guilty", "guinea", "guitar", "gum", "gumshoe", "gun", "gutter", "guy", "gym", "gymnast", "gyro", "habit",
+ "habitat", "hacksaw", "hail", "hair", "haircut", "hake", "half", "halibut", "hall", "hallway", "halt", "ham", "hammer", "hammock", "hamster", "hand", "handful", "handgun", "handle", "handsaw", "hanger", "harald", "harbor", "harbour",
+ "hardhat", "hare", "harm", "harmony", "harp", "harvest", "hash", "hashtag", "hassock", "haste", "hat", "hatbox", "hatchet", "hate", "hatred", "haunt", "haven", "havoc", "hawk", "hay", "haze", "hazel", "head", "health", "hearing", "hearsay",
+ "heart", "hearth", "heat", "heater", "heating", "heaven", "heavy", "hectare", "hedge", "heel", "heifer", "height", "heir", "helium", "hell", "hellcat", "hello", "helmet", "helo", "help", "hemp", "hen", "herb", "herbs", "hermit", "hero",
+ "heroine", "heron", "herring", "hexagon", "heyday", "hiccups", "hide", "high", "highway", "hike", "hiking", "hill", "hint", "hip", "hire", "hiring", "history", "hit", "hive", "hobbit", "hobby", "hockey", "hoe", "hog", "hold", "holder",
+ "hole", "holiday", "home", "homonym", "honesty", "honey", "honor", "honoree", "hood", "hoof", "hook", "hop", "hope", "hops", "horde", "horizon", "hormone", "horn", "hornet", "horror", "horse", "horst", "hose", "hosiery", "hospice",
+ "host", "hostel", "hostess", "hotdog", "hotel", "hound", "hour", "house", "housing", "hovel", "howard", "hub", "hubcap", "hubris", "hug", "hugger", "hull", "human", "hummus", "humor", "humour", "hundred", "hunger", "hunt", "hunter",
+ "hunting", "hurdle", "hurdler", "hurry", "hurt", "husband", "hut", "hutch", "hydrant", "hyena", "hype", "ice", "iceberg", "icicle", "icing", "icon", "icy", "id", "idea", "ideal", "idiom", "idiot", "igloo", "ikebana", "illegal", "illness",
+ "image", "impact", "impala", "import", "impress", "impulse", "in-joke", "in-laws", "inbox", "incense", "inch", "income", "index", "infancy", "infant", "infix", "influx", "info", "ingrate", "initial", "injury", "ink", "inlay", "inn", "input",
+ "inquiry", "insect", "insert", "inside", "insight", "instant", "integer", "intent", "invader", "inverse", "invite", "invoice", "iris", "iron", "irony", "island", "issue", "item", "ivory", "jack", "jackal", "jacket", "jade", "jaguar", "jail",
+ "jam", "jar", "jasmine", "jaw", "jazz", "jeans", "jeep", "jelly", "jerk", "jet", "jewel", "jewelry", "jicama", "jiffy", "job", "jockey", "joey", "jogging", "joint", "joke", "jot", "journal", "journey", "joy", "judge", "judo", "jug", "juice",
+ "jumbo", "jump", "jumper", "jungle", "junior", "junk", "junker", "junket", "jury", "justice", "jute", "kale", "karate", "kayak", "kazoo", "kebab", "keep", "keeper", "kendo", "kennel", "ketch", "ketchup", "kettle", "key", "kick", "kid", "kidney",
+ "kill", "killer", "killing", "kilt", "kimono", "kinase", "kind", "king", "kingdom", "kiosk", "kiss", "kit", "kitchen", "kite", "kitsch", "kitten", "kitty", "kiwi", "knee", "knife", "knight", "knock", "knot", "knuckle", "koala", "kumquat", "lab",
+ "label", "labor", "laborer", "labour", "lace", "lack", "lad", "ladder", "ladle", "lady", "ladybug", "lag", "lake", "lamb", "lambkin", "lament", "lamp", "lanai", "land", "landing", "lane", "lantern", "lap", "lapdog", "laptop", "larch", "lard",
+ "larder", "lark", "larva", "lasagna", "lashes", "last", "latency", "latex", "lathe", "latte", "latter", "laugh", "laundry", "lava", "law", "lawn", "lawsuit", "lawyer", "lay", "layer", "layout", "lead", "leader", "leading", "leaf", "league",
+ "leaker", "leap", "leash", "leather", "leave", "leaver", "lecture", "leek", "leeway", "left", "leg", "legacy", "legal", "legend", "legging", "legume", "leisure", "lemon", "lemur", "lender", "lending", "length", "lens", "lentil", "leopard",
+ "leprosy", "lesbian", "lesson", "letter", "lettuce", "level", "lever", "leveret", "liar", "liberty", "libido", "library", "licence", "license", "lid", "lie", "lieu", "life", "lift", "ligand", "light", "ligula", "lilac", "lily", "limb", "lime",
+ "limit", "limo", "line", "linen", "liner", "lining", "link", "linkage", "linseed", "lion", "lip", "lipid", "liquid", "liquor", "list", "listing", "litmus", "litter", "liver", "living", "lizard", "llama", "load", "loading", "loaf", "loafer", "loan",
+ "lobby", "lobster", "local", "lock", "locker", "locket", "locust", "lode", "loft", "log", "loggia", "logic", "login", "logo", "look", "lookout", "loop", "loquat", "lord", "loss", "lot", "lotion", "lottery", "lounge", "louse", "lout", "love", "lover",
+ "lox", "loyalty", "luck", "luggage", "lumber", "lunch", "lung", "lunge", "lust", "lute", "luxury", "lychee", "lycra", "lye", "lynx", "lyocell", "lyre", "lyrics", "lysine", "mRNA", "macaw", "machine", "macrame", "macro", "madam", "maestro", "maggot",
+ "magic", "magnet", "maid", "maiden", "mail", "mailbox", "mailer", "mailing", "mailman", "main", "maize", "major", "maker", "makeup", "making", "male", "malice", "mall", "mallard", "mallet", "mama", "mambo", "mammoth", "man", "manacle", "manager",
+ "manatee", "mandate", "mangle", "mango", "manhunt", "maniac", "mankind", "manner", "manor", "mansard", "mansion", "mantel", "mantle", "mantua", "many", "map", "maple", "mapping", "maracas", "marble", "march", "mare", "margin", "marimba", "marines",
+ "mark", "marker", "market", "markup", "marsh", "marten", "marxism", "mascara", "mask", "masonry", "mass", "massage", "mast", "master", "mastoid", "mat", "match", "mate", "math", "matrix", "matter", "mattock", "max", "maximum", "maybe", "mayor",
+ "meadow", "meal", "mean", "meander", "meaning", "means", "measles", "measure", "meat", "mecca", "med", "medal", "media", "median", "medium", "meet", "meeting", "melody", "melon", "member", "meme", "memo", "memory", "men", "menorah", "mention", "mentor",
+ "menu", "mercury", "merit", "mess", "message", "messy", "metal", "meteor", "meter", "methane", "method", "metric", "metro", "midden", "middle", "midline", "midwife", "might", "migrant", "mile", "mileage", "milk", "mill", "millet", "million", "mime", "mimosa",
+ "min", "mind", "mine", "mineral", "mini", "minibus", "minimum", "mining", "minion", "mink", "minnow", "minor", "mint", "minute", "miracle", "mirror", "misfit", "miss", "missile", "mission", "mist", "mistake", "mister", "miter", "mitten", "mix", "mixer",
+ "mixture", "moai", "moat", "mob", "mobile", "mobster", "mocha", "mochi", "mode", "model", "modem", "molar", "molding", "mole", "mom", "moment", "money", "monger", "monitor", "monk", "monkey", "monocle", "monsoon", "monster", "month", "mood", "moody", "moon",
+ "moose", "mop", "morale", "morbid", "morning", "moron", "morsel", "mortal", "mortise", "mosque", "most", "motel", "moth", "mother", "motion", "motive", "motor", "mound", "mouse", "mouser", "mousse", "mouth", "mouton", "mover", "movie", "mower", "mud", "muffin",
+ "mug", "mukluk", "mule", "murder", "muscat", "muscle", "museum", "music", "muskrat", "mussel", "mustard", "mutt", "mutton", "mystery", "myth", "nail", "name", "naming", "napkin", "nasal", "nation", "native", "nature", "neck", "necktie", "nectar", "need",
+ "needle", "neglect", "neon", "neonate", "nephew", "nerve", "nest", "net", "netball", "netbook", "netsuke", "network", "neuron", "news", "nexus", "nibble", "nicety", "niche", "nick", "nickel", "niece", "night", "ninja", "nit", "nobody", "nod", "node", "noir",
+ "noise", "noodle", "noodles", "noon", "norm", "normal", "north", "nose", "note", "notepad", "nothing", "notice", "notion", "nougat", "noun", "novel", "nudge", "nuke", "number", "numeric", "nun", "nurse", "nursery", "nursing", "nurture", "nut", "nutmeg",
+ "nylon", "nymph", "oak", "oar", "oasis", "oat", "oatmeal", "oats", "obesity", "obi", "object", "oboe", "ocean", "ocelot", "octagon", "octave", "octavo", "octet", "octopus", "odyssey", "oeuvre", "offence", "offense", "offer", "office", "officer", "offset",
+ "oil", "okra", "oldie", "oleo", "olive", "omega", "omelet", "onion", "online", "onset", "opening", "opera", "opinion", "opium", "opossum", "optimal", "option", "orange", "orator", "orchard", "orchid", "order", "ore", "oregano", "organ", "orient", "origin",
+ "osmosis", "osprey", "ostrich", "other", "otter", "ottoman", "ounce", "outback", "outcome", "outfit", "outlaw", "outlay", "outlet", "outline", "outlook", "output", "outrage", "outrun", "outset", "outside", "oval", "ovary", "oven", "owl", "owner", "ox",
+ "oxford", "oxygen", "oyster", "ozone", "pace", "pack", "package", "packet", "pad", "paddle", "paddock", "pagan", "page", "pagoda", "pail", "pain", "paint", "painter", "pair", "pajamas", "palace", "palate", "palm", "pan", "pancake", "panda", "panel", "panic",
+ "pannier", "panpipe", "pansy", "panther", "panties", "pantry", "pants", "panty", "papa", "papaya", "paper", "parable", "parade", "parcel", "pard", "pardon", "parent", "park", "parka", "parking", "parole", "parrot", "parser", "parsley", "parsnip", "part",
+ "partner", "party", "pass", "passage", "passing", "passion", "passive", "past", "pasta", "paste", "pastor", "pastry", "pasture", "pat", "patch", "pate", "patent", "path", "pathway", "patient", "patina", "patio", "patriot", "patrol", "patron", "pattern",
+ "patty", "pause", "paw", "pay", "payee", "payment", "payoff", "pea", "peace", "peach", "peacoat", "peacock", "peak", "peanut", "pear", "pearl", "peasant", "pecan", "pecker", "pedal", "peek", "peen", "peer", "pelican", "pelt", "pen", "penalty", "pence",
+ "pencil", "pendant", "penguin", "penis", "pennant", "penny", "pension", "peony", "people", "pepper", "percent", "perch", "perfume", "period", "permit", "perp", "person", "pest", "pet", "petal", "pew", "phase", "phone", "photo", "phrase", "physics",
+ "pianist", "piano", "piccolo", "pick", "pickax", "pickaxe", "picket", "pickle", "pickup", "picnic", "picture", "pie", "piece", "pier", "piety", "pig", "pigeon", "piglet", "pigpen", "pigsty", "pike", "pilaf", "pile", "pilgrim", "pill", "pillar", "pillbox",
+ "pillow", "pilot", "pimp", "pimple", "pin", "pine", "ping", "pink", "pinkie", "pinot", "pint", "pinto", "pinworm", "pioneer", "pipe", "piracy", "pirate", "piss", "pistol", "pit", "pita", "pitch", "pitcher", "pith", "pizza", "place", "placebo", "placode",
+ "plain", "plan", "plane", "planet", "plant", "planter", "planula", "plaster", "plastic", "plate", "platter", "play", "player", "plea", "pleat", "pledge", "plenty", "plier", "pliers", "plight", "plot", "plough", "plover", "plow", "plowman", "plug", "plugin",
+ "plum", "plumber", "plume", "plunger", "plywood", "pocket", "pod", "podcast", "poem", "poet", "poetry", "point", "poison", "poker", "pole", "polenta", "police", "policy", "polish", "poll", "polo", "polyp", "pomelo", "pompom", "poncho", "pond", "pony", "pool",
+ "poor", "pop", "popcorn", "poppy", "porch", "pork", "port", "porter", "portion", "post", "postage", "postbox", "poster", "postfix", "pot", "potato", "pottery", "potty", "pouch", "poultry", "pound", "poverty", "powder", "power", "prairie", "praise", "pray",
+ "prayer", "preface", "prefix", "prelude", "premier", "premise", "premium", "present", "press", "presume", "pretzel", "prey", "price", "pricing", "pride", "priest", "primary", "primate", "prince", "print", "printer", "prior", "prison", "privacy", "private",
+ "prize", "probe", "problem", "process", "proctor", "produce", "product", "profile", "profit", "program", "project", "promise", "prompt", "pronoun", "proof", "propane", "prophet", "prose", "protein", "protest", "prow", "prune", "pruner", "pub", "public",
+ "pudding", "puddle", "puffin", "pug", "puggle", "pulley", "pulse", "puma", "pump", "pumpkin", "pun", "punch", "pup", "pupa", "pupil", "puppet", "puppy", "puritan", "purity", "purple", "purpose", "purr", "purse", "pursuit", "push", "pusher", "put", "puzzle",
+ "pyramid", "quail", "quality", "quart", "quarter", "quartet", "quartz", "queen", "query", "quest", "quiche", "quiet", "quill", "quilt", "quince", "quinoa", "quit", "quiver", "quota", "quote", "rabbi", "rabbit", "raccoon", "race", "racer", "racing", "racism",
+ "racist", "rack", "radar", "radio", "radish", "raffle", "raft", "rag", "rage", "raid", "rail", "railing", "railway", "raiment", "rain", "rainbow", "rainy", "raise", "raisin", "rake", "rally", "ram", "rambler", "ramen", "ramie", "ranch", "rancher", "range",
+ "ranger", "rank", "rap", "rape", "rat", "rate", "rating", "ratio", "rations", "raven", "ravioli", "rawhide", "ray", "rayon", "razor", "reach", "read", "reader", "reading", "real", "reality", "realm", "reamer", "rear", "reason", "rebel", "reboot", "recall",
+ "receipt", "recess", "recipe", "record", "recruit", "red", "redhead", "reef", "reform", "refuge", "refund", "refusal", "refuse", "regard", "regime", "region", "regret", "reject", "relay", "release", "relief", "relish", "remains", "remark", "remnant",
+ "remote", "removal", "rent", "repair", "repeat", "replica", "reply", "report", "request", "resale", "rescue", "reserve", "reset", "residue", "resist", "resolve", "resort", "respect", "respite", "rest", "result", "resume", "retina", "retreat", "return",
+ "reunion", "reveal", "revenge", "revenue", "reverse", "review", "revival", "reward", "rhubarb", "rhyme", "rhythm", "rib", "ribbon", "rice", "riddle", "ride", "rider", "ridge", "riding", "rifle", "right", "rim", "ring", "riot", "rip", "ripple", "rise", "riser",
+ "risk", "rite", "ritual", "river", "rivulet", "road", "roadway", "roar", "roast", "robe", "robin", "robot", "rock", "rocker", "rocket", "rod", "role", "roll", "roller", "romaine", "romance", "roof", "room", "rooster", "root", "rope", "rose", "roster", "rostrum",
+ "round", "route", "router", "routine", "row", "rowboat", "rowing", "rubber", "rubbish", "rubric", "ruby", "ruckus", "ruffle", "rug", "rugby", "ruin", "rule", "ruler", "ruling", "rum", "rumor", "run", "runaway", "runner", "running", "runway", "rush", "rust",
+ "rye", "sabre", "sac", "sack", "saddle", "sadness", "safari", "safe", "safety", "saffron", "sage", "sail", "sailing", "sailor", "saint", "sake", "salad", "salami", "salary", "sale", "salmon", "salon", "saloon", "salsa", "salt", "salute", "samovar", "sampan",
+ "sample", "samurai", "sand", "sandal", "sandbar", "sanity", "sardine", "sari", "sarong", "sash", "satin", "satire", "sauce", "saucer", "sausage", "savage", "saving", "savings", "savior", "saviour", "savory", "saw", "scale", "scalp", "scam", "scanner", "scarf",
+ "scene", "scenery", "scent", "schema", "scheme", "scholar", "school", "science", "scooter", "scope", "score", "scorn", "scotch", "scout", "scow", "scrap", "scraper", "scratch", "screen", "screw", "scrim", "scrip", "script", "sea", "seabass", "seafood",
+ "seagull", "seal", "search", "seaside", "season", "seat", "seaweed", "second", "secrecy", "secret", "section", "sector", "seed", "seeder", "seeker", "seep", "segment", "seizure", "self", "seller", "selling", "seminar", "senate", "senator", "sender", "senior",
+ "sense", "sensor", "sepal", "sequel", "serial", "series", "sermon", "serum", "serval", "servant", "server", "service", "sesame", "session", "set", "setback", "setting", "settler", "sewer", "sex", "shack", "shackle", "shade", "shadow", "shaker", "shallot",
+ "shame", "shampoo", "shanty", "shape", "share", "shark", "shaw", "shawl", "shear", "sheath", "shed", "sheep", "sheet", "shelf", "shell", "shelter", "sherbet", "sherry", "shield", "shift", "shin", "shine", "shingle", "ship", "shipper", "shirt", "shjt", "shoat",
+ "shock", "shoe", "shoes", "shofar", "shoot", "shop", "shopper", "shore", "short", "shorts", "shot", "shout", "shovel", "show", "shower", "shred", "shrimp", "shrine", "sibling", "sick", "side", "sidecar", "siding", "siege", "sigh", "sight", "sign", "signal",
+ "signet", "signify", "signup", "silence", "silica", "silicon", "silk", "sill", "silly", "silo", "silver", "simple", "sin", "singer", "singing", "sink", "sip", "sir", "sister", "sitar", "site", "size", "skate", "skating", "skean", "ski", "skiing", "skill",
+ "skin", "skirt", "skull", "skunk", "sky", "skyline", "skywalk", "slang", "slash", "slate", "slave", "slavery", "slaw", "sled", "sledge", "sleep", "sleet", "sleuth", "slice", "slide", "slider", "slime", "slip", "slipper", "slope", "slot", "sloth", "slump",
+ "smell", "smile", "smith", "smock", "smog", "smoke", "smoking", "smolt", "snack", "snail", "snake", "snap", "snarl", "sneaker", "sneeze", "sniffle", "snob", "snorer", "snow", "snowman", "snuck", "snug", "snuggle", "soap", "soccer", "society", "sock", "socks",
+ "soda", "sofa", "soil", "soldier", "sole", "someone", "son", "sonar", "sonata", "song", "sonnet", "soot", "soprano", "sorbet", "sorghum", "sorrel", "sorrow", "sort", "soul", "sound", "soup", "source", "south", "sow", "soy", "soybean", "space", "spacing",
+ "spade", "span", "spandex", "spank", "spark", "sparrow", "spasm", "spat", "spatula", "spawn", "speaker", "spear", "spec", "special", "species", "speech", "speed", "spell", "spelt", "sphere", "sphynx", "spice", "spider", "spike", "spill", "spinach", "spine",
+ "spiral", "spirit", "spit", "spite", "spleen", "split", "sponge", "sponsor", "spool", "spoon", "spork", "sport", "spot", "spouse", "sprag", "sprat", "spray", "spread", "spree", "spring", "sprout", "spruce", "spud", "spume", "spur", "spy", "square", "squash",
+ "squid", "stab", "stable", "stack", "stadium", "staff", "stag", "stage", "stain", "stair", "stake", "stalk", "stall", "stamen", "stamina", "stamp", "stance", "stand", "star", "start", "starter", "state", "statin", "station", "statue", "status", "statute",
+ "stay", "steak", "stealth", "steam", "steel", "steeple", "stem", "stench", "stencil", "step", "stepson", "stereo", "stew", "steward", "stick", "sticker", "still", "sting", "stinger", "stitch", "stock", "stole", "stomach", "stone", "stool", "stop", "storage",
+ "store", "storey", "storm", "story", "stot", "stove", "strait", "strand", "strap", "straw", "stream", "street", "stress", "stretch", "strife", "strike", "string", "strip", "stripe", "strobe", "stroke", "strudel", "stucco", "stud", "student", "studio", "study",
+ "stuff", "stump", "sty", "style", "styling", "stylus", "sub", "subject", "subset", "subsidy", "suburb", "subway", "success", "suck", "sucker", "suede", "suet", "sugar", "suicide", "suit", "suite", "sulfur", "sultan", "sum", "summary", "summer", "summit", "sun",
+ "sunbeam", "sundae", "sunday", "sundial", "sunlamp", "sunrise", "sunroom", "sunset", "supper", "supply", "support", "supreme", "surface", "surge", "surgeon", "surgery", "surname", "surplus", "survey", "sushi", "suspect", "swallow", "swamp", "swan", "swath",
+ "sweat", "sweater", "sweets", "swell", "swim", "swine", "swing", "switch", "swivel", "sword", "symbol", "symptom", "synergy", "synod", "synonym", "syrup", "system", "t-shirt", "tab", "tabby", "table", "tablet", "tackle", "taco", "tactics", "tactile",
+ "tadpole", "tag", "tail", "tailbud", "tailor", "tale", "talent", "talk", "talking", "tamale", "tambour", "tan", "tandem", "tank", "tanker", "tankful", "tap", "tape", "tapioca", "target", "taro", "tart", "task", "tassel", "taste", "tatami", "tattler", "tattoo",
+ "tavern", "tax", "taxi", "taxicab", "tea", "teacher", "team", "teapot", "tear", "tech", "teen", "teepee", "tell", "teller", "temp", "temper", "temple", "tempo", "tenant", "tender", "tenet", "tennis", "tenor", "tension", "tensor", "tent", "tenth", "tepee",
+ "term", "termite", "terrace", "terror", "test", "testing", "text", "textual", "texture", "thanks", "thaw", "theater", "theft", "theism", "theme", "theory", "therapy", "thesis", "thief", "thigh", "thing", "thirst", "thistle", "thong", "thongs", "thorn", "thought",
+ "thread", "threat", "thrift", "thrill", "throat", "throne", "thrush", "thrust", "thug", "thumb", "thump", "thunder", "thyme", "tiara", "tic", "tick", "ticket", "tide", "tie", "tiger", "tights", "tile", "till", "tilt", "timbale", "timber", "time", "timeout",
+ "timer", "timing", "timpani", "tin", "tinkle", "tintype", "tip", "tire", "tissue", "title", "toad", "toast", "toaster", "tobacco", "today", "toe", "toenail", "toffee", "tofu", "tog", "toga", "toilet", "toll", "tom-tom", "tomato", "tomb", "ton", "tone", "tongue",
+ "tonic", "tonight", "tool", "toot", "tooth", "top", "top-hat", "topic", "topsail", "toque", "tornado", "torso", "torte", "tosser", "total", "tote", "touch", "tour", "tourism", "tourist", "towel", "tower", "town", "toy", "trace", "track", "tract", "tractor",
+ "trade", "trader", "trading", "traffic", "tragedy", "trail", "trailer", "train", "trainer", "trait", "tram", "tramp", "trance", "transit", "transom", "trap", "trash", "travel", "tray", "treat", "treaty", "tree", "trek", "trellis", "tremor", "trench", "trend",
+ "triad", "trial", "tribe", "trick", "trigger", "trim", "trinket", "trip", "tripod", "tritone", "triumph", "trolley", "troop", "trooper", "trophy", "trouble", "trout", "trove", "trowel", "truck", "trumpet", "trunk", "trust", "trustee", "truth", "try",
+ "tsunami", "tub", "tuba", "tube", "tuber", "tug", "tugboat", "tuition", "tulip", "tumbler", "tummy", "tuna", "tune", "tune-up", "tunic", "tunnel", "turban", "turf", "turkey", "turn", "turning", "turnip", "turret", "turtle", "tusk", "tussle", "tutu",
+ "tuxedo", "tweet", "twig", "twine", "twins", "twist", "twister", "twitter", "type", "typhoon", "ukulele", "uncle", "unibody", "uniform", "union", "unique", "unit", "unity", "update", "upgrade", "uplift", "upper", "upward", "urge", "urgency", "urn", "usage",
+ "use", "user", "usher", "usual", "utensil", "utility", "vaccine", "vacuum", "vagrant", "valance", "valley", "value", "vampire", "van", "vanadyl", "vane", "vanilla", "vanity", "variant", "variety", "vase", "vault", "veal", "vector", "vehicle", "veil", "vein",
+ "veldt", "vellum", "velvet", "vendor", "veneer", "venison", "venom", "venti", "venture", "venue", "veranda", "verb", "verdict", "verse", "version", "vertigo", "verve", "vessel", "vest", "vet", "veteran", "veto", "vibe", "vice", "victim", "victory", "video",
+ "view", "viewer", "villa", "village", "vine", "vinegar", "vintage", "vintner", "vinyl", "viola", "violet", "violin", "virtue", "virus", "visa", "viscose", "vise", "vision", "visit", "visitor", "visor", "vista", "visual", "vitamin", "vitro", "vivo", "vixen",
+ "vodka", "vogue", "voice", "void", "vol", "volcano", "volume", "vomit", "vote", "voter", "voting", "voyage", "vulture", "wad", "wafer", "waffle", "wage", "wagon", "waist", "wait", "waiter", "waiting", "waiver", "wake", "walk", "walker", "walking", "walkway",
+ "wall", "wallaby", "wallet", "walnut", "walrus", "wampum", "wannabe", "want", "war", "warden", "warfare", "warlock", "warlord", "warm-up", "warming", "warmth", "warning", "warrant", "warren", "warrior", "wasabi", "wash", "washer", "washtub", "wasp", "waste",
+ "wasting", "watch", "watcher", "water", "wave", "wax", "way", "wealth", "weapon", "wear", "weasel", "weather", "web", "webinar", "webmail", "webpage", "website", "wedding", "wedge", "weed", "weeder", "week", "weekend", "weight", "weird", "welcome", "welfare",
+ "well", "west", "western", "wet-bar", "wetland", "wetsuit", "whack", "whale", "wharf", "wheat", "wheel", "whelp", "whey", "whip", "whisker", "whiskey", "whisper", "whistle", "white", "whole", "whorl", "wick", "widget", "widow", "width", "wife", "wifi", "wild",
+ "will", "willow", "win", "wind", "windage", "window", "wine", "winery", "wing", "wingman", "wingtip", "wink", "winner", "winter", "wire", "wiretap", "wiring", "wisdom", "wiseguy", "wish", "wit", "witch", "witness", "wok", "wolf", "woman", "wombat", "wonder",
+ "wont", "wood", "wool", "woolens", "word", "wording", "work", "worker", "working", "workout", "world", "worm", "worry", "worship", "worth", "wound", "wrap", "wrapper", "wreck", "wrecker", "wren", "wrench", "wrinkle", "wrist", "writer", "writing", "wrong",
+ "yacht", "yahoo", "yak", "yam", "yang", "yard", "yarn", "yawl", "year", "yeast", "yellow", "yew", "yin", "yoga", "yogurt", "yoke", "yolk", "young", "youth", "yoyo", "yurt", "zampone", "zebra", "zen", "zephyr", "zero", "zinc", "zipper", "zither", "zombie", "zone",
+ "zoo", "zoology",
+ ];
- //let random1 = Math.floor(Math.random() * (adjectives.length + 1));
- let adjective = adjectives[rand(0, adjectives.length - 1)];
- let list2Limit = 16 - adjective.length;
- let list2 = nouns.filter(function (element) {
- return element.length < list2Limit;
- });
+ const NameGen = function () {
+ //let random1 = Math.floor(Math.random() * (adjectives.length + 1));
+ let adjective = adjectives[rand(0, adjectives.length - 1)];
+ let list2Limit = 16 - adjective.length;
+ let list2 = nouns.filter(function (element) {
+ return element.length < list2Limit;
+ });
- let noun = list2[rand(0, list2.length - 1)];
- let namechosen = adjective.toLowerCase() + noun.toLowerCase();
+ let noun = list2[rand(0, list2.length - 1)];
+ let namechosen = adjective + noun;
- return namechosen;
-};
+ return namechosen.toLowerCase();
+ };
+
+ module.exports = NameGen;
+})(module);
diff --git a/libs/SoloPlay/Tools/OOGOverrides.js b/libs/SoloPlay/Tools/OOGOverrides.js
index 8adce412..ce1bae0d 100644
--- a/libs/SoloPlay/Tools/OOGOverrides.js
+++ b/libs/SoloPlay/Tools/OOGOverrides.js
@@ -4,953 +4,1545 @@
* @desc OOG.js fixes to improve functionality
*
*/
+
+/**
+ * @typedef {import("../../modules/Control")} Controls
+ */
includeIfNotIncluded("OOG.js");
(function (global, original) {
- global.login = function (...args) {
- console.trace();
- return original.apply(this, args);
- };
+ global.login = function (...args) {
+ console.trace();
+ return original.apply(this, args);
+ };
})([].filter.constructor("return this")(), login);
-ControlAction.scrollDown = function () {
- me.blockMouse = true;
- for (let i = 0; i < 4; i++) {
- sendKey(sdk.keys.code.DownArrow);
- }
- me.blockMouse = false;
-};
-
-ControlAction.makeCharacter = function (info) {
- me.blockMouse = true;
- !info.charClass && (info.charClass = "barbarian");
-
- let clickCoords = [];
- let soloStats = CharData.getStats();
-
- soloStats.me.startTime !== 0 && Tracker.reset();
- if (soloStats.me.currentBuild !== "Start" || soloStats.me.level > 1) {
- let finalBuild = soloStats.me.finalBuild;
- Object.assign(soloStats, CharData.default);
- soloStats.me.finalBuild = finalBuild;
- CharData.updateData("me", soloStats);
- }
-
- D2Bot.updateStatus("Making Character: " + info.charName);
-
- // cycle until in lobby
- while (getLocation() !== sdk.game.locations.Lobby) {
- switch (getLocation()) {
- case sdk.game.locations.CharSelect:
- case sdk.game.locations.CharSelectConnecting:
- case sdk.game.locations.CharSelectNoChars:
- let control = Controls.CharSelectCreate.control;
-
- // Create Character greyed out
- if (control && control.disabled === sdk.game.controls.Disabled) {
- me.blockMouse = false;
-
- return false;
- }
-
- Controls.CharSelectCreate.click();
-
- break;
- case sdk.game.locations.LobbyPleaseWait:
- D2Bot.restart(); // single player error on finding character
-
- break;
- case sdk.game.locations.CharacterCreate:
- clickCoords = (() => {
- switch (info.charClass) {
- case "barbarian":
- return [400, 280];
- case "amazon":
- return [100, 280];
- case "necromancer":
- return [300, 290];
- case "sorceress":
- return [620, 270];
- case "assassin":
- return [200, 280];
- case "druid":
- return [700, 280];
- case "paladin":
- default:
- return [521, 260];
- }
- })();
-
- getControl().click(clickCoords[0], clickCoords[1]);
- delay(500);
-
- break;
- case sdk.game.locations.NewCharSelected:
- // hardcore char warning
- if (Controls.CharCreateHCWarningOk.control) {
- Controls.CharCreateHCWarningOk.click();
- } else {
- Controls.CharCreateCharName.setText(info.charName);
-
- if (!info.expansion) {
- // @credit isid0re
- if (["druid", "assassin"].includes(info.charClass)) {
- D2Bot.printToConsole("Error in profile name. Expansion characters cannot be made in classic", sdk.colors.D2Bot.Red);
- D2Bot.stop();
-
- return false;
- }
-
- 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)
- ControlAction.timeoutDelay("Character Name exists: " + info.charName + ". Making new Name.", 5e3);
- info.charName = NameGen();
- delay(500);
- Controls.OkCentered.click();
- D2Bot.updateStatus("Making Character: " + info.charName);
-
- break;
- default:
- break;
- }
-
- // Singleplayer loop break fix.
- if (me.ingame) {
- break;
- }
-
- delay(500);
- }
-
- me.blockMouse = false;
- D2Bot.setProfile(null, null, info.charName, "Normal");
- let gamename = Starter.profileInfo.charName.substring(0, 7) + "-" + Starter.randomString(3, false) + "-";
- DataFile.updateStats("gameName", gamename);
-
- return true;
-};
-
-ControlAction.findCharacter = function (info) {
- let count = 0;
- let singlePlayer = ![sdk.game.gametype.OpenBattlenet, sdk.game.gametype.BattleNet].includes(Profile().type);
- // offline doesn't have a character limit cap
- let cap = singlePlayer ? 999 : 24;
- let tick = getTickCount();
- let firstCheck;
-
- while (getLocation() !== sdk.game.locations.CharSelect) {
- if (getTickCount() - tick >= 5000) {
- break;
- }
-
- delay(25);
- }
-
- if (getLocation() === sdk.game.locations.CharSelectConnecting) {
- if (!Starter.charSelectConnecting()) {
- D2Bot.printToConsole("Stuck at connecting screen");
- D2Bot.restart();
- }
- }
-
- // 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 (text[1].toLowerCase() === info.charName.toLowerCase()) {
- return true;
- }
- }
- } 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 (firstCheck[1].toLowerCase() === nameCheck[1].toLowerCase()) {
- return false;
- }
- }
- }
- } else {
- // no further check necessary
- break;
- }
- }
-
- return false;
-};
-
-ControlAction.loginCharacter = function (info, startFromTop = true) {
- me.blockMouse = true;
-
- let count = 0;
-
- // start from beginning of the char list
- startFromTop && sendKey(sdk.keys.code.Home);
-
- MainLoop:
- // cycle until in lobby or in game
- while (getLocation() !== sdk.game.locations.Lobby) {
- switch (getLocation()) {
- case sdk.game.locations.SplashScreen:
- case sdk.game.locations.MainMenu:
- case sdk.game.locations.Login:
- if (getLocation() === sdk.game.locations.MainMenu
- && Profile().type === sdk.game.profiletype.SinglePlayer
- && Controls.SinglePlayer.click()) {
- Starter.checkDifficulty();
- break;
- } else if (Starter.BNET) {
- Starter.LocationEvents.login();
- }
-
- break;
- 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 {
- Starter.LocationEvents.selectDifficultySP();
- Starter.locationTimeout(Time.seconds(3), sdk.game.locations.SelectDifficultySP);
- } 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) {
- Controls.CharSelectChar6.click() && this.scrollDown();
- } 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;
-};
-
-// need open bnet check
-ControlAction.makeAccount = function (info) {
- me.blockMouse = true;
-
- let tick;
- let realms = { "uswest": 0, "useast": 1, "asia": 2, "europe": 3 };
- D2Bot.updateStatus("Making Account: " + info.account);
-
- // cycle until in empty char screen
- while (getLocation() !== sdk.game.locations.CharSelectNoChars) {
- switch (getLocation()) {
- case sdk.game.locations.MainMenu:
- ControlAction.clickRealm(realms[info.realm]);
- Controls.BattleNet.click();
-
- break;
- case sdk.game.locations.Login:
- Controls.CreateNewAccount.click();
-
- break;
- case sdk.game.locations.LoginError:
- case sdk.game.locations.LoginUnableToConnect:
- return false;
- case sdk.game.locations.SplashScreen:
- Controls.SplashScreen.click();
-
- break;
- case sdk.game.locations.MainMenuConnecting:
- tick = getTickCount();
-
- while (getLocation() === sdk.game.locations.MainMenuConnecting) {
- if (getTickCount() - tick > 10000) {
- Controls.LoginCancelWait.click();
- }
-
- delay(500);
- }
-
- break;
- case sdk.game.locations.CharacterCreate:
- Controls.CharSelectExit.click();
-
- break;
- case sdk.game.locations.OkCenteredErrorPopUp:
- info.account = "";
- info.password = "";
- D2Bot.setProfile(info.account, info.password);
- D2Bot.restart(true);
-
- 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:
- if (Developer.setEmail.enabled
- && (!Developer.setEmail.profiles.length || Developer.setEmail.profiles.includes(me.profile))
- && (!Developer.setEmail.realms.length || Developer.setEmail.realms.includes(Profile().gateway.toLowerCase()))) {
- ControlAction.setEmail();
- } else {
- Controls.EmailDontRegisterContinue.control ? Controls.EmailDontRegisterContinue.click() : Controls.EmailDontRegister.click();
- }
-
- break;
- default:
- break;
- }
-
- delay(100);
- }
-
- me.blockMouse = false;
-
- return true;
-};
-
-ControlAction.deleteAndRemakeChar = function (info) {
- me.blockMouse = true;
-
- ControlAction.findCharacter(info);
-
- MainLoop:
- // Cycle until in lobby
- 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" && text[1].toLowerCase() === info.charName.toLowerCase()) {
- control.click();
- Controls.CharSelectDelete.click();
- delay(500);
- Controls.CharDeleteYes.click();
-
- break MainLoop;
- }
- } while (control.getNext());
- }
-
- break;
- case sdk.game.locations.CharSelectNoChars:
- break MainLoop;
-
- case sdk.game.locations.Disconnected:
- case sdk.game.locations.OkCenteredErrorPopUp:
- me.blockMouse = false;
-
- return false;
- default:
- break;
- }
-
- delay(100);
- }
-
- me.blockMouse = false;
-
- // Delete old files - leaving csv file's for now as I don't think they interfere with the overlay
- CharData.delete(true);
- DataFile.create();
- CharData.updateData("me", "finalBuild", Starter.profileInfo.tag);
- Developer.logPerformance && Tracker.initialize();
- D2Bot.printToConsole("Deleted: " + info.charName + ". Now remaking...", sdk.colors.D2Bot.Gold);
- ControlAction.makeCharacter(Starter.profileInfo);
-
- return true;
-};
-
-ControlAction.saveInfo = function (info) {
- // Data-file already exists
- if (FileTools.exists("logs/Kolbot-SoloPlay/" + info.realm + "/" + info.charClass + "-" + info.charClass + "-" + info.charName + ".json")) {
- return;
- }
-
- let folder;
-
- if (!FileTools.exists("logs/Kolbot-SoloPlay")) {
- folder = dopen("logs");
- folder.create("Kolbot-SoloPlay");
- }
-
- if (!FileTools.exists("logs/Kolbot-SoloPlay/" + info.realm)) {
- folder = dopen("logs/Kolbot-SoloPlay");
- folder.create(info.realm);
- }
-
- if (!FileTools.exists("logs/Kolbot-SoloPlay/" + info.realm + "/" + info.charClass + "-" + info.charName + ".json")) {
- FileTools.writeText("logs/Kolbot-SoloPlay/" + info.realm + "/" + info.charClass + "-" + info.charName + ".json", JSON.stringify(info));
- }
-};
-
-ControlAction.loginAccount = function (info) {
- me.blockMouse = true;
-
- let locTick;
- let tick = getTickCount();
- let realms = { "uswest": 0, "useast": 1, "asia": 2, "europe": 3 };
-
- 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
- case sdk.game.locations.Lobby:
- case sdk.game.locations.LobbyChat:
- // somehow we are in the lobby?
- Control.LobbyQuit.click();
-
- break;
- default:
- console.log(getLocation());
-
- me.blockMouse = false;
-
- return false;
- }
-
- if (getTickCount() - tick >= 20000) {
- return false;
- }
-
- delay(100);
- }
-
- delay(1000);
-
- me.blockMouse = false;
-
- return getLocation() === sdk.game.locations.CharSelect || getLocation() === sdk.game.locations.CharSelectNoChars;
-};
-
-Starter.randomNumberString = function (len) {
- len === undefined && (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;
-};
-
-Starter.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.CharSelectExit.click() && delay(1000);
-
- return (getLocation() !== sdk.game.locations.CharSelectConnecting);
- } else {
- return true;
- }
-};
-
-Starter.BNET = false;
-Starter.LocationEvents.oogCheck = function () {
- return (AutoMule.outOfGameCheck() || TorchSystem.outOfGameCheck() || Gambling.outOfGameCheck() || CraftingSystem.outOfGameCheck() || SoloEvents.outOfGameCheck());
-};
-
-Starter.checkDifficulty = function () {
- let setDiff = CharData.getStats().me.setDifficulty;
- if (setDiff) {
- Starter.gameInfo.difficulty = setDiff;
- }
+const LocationAction = {
+ run: function () {
+ // placeholder
+ }
};
-Starter.LocationEvents.login = function () {
- Starter.inGame && (Starter.inGame = false);
- if (getLocation() === sdk.game.locations.MainMenu && Starter.firstRun
- && Profile().type === sdk.game.profiletype.SinglePlayer
- && Controls.SinglePlayer.click()) {
- return;
- }
-
- // 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;
- }
- }
-
- // 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 {
- // make battlenet accounts/characters
- if (Starter.BNET) {
- ControlAction.timeoutDelay("Login Delay", Starter.Config.DelayBeforeLogin * 1e3);
- D2Bot.updateStatus("Logging in");
- // existing account
- if (Starter.profileInfo.account !== "") {
- try {
- // ControlAction.loginAccount(Starter.profileInfo);
- login(me.profile);
- } catch (error) {
- if (DataFile.getStats().AcctPswd) {
- Starter.profileInfo.account = DataFile.getStats().AcctName;
- Starter.profileInfo.password = DataFile.getStats().AcctPswd;
-
- for (let i = 0; i < 5; i++) {
- if (ControlAction.loginAccount(Starter.profileInfo)) {
- break;
- }
-
- if (getLocation() === sdk.game.locations.CharSelectConnecting) {
- if (Starter.charSelectConnecting()) {
- break;
- }
- }
-
- ControlAction.timeoutDelay("Unable to Connect", Starter.Config.UnableToConnectDelay * 6e4);
- Starter.profileInfo.account = DataFile.getStats().AcctName;
- Starter.profileInfo.password = DataFile.getStats().AcctPswd;
- }
- }
- }
- } else {
- // new account
- if (Starter.profileInfo.account === "") {
- if (Starter.Config.GlobalAccount || Starter.Config.GlobalAccountPassword) {
- Starter.profileInfo.account = Starter.Config.GlobalAccount.length > 0 ? Starter.Config.GlobalAccount + Starter.randomNumberString(Starter.Config.AccountSuffixLength) : Starter.randomString(12, true);
- Starter.profileInfo.password = Starter.Config.GlobalAccountPassword.length > 0 ? Starter.Config.GlobalAccountPassword : Starter.randomString(12, true);
-
- try {
- if (Starter.profileInfo.account.length > 15) throw new Error("Account name exceeds MAXIMUM length (15). Please enter a shorter name or reduce the AccountSuffixLength under StarterConfig");
- if (Starter.profileInfo.password.length > 15) throw new Error("Password name exceeds MAXIMUM length (15). Please enter a shorter name under StarterConfig");
- } catch (e) {
- D2Bot.printToConsole("Kolbot-SoloPlay: " + e.message, sdk.colors.D2Bot.Gold);
- D2Bot.setProfile("", "", null, "Normal");
- D2Bot.stop();
- }
-
- console.log("Kolbot-SoloPlay :: Generated account information. " + (Starter.Config.GlobalAccount.length > 0 ? "Pre-defined " : "Random ") + "account used");
- console.log("Kolbot-SoloPlay :: Generated password information. " + (Starter.Config.GlobalAccountPassword.length > 0 ? "Pre-defined " : "Random ") + "password used");
- ControlAction.timeoutDelay("Generating Account Information", Starter.Config.DelayBeforeLogin * 1e3);
- } else {
- Starter.profileInfo.account = Starter.randomString(12, true);
- Starter.profileInfo.password = Starter.randomString(12, true);
- console.log("Generating Random Account Information");
- ControlAction.timeoutDelay("Generating Random Account Information", Starter.Config.DelayBeforeLogin * 1e3);
- }
-
- if (ControlAction.makeAccount(Starter.profileInfo)) {
- D2Bot.setProfile(Starter.profileInfo.account, Starter.profileInfo.password, null, "Normal");
- DataFile.updateStats("AcctName", Starter.profileInfo.account);
- DataFile.updateStats("AcctPswd", Starter.profileInfo.password);
-
- return;
- } else {
- Starter.profileInfo.account = "";
- Starter.profileInfo.password = "";
- D2Bot.setProfile(Starter.profileInfo.account, Starter.profileInfo.password, null, "Normal");
- D2Bot.restart(true);
- }
- }
- }
- } else {
- // SP/TCP characters
- try {
- if (getLocation() === sdk.game.locations.MainMenu && Profile().type === sdk.game.profiletype.SinglePlayer) {
- Controls.SinglePlayer.click();
- }
- Starter.checkDifficulty();
- Starter.LocationEvents.charSelect(getLocation());
- } catch (err) {
- console.error(err);
- // Try to find the character and if that fails, make character
- if (!ControlAction.findCharacter(Starter.profileInfo)) {
- // Pop-up that happens when choosing a dead HC char
- if (getLocation() === sdk.game.locations.OkCenteredErrorPopUp) {
- Controls.OkCentered.click(); // Exit from that pop-up
- D2Bot.printToConsole("Character died", sdk.colors.D2Bot.Red);
- ControlAction.deleteAndRemakeChar(Starter.profileInfo);
- } else {
- // If make character fails, check how many characters are on that account
- if (!ControlAction.makeCharacter(Starter.profileInfo)) {
- // Account is full
- if (ControlAction.getCharacters().length >= 18) {
- D2Bot.printToConsole("Kolbot-SoloPlay: Account is full", sdk.colors.D2Bot.Orange);
- D2Bot.stop();
- }
- }
- }
- }
- }
- }
- } catch (e) {
- console.log(e + " " + getLocation());
- }
-};
-
-Starter.LocationEvents.loginError = function () {
- let string = "";
- let text = Controls.LoginErrorText.getText();
-
- if (text) {
- for (let i = 0; i < text.length; i++) {
- string += text[i];
-
- if (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.updateStatus("Invalid Password");
- D2Bot.printToConsole("Invalid Password");
- ControlAction.timeoutDelay("Invalid password delay", Starter.Config.InvalidPasswordDelay * 6e4);
- D2Bot.printToConsole("Invalid Password - Restart");
- D2Bot.restart();
-
- break;
- case getLocaleString(5208): // Invalid account
- case getLocaleString(5239): // An account name already exists
- case getLocaleString(5249): // Unable to create account
- D2Bot.updateStatus("Invalid Account Name");
- D2Bot.printToConsole("Invalid Account Name");
- Starter.profileInfo.account = "";
- Starter.profileInfo.password = "";
- D2Bot.setProfile(Starter.profileInfo.account, Starter.profileInfo.password);
- D2Bot.restart(true);
-
- break;
- case getLocaleString(5202): // cd key intended for another product
- case getLocaleString(10915): // lod key intended for another product
- 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();
- }
-
- break;
- case getLocaleString(5199):
- 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(10913):
- D2Bot.updateStatus("Disabled LoD CDKey");
- D2Bot.printToConsole("Disabled LoD 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(5347):
- D2Bot.updateStatus("Disconnected from battle.net.");
- D2Bot.printToConsole("Disconnected from battle.net.");
- Controls.OkCentered.click();
- Controls.LoginErrorOk.click();
-
- return;
- 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;
- }
- }
-
- Controls.LoginErrorOk.click();
- delay(1000);
- Controls.CharSelectExit.click();
-};
-
-Starter.LocationEvents.charSelect = function (loc) {
- let string = "";
- let text = Controls.CharSelectError.getText();
-
- if (text) {
- for (let i = 0; i < text.length; i++) {
- string += text[i];
-
- if (i !== text.length - 1) {
- string += " ";
- }
- }
-
- // CDKey disabled from realm play
- 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.deadCheck && ControlAction.deleteAndRemakeChar(Starter.profileInfo)) {
- Starter.deadCheck = false;
- }
-
- if (Object.keys(Starter.profileInfo).length) {
- if (!ControlAction.findCharacter(Starter.profileInfo)) {
- if (Starter.profileInfo.charName === DataFile.getObj().name
- && getLocation() !== sdk.game.locations.CharSelectNoChars && ControlAction.getCharacters().length === 0) {
- ControlAction.timeoutDelay("[R/D] Character not found ", 18e4);
- D2Bot.printToConsole("Avoid Creating New Character - Restart");
- D2Bot.restart();
- } else {
- if (!ControlAction.makeCharacter(Starter.profileInfo)) {
- if (ControlAction.getCharacters().length >= 18) {
- D2Bot.printToConsole("Kolbot-SoloPlay: Account is full", sdk.colors.D2Bot.Red);
- D2Bot.stop();
- }
- }
- }
- } else {
- ControlAction.loginCharacter(Starter.profileInfo, false);
- }
- }
-
- if (!Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, loc)) {
- Controls.CharSelectExit.click();
- Starter.gameInfo.rdBlocker && D2Bot.restart();
- }
-};
-
-Starter.LocationEvents.lobbyChat = function () {
- 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());
- }
- }
+(function () {
+ let joinInfo;
+
+ Starter.Config.StopOnDeadHardcore = false;
+ const Controls = require("../../modules/Control");
+ const Overrides = require("../../modules/Override");
+ const SoloEvents = (() => {
+ let { outOfGameCheck, check, gameInfo } = require("../Functions/SoloEvents");
+ return {
+ check: check,
+ gameInfo: gameInfo,
+ outOfGameCheck: outOfGameCheck,
+ };
+ })();
+
+ /**
+ * @param {Control} control
+ * @returns {string}
+ */
+ const parseControlText = (control) => {
+ if (!control) return "";
+ let text = control.getText();
+ if (!text || !text.length) return "";
+ return text.join(" ");
+ };
+
+ new Overrides.Override(Starter, Starter.receiveCopyData, function (orignal, mode, msg) {
+ switch (mode) {
+ case 1: // Join Info
+ console.log("Got Join Info");
+ joinInfo = JSON.parse(msg);
+
+ SoloEvents.gameInfo.gameName = joinInfo.gameName.toLowerCase();
+ SoloEvents.gameInfo.gamePass = joinInfo.gamePass.toLowerCase();
+
+ break;
+ case 1638:
+ try {
+ /** @type {Map} */
+ const classMap = new Map([
+ ["ZON", "amazon"],
+ ["SOR", "sorceress"],
+ ["NEC", "necromancer"],
+ ["PAL", "paladin"],
+ ["BAR", "barbarian"],
+ ["DRU", "druid"],
+ ["SIN", "assassin"],
+ ]);
+ let [modePrefix, charClass] = me.profile.toUpperCase().split("-");
+
+ if (!modePrefix || !charClass || !(charClass = classMap.get(charClass.substring(0, 3)))) {
+ D2Bot.printToConsole(
+ "*** Invalid profile name ***\n"
+ + "Profile :: " + me.profile + " | Prefix: " + modePrefix + " | CharClass: " + charClass + "\n"
+ + "@see https://github.com/blizzhackers/kolbot-SoloPlay#possible-profile-names \n"
+ + "**********************************************************************************",
+ sdk.colors.D2Bot.Red
+ );
+ CharData.delete(true);
+ throw new Error("Invalid profile name :: " + me.profile);
+ }
+
+ let obj = JSON.parse(msg);
+ let infoTag = obj.Tag.trim().capitalize(true) || "";
+ if (!infoTag) {
+ D2Bot.printToConsole(
+ "*** Invalid profile InfoTag ***\n"
+ + "Tag :: " + obj.Tag + "\n"
+ + "@see https://github.com/blizzhackers/kolbot-SoloPlay#available-characters-and-builds \n"
+ + "**********************************************************************************",
+ sdk.colors.D2Bot.Red
+ );
+ throw new Error("Invalid profile InfoTag :: " + obj.Tag);
+ }
+
+ Starter.profileInfo.profile = me.profile;
+ Starter.profileInfo.account = obj.Account;
+ if (Starter.profileInfo.account.length < 2 || Starter.profileInfo.account.length > 15) {
+ // console.warn("Invalid account name length");
+ Starter.profileInfo.account = "";
+ }
+ Starter.profileInfo.password = "";
+ Starter.profileInfo.charName = obj.Character;
+ Starter.profileInfo.difficulty = obj.Difficulty;
+ obj.Realm = obj.Realm.toLowerCase();
+ Starter.profileInfo.realm = ["east", "west"].includes(obj.Realm) ? "us" + obj.Realm : obj.Realm;
+ Starter.profileInfo.mode = Profile().type;
+ Starter.profileInfo.tag = infoTag;
+
+ /**
+ * @example SCL-ZON123
+ */
+ Starter.profileInfo.hardcore = modePrefix.includes("HC"); // SC softcore = false
+ Starter.profileInfo.expansion = modePrefix.indexOf("CC") === -1; // not CC so not classic - true
+ Starter.profileInfo.ladder = modePrefix.indexOf("NL") === -1; // not NL so its ladder - true
+ Starter.profileInfo.charClass = charClass;
+
+ if (["druid", "assassin"].includes(charClass) && !Starter.profileInfo.expansion) {
+ D2Bot.printToConsole(
+ "*** Invalid character class for mode ***\n"
+ + "CharClass :: " + charClass + "\n"
+ + "Expansion characters cannot be made in classic\n"
+ + "**********************************************************************************",
+ sdk.colors.D2Bot.Red
+ );
+ throw new Error("Expansion characters cannot be made in classic");
+ }
+
+ {
+ let soloStats = CharData.getStats();
+ let update = false;
+ // new profile
+ if (!soloStats.finalBuild) {
+ soloStats.finalBuild = Starter.profileInfo.tag;
+ CharData.updateData("me", soloStats);
+ } else if (soloStats.finalBuild !== Starter.profileInfo.tag) {
+ soloStats.finalBuild = Starter.profileInfo.tag;
+ update = true;
+ }
+
+ if (soloStats.currentBuild !== soloStats.finalBuild
+ && !["Start", "Stepping", "Leveling"].includes(soloStats.currentBuild)) {
+ soloStats.currentBuild = "Leveling";
+ }
+
+ if (update) {
+ soloStats.charms = {};
+ CharData.updateData("me", soloStats);
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ D2Bot.stop();
+ }
+
+ break;
+ default:
+ orignal(mode, msg);
+ }
+ }).apply();
+
+ new Overrides.Override(Starter, Starter.scriptMsgEvent, function (orignal, msg) {
+ if (typeof msg !== "string") return;
+ if (msg === "event") {
+ SoloEvents.check = true;
+ } else if (msg === "diffChange") {
+ Starter.checkDifficulty();
+ } else if (msg === "test") {
+ console.debug(
+ sdk.colors.Green
+ + "//-----------DataDump Start-----------//",
+ "\nÿc8ThreadData ::\n", getScript(true),
+ "\nÿc8GlobalVariabls ::\n", Object.keys(global),
+ "\n" + sdk.colors.Red
+ + "//-----------DataDump End-----------//"
+ );
+ } else if (msg === "remake") {
+ Starter.deadCheck = true;
+ } else {
+ orignal(msg);
+ }
+ }).apply();
+
+ /** @param {number} [amount] */
+ const scrollDown = function (amount = 4) {
+ try {
+ me.blockMouse = true;
+ for (let i = 0; i < amount; i++) {
+ sendKey(sdk.keys.code.DownArrow);
+ }
+ } finally {
+ me.blockMouse = false;
+ }
+ };
+
+ /**
+ * @typedef {Object} CharInfo
+ * @property {string} [account]
+ * @property {string} [password]
+ * @property {string} [realm]
+ * @property {string} charClass
+ * @property {string} charName
+ * @property {boolean} ladder
+ * @property {boolean} expansion
+ * @property {boolean} hardcore
+ */
+
+ /**
+ * @param {CharInfo} info
+ * @returns {boolean}
+ */
+ ControlAction.makeCharacter = function (info) {
+ try {
+ const NameGen = require("./NameGen");
+ !info.charClass && (info.charClass = "barbarian");
+ !info.charName && (info.charName = NameGen());
+ me.blockMouse = true;
+
+ let clickCoords = [];
+ let soloStats = CharData.getStats();
+ let timeout = getTickCount() + Time.minutes(5);
+
+ /** @type {Map 1) {
+ CharData.delete(false);
+ CharData.updateData("me", "finalBuild", Starter.profileInfo.tag);
+ Developer.logPerformance && Tracker.initialize();
+ }
+
+ D2Bot.updateStatus("Making Character: " + info.charName);
+
+ // cycle until in lobby
+ while (getLocation() !== sdk.game.locations.Lobby && !me.ingame) {
+ switch (getLocation()) {
+ case sdk.game.locations.CharSelect:
+ case sdk.game.locations.CharSelectConnecting:
+ case sdk.game.locations.CharSelectNoChars:
+ let control = Controls.CharSelectCreate.control;
+
+ // Create Character greyed out
+ if (control && control.disabled === sdk.game.controls.Disabled) {
+ return false;
+ }
+
+ Controls.CharSelectCreate.click();
+
+ break;
+ case sdk.game.locations.LobbyPleaseWait:
+ D2Bot.restart(); // single player error on finding character
+
+ break;
+ case sdk.game.locations.CharacterCreate:
+ clickCoords = coords.get(info.charClass.toLowerCase()) || coords.get("paladin");
+ getControl().click(clickCoords[0], clickCoords[1]);
+ delay(500);
+
+ break;
+ case sdk.game.locations.NewCharSelected:
+ // hardcore char warning
+ if (Controls.CharCreateHCWarningOk.control) {
+ Controls.CharCreateHCWarningOk.click();
+ } else {
+ Controls.CharCreateCharName.setText(info.charName);
+
+ !info.expansion && Controls.CharCreateExpansion.click();
+ !info.ladder && Controls.CharCreateLadder.click();
+ info.hardcore && Controls.CharCreateHardcore.click();
+ Controls.BottomRightOk.click();
+ }
+
+ break;
+ case sdk.game.locations.OkCenteredErrorPopUp:
+ // char name exists (text box 4, 268, 320, 264, 120)
+ ControlAction.timeoutDelay("Character Name exists: " + info.charName + ". Making new Name.", 5e3);
+ Starter.profileInfo.charName = info.charName = NameGen();
+ Controls.OkCentered.click();
+ D2Bot.updateStatus("Making Character: " + info.charName);
+
+ break;
+ default:
+ console.debug("Unknown Location: " + getLocation());
+ break;
+ }
+
+ if (getTickCount() > timeout) {
+ D2Bot.printToConsole(
+ "Failed to create character: " + info.charName
+ + " Location: " + getLocation(),
+ sdk.colors.D2Bot.Red
+ );
+ return false;
+ }
+
+ delay(500);
+ }
+
+ D2Bot.setProfile(null, null, info.charName, "Normal");
+ let gamename = info.charName.substring(0, 7) + "-" + Starter.randomString(3, false) + "-";
+ DataFile.updateStats("gameName", gamename);
+
+ return true;
+ } finally {
+ me.blockMouse = false;
+ }
+ };
+
+ /**
+ * @param {CharInfo} info
+ * @returns {Control}
+ */
+ ControlAction.findCharacter = function (info) {
+ let count = 0;
+ let singlePlayer = ![sdk.game.gametype.OpenBattlenet, sdk.game.gametype.BattleNet].includes(Profile().type);
+ // offline doesn't have a character limit cap
+ let cap = singlePlayer ? 999 : 24;
+ 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.charSelectConnecting()) {
+ D2Bot.printToConsole("Stuck at connecting screen");
+ D2Bot.restart();
+ }
+ }
+
+ // 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 (String.isEqual(text[1], info.charName)) {
+ 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()) {
+ 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;
+ };
+
+ /**
+ * @param {CharInfo} info
+ * @param {boolean} startFromTop
+ * @returns {boolean}
+ */
+ ControlAction.loginCharacter = function (info, startFromTop = true) {
+ try {
+ me.blockMouse = true;
+ // start from beginning of the char list
+ startFromTop && sendKey(sdk.keys.code.Home);
+
+ MainLoop:
+ // cycle until in lobby or in game
+ while (getLocation() !== sdk.game.locations.Lobby) {
+ switch (getLocation()) {
+ case sdk.game.locations.SplashScreen:
+ case sdk.game.locations.MainMenu:
+ case sdk.game.locations.Login:
+ if (getLocation() === sdk.game.locations.MainMenu
+ && Profile().type === sdk.game.profiletype.SinglePlayer
+ && Controls.SinglePlayer.click()) {
+ Starter.checkDifficulty();
+ break;
+ } else if (Starter.BNET) {
+ Starter.LocationEvents.login();
+ }
+
+ break;
+ case sdk.game.locations.CharSelect:
+ let control = ControlAction.findCharacter(info);
+
+ if (control) {
+ control.click();
+ Controls.BottomRightOk.click();
+
+ if (getLocation() === sdk.game.locations.SelectDifficultySP) {
+ try {
+ Starter.LocationEvents.selectDifficultySP();
+ Starter.locationTimeout(Time.seconds(3), sdk.game.locations.SelectDifficultySP);
+ } catch (err) {
+ break MainLoop;
+ }
+
+ if (me.ingame) {
+ return true;
+ }
+ }
+
+ return true;
+ } else if (getLocation() !== sdk.game.locations.CharSelect) {
+ break;
+ }
+
+ break MainLoop;
+ case sdk.game.locations.CharSelectNoChars:
+ Controls.BottomLeftExit.click(); // why exit rather than returning false?
+
+ break;
+ case sdk.game.locations.Disconnected:
+ case sdk.game.locations.OkCenteredErrorPopUp:
+ break MainLoop;
+ default:
+ break;
+ }
+
+ delay(100);
+ }
+
+ return false;
+ } finally {
+ me.blockMouse = false;
+ }
+ };
+
+ /**
+ * @todo add open bnet check
+ * @param {CharInfo} info
+ * @returns {boolean}
+ */
+ ControlAction.makeAccount = function (info) {
+ try {
+ me.blockMouse = true;
+ D2Bot.updateStatus("Making Account: " + info.account);
+
+ // cycle until in empty char screen
+ while (getLocation() !== sdk.game.locations.CharSelectNoChars) {
+ switch (getLocation()) {
+ case sdk.game.locations.MainMenu:
+ ControlAction.clickRealm(ControlAction.realms[info.realm]);
+ Controls.BattleNet.click();
+
+ break;
+ case sdk.game.locations.Login:
+ Controls.CreateNewAccount.click();
+
+ break;
+ case sdk.game.locations.LoginError:
+ case sdk.game.locations.LoginUnableToConnect:
+ return false;
+ case sdk.game.locations.SplashScreen:
+ Controls.SplashScreen.click();
+
+ break;
+ case sdk.game.locations.MainMenuConnecting:
+ let tick = getTickCount();
+
+ while (getLocation() === sdk.game.locations.MainMenuConnecting) {
+ if (getTickCount() - tick > 10000) {
+ Controls.LoginCancelWait.click();
+ }
+
+ delay(500);
+ }
+
+ break;
+ case sdk.game.locations.CharacterCreate:
+ Controls.BottomLeftExit.click();
+
+ break;
+ case sdk.game.locations.OkCenteredErrorPopUp:
+ info.account = "";
+ info.password = "";
+ D2Bot.setProfile(info.account, info.password);
+ D2Bot.restart(true);
+
+ 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:
+ let { profiles, realms } = Developer.setEmail;
+ if (Developer.setEmail.enabled
+ && (!profiles.length || profiles.includes(me.profile))
+ && (!realms.length || realms.includes(Profile().gateway.toLowerCase()))) {
+ ControlAction.setEmail();
+ } else {
+ Controls.EmailDontRegisterContinue.control
+ ? Controls.EmailDontRegisterContinue.click()
+ : Controls.EmailDontRegister.click();
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ delay(100);
+ }
+
+ return true;
+ } finally {
+ me.blockMouse = false;
+ }
+ };
+
+ /**
+ * @param {CharInfo} info
+ * @returns {boolean}
+ */
+ ControlAction.deleteAndRemakeChar = function (info) {
+ if (!ControlAction.deleteCharacter(info)) {
+ return false;
+ }
+
+ // Delete old files - leaving csv file's for now as I don't think they interfere with the overlay
+ CharData.delete(true);
+ DataFile.create();
+ DataFile.updateStats("handle", Starter.handle);
+ CharData.updateData("me", "finalBuild", Starter.profileInfo.tag);
+ Developer.logPerformance && Tracker.initialize();
+ D2Bot.printToConsole("Deleted: " + info.charName + ". Now remaking...", sdk.colors.D2Bot.Gold);
+ Starter.deadCheck = false;
+
+ return ControlAction.makeCharacter(Starter.profileInfo);
+ };
+
+ /**
+ * @param {CharInfo} info
+ * @returns {void}
+ */
+ ControlAction.saveInfo = function (info) {
+ // Data-file already exists
+ if (FileTools.exists("logs/Kolbot-SoloPlay/" + info.realm + "/" + info.charClass + "-" + info.charClass + "-" + info.charName + ".json")) {
+ return;
+ }
+
+ let folder;
+
+ if (!FileTools.exists("logs/Kolbot-SoloPlay")) {
+ folder = dopen("logs");
+ folder.create("Kolbot-SoloPlay");
+ }
+
+ if (!FileTools.exists("logs/Kolbot-SoloPlay/" + info.realm)) {
+ folder = dopen("logs/Kolbot-SoloPlay");
+ folder.create(info.realm);
+ }
+
+ if (!FileTools.exists("logs/Kolbot-SoloPlay/" + info.realm + "/" + info.charClass + "-" + info.charName + ".json")) {
+ FileTools.writeText("logs/Kolbot-SoloPlay/" + info.realm + "/" + info.charClass + "-" + info.charName + ".json", JSON.stringify(info));
+ }
+ };
+
+ /**
+ * @param {CharInfo} info
+ * @returns {boolean}
+ */
+ ControlAction.loginAccount = function (info) {
+ try {
+ me.blockMouse = true;
+ let tick = getTickCount();
+
+ MainLoop:
+ while (true) {
+ switch (getLocation()) {
+ case sdk.game.locations.PreSplash:
+ break;
+ case sdk.game.locations.MainMenu:
+ info.realm && ControlAction.clickRealm(ControlAction.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.LoginUnableToConnect:
+ case sdk.game.locations.RealmDown:
+ // Unable to connect, let the caller handle it.
+ 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
+ let 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
+ case sdk.game.locations.Lobby:
+ case sdk.game.locations.LobbyChat:
+ // somehow we are in the lobby?
+ Control.LobbyQuit.click();
+
+ break;
+ default:
+ console.log(getLocation());
+
+ return false;
+ }
+
+ if (getTickCount() - tick >= 20000) {
+ return false;
+ }
+
+ delay(100);
+ }
+
+ delay(1000);
+
+ return getLocation() === sdk.game.locations.CharSelect || getLocation() === sdk.game.locations.CharSelectNoChars;
+ } finally {
+ me.blockMouse = false;
+ }
+ };
+
+ Starter.randomNumberString = function (len) {
+ len === undefined && (len = rand(2, 5));
+
+ let rval = "";
+ const vals = "0123456789".split("");
+
+ for (let i = 0; i < len; i += 1) {
+ rval += vals.random();
+ }
+
+ return rval;
+ };
+
+ Starter.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;
+ }
+ };
+
+ Starter.BNET = ([sdk.game.profiletype.Battlenet, sdk.game.profiletype.OpenBattlenet].includes(Profile().type));
+ Starter.LocationEvents.oogCheck = function () {
+ return (
+ AutoMule.outOfGameCheck()
+ || TorchSystem.outOfGameCheck()
+ || Gambling.outOfGameCheck()
+ || CraftingSystem.outOfGameCheck()
+ || SoloEvents.outOfGameCheck()
+ );
+ };
+
+ Starter.checkDifficulty = function () {
+ let setDiff = CharData.getStats().setDifficulty;
+ if (setDiff) {
+ console.debug(setDiff);
+ Starter.gameInfo.difficulty = setDiff;
+ }
+ };
+
+ Starter.LocationEvents.login = function () {
+ Starter.inGame && (Starter.inGame = false);
+ let pType = Profile().type;
+
+ 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
+ if (Starter.BNET) {
+ ControlAction.timeoutDelay("Login Delay", Starter.Config.DelayBeforeLogin * 1e3);
+ D2Bot.updateStatus("Logging in");
+ // existing account
+ if (Starter.profileInfo.account !== "") {
+ try {
+ // ControlAction.loginAccount(Starter.profileInfo);
+ login(me.profile);
+ } catch (error) {
+ if (DataFile.getStats().AcctPswd) {
+ Starter.profileInfo.account = DataFile.getStats().AcctName;
+ Starter.profileInfo.password = DataFile.getStats().AcctPswd;
+
+ for (let i = 0; i < 5; i++) {
+ if (ControlAction.loginAccount(Starter.profileInfo)) {
+ break;
+ }
+
+ if (getLocation() === sdk.game.locations.CharSelectConnecting) {
+ if (Starter.charSelectConnecting()) {
+ break;
+ }
+ }
+
+ ControlAction.timeoutDelay("Unable to Connect", Starter.Config.UnableToConnectDelay * 6e4);
+ Starter.profileInfo.account = DataFile.getStats().AcctName;
+ Starter.profileInfo.password = DataFile.getStats().AcctPswd;
+ }
+ }
+ }
+ } else {
+ // new account
+ if (Starter.profileInfo.account === "") {
+ if (Starter.Config.GlobalAccount || Starter.Config.GlobalAccountPassword) {
+ Starter.profileInfo.account = Starter.Config.GlobalAccount.length > 0
+ ? Starter.Config.GlobalAccount + Starter.randomNumberString(Starter.Config.AccountSuffixLength)
+ : Starter.randomString(12, true);
+ Starter.profileInfo.password = Starter.Config.GlobalAccountPassword.length > 0
+ ? Starter.Config.GlobalAccountPassword
+ : Starter.randomString(12, true);
+
+ try {
+ if (Starter.profileInfo.account.length > 15) throw new Error("Account name exceeds MAXIMUM length (15). Please enter a shorter name or reduce the AccountSuffixLength under StarterConfig");
+ if (Starter.profileInfo.password.length > 15) throw new Error("Password name exceeds MAXIMUM length (15). Please enter a shorter name under StarterConfig");
+ } catch (e) {
+ D2Bot.printToConsole("Kolbot-SoloPlay: " + e.message, sdk.colors.D2Bot.Gold);
+ D2Bot.setProfile("", "", null, "Normal");
+ D2Bot.stop();
+ }
+
+ console.log("Kolbot-SoloPlay :: Generated account information. " + (Starter.Config.GlobalAccount.length > 0 ? "Pre-defined " : "Random ") + "account used");
+ console.log("Kolbot-SoloPlay :: Generated password information. " + (Starter.Config.GlobalAccountPassword.length > 0 ? "Pre-defined " : "Random ") + "password used");
+ ControlAction.timeoutDelay("Generating Account Information", Starter.Config.DelayBeforeLogin * 1e3);
+ } else {
+ Starter.profileInfo.account = Starter.randomString(12, true);
+ Starter.profileInfo.password = Starter.randomString(12, true);
+ console.log("Generating Random Account Information");
+ ControlAction.timeoutDelay("Generating Random Account Information", Starter.Config.DelayBeforeLogin * 1e3);
+ }
+
+ if (ControlAction.makeAccount(Starter.profileInfo)) {
+ D2Bot.setProfile(Starter.profileInfo.account, Starter.profileInfo.password, null, "Normal");
+ DataFile.updateStats("AcctName", Starter.profileInfo.account);
+ DataFile.updateStats("AcctPswd", Starter.profileInfo.password);
+
+ return;
+ } else {
+ Starter.profileInfo.account = "";
+ Starter.profileInfo.password = "";
+ D2Bot.setProfile(Starter.profileInfo.account, Starter.profileInfo.password, null, "Normal");
+ D2Bot.restart(true);
+ }
+ }
+ }
+ } else {
+ // SP/TCP characters
+ try {
+ if (getLocation() === sdk.game.locations.MainMenu) {
+ pType === sdk.game.profiletype.SinglePlayer
+ ? Controls.SinglePlayer.click()
+ : ControlAction.loginOtherMultiplayer();
+ }
+ Starter.checkDifficulty();
+ Starter.LocationEvents.charSelect(getLocation());
+ } catch (err) {
+ console.error(err);
+ // Try to find the character and if that fails, make character
+ if (!ControlAction.findCharacter(Starter.profileInfo)) {
+ // Pop-up that happens when choosing a dead HC char
+ if (getLocation() === sdk.game.locations.OkCenteredErrorPopUp) {
+ Controls.OkCentered.click(); // Exit from that pop-up
+ D2Bot.printToConsole("Character died", sdk.colors.D2Bot.Red);
+ ControlAction.deleteAndRemakeChar(Starter.profileInfo);
+ } else {
+ // If make character fails, check how many characters are on that account
+ if (!ControlAction.makeCharacter(Starter.profileInfo)) {
+ // Account is full
+ if (ControlAction.getCharacters().length >= 18) {
+ D2Bot.printToConsole("Kolbot-SoloPlay: Account is full", sdk.colors.D2Bot.Orange);
+ D2Bot.stop();
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (e) {
+ console.log(e + " " + getLocation());
+ }
+ };
+
+ Starter.accountExists = false;
+
+ Starter.LocationEvents.loginError = function () {
+ let cdkeyError = false;
+ let defaultPrint = true;
+ let string = "";
+ let text = getLocation() === sdk.game.locations.LoginError
+ ? Controls.LoginErrorText.getText()
+ : Controls.LoginCdKeyInUseBy.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):
+ case getLocaleString(sdk.locale.text.InvalidPassword):
+ D2Bot.printToConsole(string);
+ D2Bot.stop();
+
+ break;
+ case getLocaleString(5208): // Invalid account
+ D2Bot.updateStatus("Invalid Account Name");
+ D2Bot.printToConsole("Invalid Account Name :: " + Starter.profileInfo.account);
+ Starter.profileInfo.account = "";
+ Starter.profileInfo.password = "";
+ D2Bot.setProfile(Starter.profileInfo.account, Starter.profileInfo.password);
+ D2Bot.restart(true);
+
+ break;
+ case getLocaleString(5249): // Unable to create account
+ case getLocaleString(5239): // An account name already exists
+ if (!Starter.accountExists) {
+ Starter.accountExists = true;
+ Control.LoginErrorOk.click();
+ delay(100);
+ Control.BottomLeftExit.click();
+ Starter.LocationEvents.login();
+ return;
+ }
+ D2Bot.updateStatus("Account name already exists :: " + Starter.profileInfo.account);
+ D2Bot.printToConsole("Account name already exists :: " + Starter.profileInfo.account);
+ Starter.profileInfo.account = "";
+ Starter.profileInfo.password = "";
+ D2Bot.setProfile(Starter.profileInfo.account, Starter.profileInfo.password);
+
+ 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("key in use", Starter.Config.CDKeyInUseDelay * 6e4);
+
+ return;
+ }
+
+ break;
+ 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.Disconnected):
+ D2Bot.updateStatus("Disconnected");
+ D2Bot.printToConsole("Disconnected");
+ Controls.OkCentered.click();
+ Controls.LoginErrorOk.click();
+ ControlAction.timeoutDelay("Disconnected", (rand(3, 5)) * 60000);
+
+ return;
+ case getLocaleString(sdk.locale.text.LoginError):
+ case getLocaleString(sdk.locale.text.BattlenetNotResponding):
+ case getLocaleString(sdk.locale.text.BattlenetNotResponding2):
+ case getLocaleString(sdk.locale.text.OnlyOneInstanceAtATime):
+ Controls.LoginErrorOk.click();
+ Controls.BottomLeftExit.click();
+ D2Bot.printToConsole(string);
+ ControlAction.timeoutDelay("Login Error Delay", Time.minutes(Starter.Config.UnableToConnectDelay));
+ 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.BottomLeftExit.click();
+
+ while (true) {
+ delay(1000);
+ }
+ };
+
+ Starter.LocationEvents.charSelect = function (loc) {
+ let string = "";
+ let text = Controls.CharSelectError.getText();
+
+ if (text) {
+ for (let i = 0; i < text.length; i++) {
+ string += text[i];
+
+ if (i !== text.length - 1) {
+ string += " ";
+ }
+ }
+
+ // CDKey disabled from realm play
+ 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.deadCheck && ControlAction.deleteAndRemakeChar(Starter.profileInfo)) {
+ Starter.deadCheck = false;
+
+ return;
+ }
+
+ if (!Controls.CharSelectCreate.control) {
+ // We aren't in the right place
+ return;
+ }
+
+ if (Object.keys(Starter.profileInfo).length) {
+ if (!ControlAction.findCharacter(Starter.profileInfo)) {
+ let currLoc = getLocation();
+ if (Starter.profileInfo.charName === DataFile.getObj().name
+ && currLoc !== sdk.game.locations.CharSelectNoChars
+ && ControlAction.getCharacters().length === 0) {
+ ControlAction.timeoutDelay("[R/D] Character not found ", 18e4);
+ D2Bot.printToConsole("Avoid Creating New Character - Restart");
+ D2Bot.restart();
+ } else {
+ if (!ControlAction.makeCharacter(Starter.profileInfo)) {
+ if (ControlAction.getCharacters().length >= 18) {
+ D2Bot.printToConsole("Kolbot-SoloPlay: Account is full", sdk.colors.D2Bot.Red);
+ D2Bot.stop();
+ }
+ }
+ }
+ } else {
+ ControlAction.loginCharacter(Starter.profileInfo, false);
+ }
+ }
+
+ if (!Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, loc)) {
+ Controls.BottomLeftExit.click();
+ Starter.gameInfo.rdBlocker && D2Bot.restart();
+ }
+ };
+
+ Starter.LocationEvents.lobbyChat = function () {
+ 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 (oogCheck()) 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;
+
+ if (Starter.chanInfo.afterMsg) {
+ if (!Array.isArray(Starter.chanInfo.afterMsg)) {
+ 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) {
+ !Array.isArray(Starter.chanInfo.joinChannel) && (Starter.chanInfo.joinChannel = [Starter.chanInfo.joinChannel]);
+ !Array.isArray(Starter.chanInfo.firstMsg) && (Starter.chanInfo.firstMsg = [Starter.chanInfo.firstMsg]);
+
+ for (let i = 0; i < Starter.chanInfo.joinChannel.length; i++) {
+ ControlAction.timeoutDelay("Chat delay", Starter.Config.ChatActionsDelay * 1e3);
+
+ if (ControlAction.joinChannel(Starter.chanInfo.joinChannel[i])) {
+ Starter.useChat = true;
+ } else {
+ console.log("ÿ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
+ Starter.chanInfo.announce = Starter.Config.AnnounceGames;
+
+ if (Starter.chanInfo.announce) {
+ let { gameName, gamePass } = Starter.gameInfo;
+ Starter.sayMsg(
+ "Next game is " + gameName + Starter.gameCount + (gamePass === "" ? "" : "//" + gamePass)
+ );
+ }
+
+ Starter.LocationEvents.openCreateGameWindow();
+ };
+
+ const oogCheck = () => (
+ AutoMule.outOfGameCheck()
+ || TorchSystem.outOfGameCheck()
+ || Gambling.outOfGameCheck()
+ || CraftingSystem.outOfGameCheck()
+ || SoloEvents.outOfGameCheck()
+ );
+
+ const _locations = new Map([
+ [
+ sdk.game.locations.PreSplash,
+ function () {
+ ControlAction.click();
+ }
+ ],
+ [
+ sdk.game.locations.GatewaySelect,
+ function () {
+ Controls.GatewayCancel.click();
+ }
+ ],
+ [
+ sdk.game.locations.SplashScreen,
+ function () {
+ Starter.LocationEvents.login();
+ }
+ ],
+ [
+ sdk.game.locations.MainMenu,
+ function () {
+ Starter.LocationEvents.login();
+ }
+ ],
+ [
+ sdk.game.locations.Login,
+ function () {
+ Starter.LocationEvents.login();
+ }
+ ],
+ [
+ sdk.game.locations.OtherMultiplayer,
+ function () {
+ Starter.LocationEvents.otherMultiplayerSelect();
+ }
+ ],
+ [
+ sdk.game.locations.TcpIp,
+ function () {
+ Controls.TcpIpHost.click();
+ }
+ ],
+ [
+ sdk.game.locations.TcpIpEnterIp,
+ function () {
+ Controls.TcpIpCancel.click();
+ }
+ ],
+ [
+ sdk.game.locations.LoginError,
+ function () {
+ Starter.LocationEvents.loginError();
+ }
+ ],
+ [
+ sdk.game.locations.LoginUnableToConnect,
+ function () {
+ Starter.LocationEvents.unableToConnect();
+ }
+ ],
+ [
+ sdk.game.locations.TcpIpUnableToConnect,
+ function () {
+ Starter.LocationEvents.unableToConnect();
+ }
+ ],
+ [
+ sdk.game.locations.CdKeyInUse,
+ function () {
+ Starter.LocationEvents.loginError();
+ }
+ ],
+ [
+ sdk.game.locations.InvalidCdKey,
+ function () {
+ Starter.LocationEvents.loginError();
+ }
+ ],
+ [
+ sdk.game.locations.RealmDown,
+ function () {
+ Starter.LocationEvents.realmDown();
+ }
+ ],
+ [
+ sdk.game.locations.Disconnected,
+ function () {
+ ControlAction.timeoutDelay("Disconnected", 3000);
+ Controls.OkCentered.click();
+ }
+ ],
+ [
+ sdk.game.locations.RegisterEmail,
+ function () {
+ Controls.EmailDontRegisterContinue.control
+ ? Controls.EmailDontRegisterContinue.click()
+ : Controls.EmailDontRegister.click();
+ }
+ ],
+ [
+ sdk.game.locations.MainMenuConnecting,
+ function (loc) {
+ (!Starter.locationTimeout(Starter.Config.ConnectingTimeout * 1e3, loc)
+ && Controls.LoginCancelWait.click());
+ }
+ ],
+ [
+ sdk.game.locations.CharSelectPleaseWait,
+ function (loc) {
+ (!Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, loc)
+ && Controls.OkCentered.click());
+ }
+ ],
+ [
+ sdk.game.locations.CharSelect,
+ function (loc) {
+ Starter.LocationEvents.charSelect(loc);
+ }
+ ],
+ [
+ sdk.game.locations.CharSelectConnecting,
+ function (loc) {
+ Starter.LocationEvents.charSelect(loc);
+ }
+ ],
+ [
+ sdk.game.locations.CharSelectNoChars,
+ function (loc) {
+ Starter.LocationEvents.charSelect(loc);
+ }
+ ],
+ [
+ sdk.game.locations.SelectDifficultySP,
+ function () {
+ Starter.LocationEvents.selectDifficultySP();
+ }
+ ],
+ [
+ sdk.game.locations.CharacterCreate,
+ function (loc) {
+ if (!Starter.locationTimeout(Time.seconds(5), loc)) {
+ Controls.BottomLeftExit.click();
+ }
+ }
+ ],
+ [
+ sdk.game.locations.ServerDown,
+ function () {
+ ControlAction.timeoutDelay("Server Down", Time.minutes(5));
+ Controls.OkCentered.click();
+ }
+ ],
+ [
+ sdk.game.locations.LobbyPleaseWait,
+ function (loc) {
+ (!Starter.locationTimeout(Starter.Config.PleaseWaitTimeout * 1e3, loc)
+ && Controls.OkCentered.click());
+ }
+ ],
+ [
+ sdk.game.locations.Lobby,
+ function () {
+ D2Bot.updateStatus("Lobby");
+ ControlAction.saveInfo(Starter.profileInfo);
+
+ me.blockKeys = false;
+
+ !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 !== "" && Controls.LobbyEnterChat.click()) return;
+
+ if (Starter.inGame || Starter.gameInfo.error) {
+ !Starter.gameStart && (Starter.gameStart = DataFile.getStats().ingameTick);
+
+ if (getTickCount() - Starter.gameStart < Starter.Config.MinGameTime * 1e3 && !joinInfo) {
+ let _waitTime = Starter.Config.MinGameTime * 1e3 + Starter.gameStart - getTickCount();
+ ControlAction.timeoutDelay("Min game time wait", _waitTime);
+ }
+ }
+
+ if (Starter.inGame) {
+ if (oogCheck()) return;
+
+ 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 (Starter.deadCheck) {
+ Controls.LobbyQuit.click();
+ } else {
+ Starter.LocationEvents.openCreateGameWindow();
+ }
+ }
+ ],
+ [
+ sdk.game.locations.LobbyChat,
+ function () {
+ Starter.LocationEvents.lobbyChat();
+ }
+ ],
+ [
+ sdk.game.locations.CreateGame,
+ function (loc) {
+ ControlAction.timeoutDelay("Create Game Delay", Starter.Config.DelayBeforeLogin * 1e3);
+ 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());
+ }
+
+ D2Bot.requestGameInfo();
+ delay(500);
+
+ // todo - really don't need use profiles set difficulty for online. Only single player so re-write difficulty stuff
+ Starter.checkDifficulty();
+
+ Starter.gameInfo.gameName = DataFile.getStats().gameName;
+ Starter.gameInfo.gamePass = Starter.randomString(5, true);
+
+ if (!Starter.gameInfo.gameName || String.isEqual(Starter.gameInfo.gameName, "name")) {
+ Starter.gameInfo.gameName = (
+ Starter.profileInfo.charName.substring(0, 7) + "-"
+ + Starter.randomString(3, false) + "-"
+ );
+ }
+
+ // 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, gamePass, difficulty, gameDelay] = [
+ (Starter.gameInfo.gameName + Starter.gameCount),
+ Starter.gameInfo.gamePass,
+ Starter.gameInfo.difficulty,
+ Time.seconds(Starter.Config.CreateGameDelay)
+ ];
+
+ ControlAction.createGame(gameName, gamePass, difficulty, gameDelay);
+ Starter.lastGameStatus = "pending";
+ Starter.setNextGame(Starter.gameInfo);
+ Starter.locationTimeout(10000, loc);
+ }
+ ],
+ [
+ sdk.game.locations.GameNameExists,
+ function () {
+ Controls.CreateGameWindow.click();
+ Starter.gameCount += 1;
+ Starter.lastGameStatus = "ready";
+ }
+ ],
+ [
+ sdk.game.locations.WaitingInLine,
+ function () {
+ Starter.LocationEvents.waitingInLine();
+ }
+ ],
+ [
+ sdk.game.locations.JoinGame,
+ function () {
+ Starter.LocationEvents.openCreateGameWindow();
+ }
+ ],
+ [
+ sdk.game.locations.Ladder,
+ function () {
+ Starter.LocationEvents.openCreateGameWindow();
+ }
+ ],
+ [
+ sdk.game.locations.ChannelList,
+ function () {
+ Starter.LocationEvents.openCreateGameWindow();
+ }
+ ],
+ [
+ sdk.game.locations.LobbyLostConnection,
+ function () {
+ ControlAction.timeoutDelay("LostConnection", 3000);
+ Controls.OkCentered.click();
+ }
+ ],
+ [
+ sdk.game.locations.GameDoesNotExist,
+ function () {
+ Starter.LocationEvents.gameDoesNotExist();
+ }
+ ],
+ [
+ sdk.game.locations.GameIsFull,
+ function () {
+ Starter.LocationEvents.openCreateGameWindow();
+ }
+ ],
+ [
+ sdk.game.locations.OkCenteredErrorPopUp,
+ function () {
+ let string = parseControlText(Controls.OkCenteredText);
+
+ switch (string) {
+ case getLocaleString(sdk.locale.text.CannotCreateGamesDeadHCChar):
+ Starter.deadCheck = true;
+ Controls.OkCentered.click();
+ return;
+ 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);
+ Starter.profileInfo.account = "";
+ Starter.profileInfo.password = "";
+ CharData.login.updateData({ account: "", password: "" });
+
+ break;
+ default:
+ D2Bot.updateStatus("Error");
+ D2Bot.printToConsole("Error - " + string);
+
+ break;
+ }
+ Controls.OkCentered.click();
+
+ ControlAction.timeoutDelay("Error", Time.minutes(1));
+ }
+ ]
+ ]);
+
+ /**
+ * Actual definition for LocationAction.run
+ */
+ LocationAction.run = function () {
+ try {
+ let loc = getLocation();
+ let func = _locations.get(loc);
+ if (typeof func === "function") {
+ func(loc);
+ } else {
+ console.log("Unhandled location: " + loc);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ };
+})();
- if (Starter.inGame) {
- if (oogCheck()) 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;
-
- if (Starter.chanInfo.afterMsg) {
- !Array.isArray(Starter.chanInfo.afterMsg) && (Starter.chanInfo.afterMsg = [Starter.chanInfo.afterMsg]);
-
- for (let i = 0; i < Starter.chanInfo.afterMsg.length; i++) {
- Starter.sayMsg(Starter.chanInfo.afterMsg[i]);
- delay(500);
- }
- }
- }
-
- if (!Starter.chatActionsDone) {
- Starter.chatActionsDone = true;
-
- Starter.chanInfo.joinChannel = Starter.Config.JoinChannel;
- Starter.chanInfo.firstMsg = Starter.Config.FirstJoinMessage;
-
- if (Starter.chanInfo.joinChannel) {
- !Array.isArray(Starter.chanInfo.joinChannel) && (Starter.chanInfo.joinChannel = [Starter.chanInfo.joinChannel]);
- !Array.isArray(Starter.chanInfo.firstMsg) && (Starter.chanInfo.firstMsg = [Starter.chanInfo.firstMsg]);
-
- for (let i = 0; i < Starter.chanInfo.joinChannel.length; i++) {
- ControlAction.timeoutDelay("Chat delay", Starter.Config.ChatActionsDelay * 1e3);
-
- if (ControlAction.joinChannel(Starter.chanInfo.joinChannel[i])) {
- Starter.useChat = true;
- } else {
- console.log("ÿ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
- Starter.chanInfo.announce = Starter.Config.AnnounceGames;
-
- if (Starter.chanInfo.announce) {
- Starter.sayMsg("Next game is " + Starter.gameInfo.gameName + Starter.gameCount + (Starter.gameInfo.gamePass === "" ? "" : "//" + Starter.gameInfo.gamePass));
- }
-
- Starter.LocationEvents.openCreateGameWindow();
-};
diff --git a/libs/SoloPlay/Tools/Overlay.js b/libs/SoloPlay/Tools/Overlay.js
index ad76c1dc..b78cd232 100644
--- a/libs/SoloPlay/Tools/Overlay.js
+++ b/libs/SoloPlay/Tools/Overlay.js
@@ -10,653 +10,518 @@ includeIfNotIncluded("SoloPlay/Tools/Developer.js");
includeIfNotIncluded("SoloPlay/Tools/Tracker.js");
includeIfNotIncluded("SoloPlay/Functions/PrototypeOverrides.js");
+/**
+ * @todo Clean this up, probably needs to be entirely rewritten
+ * - show current script
+ */
const Overlay = {
- resfix: { x: -10, y: me.screensize ? 0 : -120 },
- quest: { x: 8, y: 368 },
- qYMod: { 1: 368, 2: 384, 3: 384, 4: 414, 5: 384 },
- dashboard: { x: 120, y: 470 },
- timer: { x: 0, y: 595 },
- build: SetUp.currentBuild,
- realm: (me.realm ? me.realm : "SinglePlayer"),
- difficulty: () => sdk.difficulty.nameOf(me.diff),
- level: () => myData.me.level,
- text: {
- hooks: [],
- GameTracker: Developer.readObj(Tracker.GTPath),
- enabled: true,
- charlvl: 0,
- tick: 0,
-
- clock: function () {
- if (!Developer.logPerformance) return "";
- this.GameTracker === undefined && (this.GameTracker = Developer.readObj(Tracker.GTPath));
- this.tick = getTickCount();
- let currInGame = getTickCount() - me.gamestarttime;
- let totalTime = Developer.formatTime(this.GameTracker.Total + currInGame);
- let totalInGame = Developer.formatTime(this.GameTracker.InGame + currInGame);
-
- return ("Total: ÿc0" + totalTime + "ÿc4 InGame: ÿc0" + totalInGame + "ÿc4 OOG: ÿc0" + Developer.formatTime(this.GameTracker.OOG));
- },
-
- timer: function () {
- return (new Date(getTickCount() - me.gamestarttime).toISOString().slice(11, -5));
- },
-
- check: function () {
- if (!this.enabled) {
- this.flush();
-
- return;
- }
-
- // Double check in case still got here before being ready
- if (!me.gameReady && !me.ingame && !me.area) return;
-
- !this.getHook("dashboard") && this.add("dashboard");
- !this.getHook("credits") && this.add("credits");
-
- if (!this.getHook("InGameTimer")) {
- this.add("InGameTimer");
- } else {
- if (getTickCount() - this.tick >= 1000) {
- this.getHook("InGameTimer").hook.text = "ÿc0" + this.timer();
- }
- }
-
- if (Developer.logPerformance) {
- this.GameTracker === undefined && (this.GameTracker = Developer.readObj(Tracker.GTPath));
- if (!this.getHook("times")) {
- this.add("times");
- } else {
- if (getTickCount() - this.tick >= 1000) {
- this.getHook("times").hook.text = this.clock();
- }
- }
- }
-
- if (!this.getHook("level")) {
- this.add("level");
- } else if (this.charlvl !== Overlay.level()) {
- this.charlvl = Overlay.level();
- this.getHook("level").hook.text = "Name: ÿc0" + me.name + "ÿc4 Diff: ÿc0" + Overlay.difficulty() + "ÿc4 Level: ÿc0" + this.charlvl;
- }
- },
-
- add: function (name) {
- switch (name) {
- case "dashboard":
- this.hooks.push({
- name: "dashboard",
- hook: new Box(Overlay.dashboard.x + Overlay.resfix.x, Overlay.dashboard.y + Overlay.resfix.y, 370, 80, 0x0, 4, 0)
- });
-
- this.hooks.push({
- name: "dashboardframe",
- hook: new Frame(Overlay.dashboard.x + Overlay.resfix.x, Overlay.dashboard.y + Overlay.resfix.y, 370, 80, 0)
- });
-
- this.getHook("dashboard").hook.zorder = 0;
-
- break;
- case "credits":
- this.hooks.push({
- name: "credits",
- hook: new Text("Kolbot-SoloPlay by: ÿc0 theBGuy" + "ÿc4 Realm: ÿc0" + Overlay.realm, Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 15, 4, 13, 0)
- });
-
- break;
- case "level":
- this.charlvl = Overlay.level();
- this.hooks.push({
- name: "level",
- hook: new Text("Name: ÿc0" + me.name + "ÿc4 Diff: ÿc0" + Overlay.difficulty() + "ÿc4 Level: ÿc0" + this.charlvl, Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 30, 4, 13, 0)
- });
-
- break;
- case "times":
- this.hooks.push({
- name: "times",
- hook: new Text(this.clock(), Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 75, 4, 13, 0)
- });
-
- break;
- case "InGameTimer":
- this.hooks.push({
- name: "timerBoard",
- hook: new Box(Overlay.timer.x, Overlay.timer.y - 15 + Overlay.resfix.y, 68, 18, 0, 4, 0)
- });
-
- this.hooks.push({
- name: "timerFrame",
- hook: new Frame(Overlay.timer.x, Overlay.timer.y - 15 + Overlay.resfix.y, 68, 18, 0)
- });
-
- this.hooks.push({
- name: "InGameTimer",
- hook: new Text("ÿc0" + this.timer(), Overlay.timer.x + 7, Overlay.timer.y + Overlay.resfix.y, 0, 13, 0)
- });
-
- break;
- }
- },
-
- getHook: function (name) {
- for (let i = 0; i < this.hooks.length; i++) {
- if (this.hooks[i].name === name) {
- return this.hooks[i];
- }
- }
-
- return false;
- },
-
- flush: function () {
- while (this.hooks.length) {
- this.hooks.shift().hook.remove();
- }
- return true;
- }
- },
-
- quests: {
- enabled: true,
- hooks: [],
- font: 12,
- qHooks: [],
-
- data: {
- Den: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.DenofEvil, sdk.quest.states.Completed)
- },
- BloodRaven: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.SistersBurialGrounds, sdk.quest.states.Completed)
- },
- Tristram: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheSearchForCain, sdk.quest.states.Completed)
- },
- Countess: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.ForgottenTower, sdk.quest.states.Completed)
- },
- Smith: {
- complete: false,
- condition: () => (me.getQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.Completed) || me.getQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete))
- },
- Andariel: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.SistersToTheSlaughter, sdk.quest.states.Completed)
- },
- Cube: {
- complete: false,
- condition: () => me.cube
- },
- Radament: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.RadamentsLair, sdk.quest.states.Completed)
- },
- HoradricStaff: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheHoradricStaff, sdk.quest.states.Completed)
- },
- Amulet: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.Completed)
- },
- Summoner: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheSummoner, sdk.quest.states.Completed)
- },
- Duriel: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheSevenTombs, sdk.quest.states.Completed)
- },
- GoldenBird: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheGoldenBird, sdk.quest.states.Completed)
- },
- KhalimsWill: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.KhalimsWill, sdk.quest.states.Completed)
- },
- LamEsen: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.LamEsensTome, sdk.quest.states.Completed)
- },
- Travincal: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheBlackenedTemple, sdk.quest.states.Completed)
- },
- Mephisto: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheGuardian, sdk.quest.states.Completed)
- },
- Izual: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TheFallenAngel, sdk.quest.states.Completed)
- },
- HellForge: {
- complete: false,
- condition: () => (me.getQuest(sdk.quest.id.HellsForge, sdk.quest.states.Completed) || me.getQuest(sdk.quest.id.HellsForge, sdk.quest.states.ReqComplete))
- },
- Diablo: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.TerrorsEnd, sdk.quest.states.Completed)
- },
- Shenk: {
- complete: false,
- condition: () => (me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.Completed) || me.getQuest(sdk.quest.id.SiegeOnHarrogath, sdk.quest.states.ReqComplete))
- },
- Barbies: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.RescueonMountArreat, sdk.quest.states.Completed)
- },
- Anya: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.PrisonofIce, sdk.quest.states.Completed)
- },
- Ancients: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.RiteofPassage, sdk.quest.states.Completed)
- },
- Baal: {
- complete: false,
- condition: () => me.getQuest(sdk.quest.id.EyeofDestruction, sdk.quest.states.Completed)
- },
- },
-
- questStatus: function (q = "") {
- if (typeof this.data[q] === "undefined") return "undefined";
- if (this.data[q].complete) return "ÿc2Complete";
- let status = this.data[q].condition();
- if (!!status) {
- this.data[q].complete = true;
- return "ÿc2Complete";
- }
- return "ÿc1Incomplete";
- },
-
- getRes: function () {
- // Double check in case still got here before being ready
- if (!me.gameReady || !me.ingame || !me.area) return "";
- return ("FR: ÿc1" + me.FR + "ÿc4 CR: ÿc3" + me.CR + "ÿc4 LR: ÿc9" + me.LR + "ÿc4 PR: ÿc2" + me.PR + "ÿc4 CurrentBuild: ÿc0" + Overlay.build);
- },
-
- getStats: function () {
- // Double check in case still got here before being ready
- if (!me.gameReady || !me.ingame || !me.area) return "";
-
- let textLine = ("MF: ÿc8" + me.getStat(sdk.stats.MagicBonus) + "ÿc4 FHR: ÿc8" + (me.FHR) + "ÿc4 FBR: ÿc8" + (me.FBR) + "ÿc4 FCR: ÿc8" + (me.FCR)
- + "ÿc4 IAS: ÿc8" + (me.IAS));
-
- return textLine;
- },
-
- check: function () {
- if (!this.enabled || !me.gameReady || !me.ingame || !me.area || me.dead) {
- this.flush();
-
- return;
- }
-
- !this.getHook("resistances", this.hooks) ? this.add("resistances") : this.getHook("resistances", this.hooks).hook.text = this.getRes();
- !this.getHook("stats", this.hooks) ? this.add("stats") : this.getHook("stats", this.hooks).hook.text = this.getStats();
- !this.getHook("questheader") && this.add("questheader");
-
- switch (me.act) {
- case 1:
- !this.getHook("Den") ? this.add("Den") : this.getHook("Den").hook.text = "Den: " + this.questStatus("Den");
- !this.getHook("BloodRaven") ? this.add("BloodRaven") : this.getHook("BloodRaven").hook.text = "Blood Raven: " + this.questStatus("BloodRaven");
- !this.getHook("Tristram") ? this.add("Tristram") : this.getHook("Tristram").hook.text = "Tristram: " + this.questStatus("Tristram");
- !this.getHook("Countess") ? this.add("Countess") : this.getHook("Countess").hook.text = "Countess: " + this.questStatus("Countess");
- !this.getHook("Smith") ? this.add("Smith") : this.getHook("Smith").hook.text = "Smith: " + this.questStatus("Smith");
- !this.getHook("Andariel") ? this.add("Andariel") : this.getHook("Andariel").hook.text = "Andariel: " + this.questStatus("Andariel");
-
- break;
- case 2:
- !this.getHook("Radament") ? this.add("Radament") : this.getHook("Radament").hook.text = "Radament: " + this.questStatus("Radament");
- //!this.getHook("Cube") ? this.add("Cube") : this.getHook("Cube").hook.text = "Horadric Cube: " + this.questStatus("Cube");
- !this.getHook("HoradricStaff") ? this.add("HoradricStaff") : this.getHook("HoradricStaff").hook.text = "Horadric Staff: " + this.questStatus("HoradricStaff");
- !this.getHook("Amulet") ? this.add("Amulet") : this.getHook("Amulet").hook.text = "Amulet: " + this.questStatus("Amulet");
- !this.getHook("Summoner") ? this.add("Summoner") : this.getHook("Summoner").hook.text = "Summoner: " + this.questStatus("Summoner");
- !this.getHook("Duriel") ? this.add("Duriel") : this.getHook("Duriel").hook.text = "Duriel: " + this.questStatus("Duriel");
-
- break;
- case 3:
- !this.getHook("GoldenBird") ? this.add("GoldenBird") : this.getHook("GoldenBird").hook.text = "Golden Bird: " + this.questStatus("GoldenBird");
- !this.getHook("KhalimsWill") ? this.add("KhalimsWill") : this.getHook("KhalimsWill").hook.text = "Khalim's Will: " + this.questStatus("KhalimsWill");
- !this.getHook("LamEsen") ? this.add("LamEsen") : this.getHook("LamEsen").hook.text = "LamEsen: " + this.questStatus("LamEsen");
- !this.getHook("Travincal") ? this.add("Travincal") : this.getHook("Travincal").hook.text = "Travincal: " + this.questStatus("Travincal");
- !this.getHook("Mephisto") ? this.add("Mephisto") : this.getHook("Mephisto").hook.text = "Mephisto: " + this.questStatus("Mephisto");
-
- break;
- case 4:
- !this.getHook("Izual") ? this.add("Izual") : this.getHook("Izual").hook.text = "Izual: " + this.questStatus("Izual");
- !this.getHook("HellForge") ? this.add("HellForge") : this.getHook("HellForge").hook.text = "HellForge: " + this.questStatus("HellForge");
- !this.getHook("Diablo") ? this.add("Diablo") : this.getHook("Diablo").hook.text = "Diablo: " + this.questStatus("Diablo");
-
- break;
- case 5:
- !this.getHook("Shenk") ? this.add("Shenk") : this.getHook("Shenk").hook.text = "Shenk: " + this.questStatus("Shenk");
- !this.getHook("Barbies") ? this.add("Barbies") : this.getHook("Barbies").hook.text = "Barbies: " + this.questStatus("Barbies");
- !this.getHook("Anya") ? this.add("Anya") : this.getHook("Anya").hook.text = "Anya: " + this.questStatus("Anya");
- !this.getHook("Ancients") ? this.add("Ancients") : this.getHook("Ancients").hook.text = "Ancients: " + this.questStatus("Ancients");
- !this.getHook("Baal") ? this.add("Baal") : this.getHook("Baal").hook.text = "Baal: " + this.questStatus("Baal");
-
- break;
- }
-
- !this.getHook("questbox") && this.add("questbox");
- },
-
- add: function (name) {
- switch (name) {
- case "resistances":
- this.hooks.push({
- name: "resistances",
- hook: new Text(this.getRes(), Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 45, 4, 13, 0)
- });
-
- break;
- case "stats":
- this.hooks.push({
- name: "stats",
- hook: new Text(this.getStats(), Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 60, 4, 13, 0)
- });
-
- break;
- case "questbox":
- this.qHooks.push({
- name: "questbox",
- hook: new Box(Overlay.quest.x - 8, Overlay.quest.y + Overlay.resfix.y - 17, 145, 10 + [0, 105, 90, 90, 60, 90][me.act], 0x0, 4, 0)
- });
-
- this.qHooks.push({
- name: "questframe",
- hook: new Frame(Overlay.quest.x - 8, Overlay.quest.y + Overlay.resfix.y - 17, 145, 10 + [0, 105, 90, 90, 60, 90][me.act], 0)
- });
-
- this.getHook("questbox").hook.zorder = 0;
-
- break;
- case "questheader":
- Overlay.quest.y = Overlay.qYMod[me.act];
-
- this.qHooks.push({
- name: "questheader",
- hook: new Text("Quests in Act: ÿc0" + me.act, Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y, 4, 0, 0)
- });
-
- break;
- case "Den":
- this.qHooks.push({
- name: "Den",
- hook: new Text("Den: " + this.questStatus("Den"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "BloodRaven":
- this.qHooks.push({
- name: "BloodRaven",
- hook: new Text("Blood Raven: " + this.questStatus("BloodRaven"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Tristram":
- this.qHooks.push({
- name: "Tristram",
- hook: new Text("Tristram: " + this.questStatus("Tristram"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Countess":
- this.qHooks.push({
- name: "Countess",
- hook: new Text("Countess: " + this.questStatus("Countess"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Smith":
- this.qHooks.push({
- name: "Smith",
- hook: new Text("Smith: " + this.questStatus("Smith"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Andariel":
- this.qHooks.push({
- name: "Andariel",
- hook: new Text("Andariel: " + this.questStatus("Andariel"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Radament":
- this.qHooks.push({
- name: "Radament",
- hook: new Text("Radament: " + this.questStatus("Radament"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "HoradricStaff":
- this.qHooks.push({
- name: "HoradricStaff",
- hook: new Text("Horadric Staff: " + this.questStatus("HoradricStaff"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Amulet":
- this.qHooks.push({
- name: "Amulet",
- hook: new Text("Amulet: " + this.questStatus("Amulet"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Summoner":
- this.qHooks.push({
- name: "Summoner",
- hook: new Text("Summoner: " + this.questStatus("Summoner"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Duriel":
- this.qHooks.push({
- name: "Duriel",
- hook: new Text("Duriel: " + this.questStatus("Duriel"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "GoldenBird":
- this.qHooks.push({
- name: "GoldenBird",
- hook: new Text("Golden Bird: " + this.questStatus("GoldenBird"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "KhalimsWill":
- this.qHooks.push({
- name: "KhalimsWill",
- hook: new Text("Khalim's Will: " + this.questStatus("KhalimsWill"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "LamEsen":
- this.qHooks.push({
- name: "LamEsen",
- hook: new Text("LamEsen: " + this.questStatus("LamEsen"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Travincal":
- this.qHooks.push({
- name: "Travincal",
- hook: new Text("Travincal: " + this.questStatus("Travincal"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Mephisto":
- this.qHooks.push({
- name: "Mephisto",
- hook: new Text("Mephisto: " + this.questStatus("Mephisto"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Izual":
- this.qHooks.push({
- name: "Izual",
- hook: new Text("Izual: " + this.questStatus("Izual"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "HellForge":
- this.qHooks.push({
- name: "HellForge",
- hook: new Text("HellForge: " + this.questStatus("HellForge"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Diablo":
- this.qHooks.push({
- name: "Diablo",
- hook: new Text("Diablo: " + this.questStatus("Diablo"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Shenk":
- this.qHooks.push({
- name: "Shenk",
- hook: new Text("Shenk: " + this.questStatus("Shenk"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Barbies":
- this.qHooks.push({
- name: "Barbies",
- hook: new Text("Barbies: " + this.questStatus("Barbies"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Anya":
- this.qHooks.push({
- name: "Anya",
- hook: new Text("Anya: " + this.questStatus("Anya"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Ancients":
- this.qHooks.push({
- name: "Ancients",
- hook: new Text("Ancients: " + this.questStatus("Ancients"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- case "Baal":
- this.qHooks.push({
- name: "Baal",
- hook: new Text("Baal: " + this.questStatus("Baal"), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * this.qHooks.length), 4, this.font, 0)
- });
-
- break;
- }
- },
-
- getHook: function (name, hooks) {
- while (!me.gameReady || !me.ingame || !me.area) {
- delay(500);
- }
-
- hooks === undefined && (hooks = this.qHooks);
-
- for (let i = 0; i < hooks.length; i += 1) {
- if (hooks[i].name === name) return hooks[i];
- }
-
- return false;
- },
-
- flush: function () {
- while (this.hooks.length) {
- this.hooks.shift().hook.remove();
- }
-
- while (this.qHooks.length) {
- this.qHooks.shift().hook.remove();
- }
- return true;
- }
- },
-
- timeOut: 0,
-
- update: function (msg = false) {
- function status () {
- let hide = [
- sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.QuickSkill, sdk.uiflags.SkillWindow,
- sdk.uiflags.ChatBox, sdk.uiflags.EscMenu, sdk.uiflags.KeytotheCairnStonesScreen, sdk.uiflags.Shop,
- sdk.uiflags.SubmitItem, sdk.uiflags.Quest, sdk.uiflags.Party, sdk.uiflags.Msgs, sdk.uiflags.Stash,
- sdk.uiflags.Cube, sdk.uiflags.Help, sdk.uiflags.MercScreen
- ];
-
- if (!me.gameReady || !me.ingame || !me.area || me.dead) {
- Overlay.disable(true);
- } else {
- while (!me.gameReady) {
- delay(100);
- }
-
- for (let flag = 0; flag < hide.length; flag++) {
- if (getUIFlag(hide[flag])) {
- Overlay.text.flush();
- Overlay.quests.flush();
-
- while (getUIFlag(hide[flag])) {
- delay(100);
- }
-
- Misc.poll(() => me.gameReady);
- flag = 0;
- } else {
- Overlay.text.enabled = true;
- }
- }
- }
-
- Overlay.text.check();
- if (Overlay.quests.enabled) {
- Overlay.quests.check();
- } else {
- if (Overlay.timeOut > 0 && getTickCount() > Overlay.timeOut) {
- Overlay.quests.enabled = true;
- Overlay.timeOut = 0;
- }
- Overlay.quests.flush();
- }
- }
-
- return msg ? true : (me.gameReady && me.ingame && !me.dead) ? status() : false;
- },
-
- disable: function (all = false) {
- me.overhead("Disable");
-
- if (all) {
- me.overhead("Disable All");
- Overlay.text.flush() && Overlay.quests.flush();
- [Overlay.text.enabled, Overlay.quests.enabled] = [false, false];
- this.timeOut = getTickCount() + Time.seconds(15);
- } else {
- Overlay.quests.flush();
- Overlay.quests.enabled = false;
- console.log(Overlay.quests.enabled);
- }
-
- delay(100);
-
- return true;
- },
-
- flush: function () {
- return Overlay.quests.flush();
- },
+ timeOut: 0,
+ resfix: { x: -10, y: me.screensize ? 0 : -120 },
+ quest: { x: 8, y: 368 },
+ qYMod: { 1: 368, 2: 384, 3: 384, 4: 414, 5: 384 },
+ dashboard: { x: 120, y: 470 },
+ timer: { x: 0, y: 595 },
+ build: SetUp.currentBuild,
+ script: "",
+ realm: (me.realm ? me.realm : "SinglePlayer"),
+ difficulty: sdk.difficulty.nameOf(me.diff),
+ level: function () {
+ return me.data.level;
+ },
+
+ text: (function () {
+ const _gameTracker = Tracker.readObj(Tracker.GTPath);
+ let [_tick, _charlvl] = [0, 0];
+
+ const _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);
+ };
+
+ const _timer = function () {
+ return (new Date(getTickCount() - me.gamestarttime).toISOString().slice(11, -5));
+ };
+
+ return {
+ /** @type {Array<{ name: string, hook: Hook }>} */
+ hooks: [],
+ enabled: true,
+
+ clock: function () {
+ if (!Developer.logPerformance) return "";
+ _gameTracker === undefined && (Object.assign(_gameTracker, Tracker.readObj(Tracker.GTPath)));
+ _tick = getTickCount();
+ let currInGame = getTickCount() - me.gamestarttime;
+ let totalTime = _format(_gameTracker.Total + currInGame);
+ let totalInGame = _format(_gameTracker.InGame + currInGame);
+
+ return ("Total: ÿc0" + totalTime + "ÿc4 InGame: ÿc0" + totalInGame + "ÿc4 OOG: ÿc0" + _format(_gameTracker.OOG));
+ },
+
+ check: function () {
+ if (!this.enabled) {
+ this.flush();
+
+ return;
+ }
+
+ // Double check in case still got here before being ready
+ if (!me.gameReady && !me.ingame && !me.area) return;
+
+ !this.getHook("dashboard") && this.add("dashboard");
+ !this.getHook("credits") && this.add("credits");
+
+ if (!this.getHook("InGameTimer")) {
+ this.add("InGameTimer");
+ } else {
+ if (getTickCount() - _tick >= 1000) {
+ this.getHook("InGameTimer").hook.text = "ÿc0" + _timer();
+ }
+ }
+
+ if (Developer.logPerformance) {
+ if (!this.getHook("times")) {
+ this.add("times");
+ } else {
+ if (getTickCount() - _tick >= 1000) {
+ this.getHook("times").hook.text = this.clock();
+ }
+ }
+ }
+
+ if (!this.getHook("level")) {
+ this.add("level");
+ } else if (_charlvl !== Overlay.level()) {
+ _charlvl = Overlay.level();
+ this.getHook("level").hook.text = "Name: ÿc0" + me.name + "ÿc4 Diff: ÿc0" + Overlay.difficulty + "ÿc4 Level: ÿc0" + _charlvl;
+ }
+ },
+
+ add: function (name) {
+ switch (name) {
+ case "dashboard":
+ this.hooks.push({
+ name: "dashboard",
+ hook: new Box(Overlay.dashboard.x + Overlay.resfix.x, Overlay.dashboard.y + Overlay.resfix.y, 370, 80, 0x0, 4, 0)
+ });
+
+ this.hooks.push({
+ name: "dashboardframe",
+ hook: new Frame(Overlay.dashboard.x + Overlay.resfix.x, Overlay.dashboard.y + Overlay.resfix.y, 370, 80, 0)
+ });
+
+ this.getHook("dashboard").hook.zorder = 0;
+
+ break;
+ case "credits":
+ this.hooks.push({
+ name: "credits",
+ hook: new Text("Kolbot-SoloPlay by: ÿc0 theBGuy" + "ÿc4 Realm: ÿc0" + Overlay.realm, Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 15, 4, 13, 0)
+ });
+
+ break;
+ case "level":
+ _charlvl = Overlay.level();
+ this.hooks.push({
+ name: "level",
+ hook: new Text("Name: ÿc0" + me.name + "ÿc4 Diff: ÿc0" + Overlay.difficulty + "ÿc4 Level: ÿc0" + _charlvl, Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 30, 4, 13, 0)
+ });
+
+ break;
+ case "times":
+ this.hooks.push({
+ name: "times",
+ hook: new Text(this.clock(), Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 75, 4, 13, 0)
+ });
+
+ break;
+ case "InGameTimer":
+ this.hooks.push({
+ name: "timerBoard",
+ hook: new Box(Overlay.timer.x, Overlay.timer.y - 15 + Overlay.resfix.y, 68, 18, 0, 4, 0)
+ });
+
+ this.hooks.push({
+ name: "timerFrame",
+ hook: new Frame(Overlay.timer.x, Overlay.timer.y - 15 + Overlay.resfix.y, 68, 18, 0)
+ });
+
+ this.hooks.push({
+ name: "InGameTimer",
+ hook: new Text("ÿc0" + _timer(), Overlay.timer.x + 7, Overlay.timer.y + Overlay.resfix.y, 0, 13, 0)
+ });
+
+ break;
+ }
+ },
+
+ getHook: function (name) {
+ for (let i = 0; i < this.hooks.length; i++) {
+ if (this.hooks[i].name === name) {
+ return this.hooks[i];
+ }
+ }
+
+ return false;
+ },
+
+ flush: function () {
+ while (this.hooks.length) {
+ this.hooks.shift().hook.remove();
+ }
+ return true;
+ }
+ };
+ })(),
+
+ quests: (function () {
+ const QuestData = require("../../core/GameData/QuestData");
+
+ /**
+ * @constructor
+ * @param {string} name
+ * @param {number} id
+ * @param {boolean} [preReq]
+ */
+ function QuestHook (name, id, preReq = false) {
+ this.name = name;
+ this.id = id;
+ this.preReq = preReq;
+ }
+
+ /**
+ * @this {QuestHook}
+ * @returns {string}
+ */
+ QuestHook.prototype.status = function () {
+ return QuestData.get(this.id).complete(this.preReq) ? "ÿc2Complete" : "ÿc1Incomplete";
+ };
+
+ /**
+ * @this {QuestHook}
+ * @returns {{ name: string, hook: Hook }}
+ */
+ QuestHook.prototype.hook = function () {
+ return {
+ name: this.name,
+ hook: new Text(this.name + ": " + this.status(), Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y + (15 * _qHooks.length), 4, _font, 0)
+ };
+ };
+
+ function StatsHook (name, status, hook) {
+ this.name = name;
+ this.status = status;
+ /**
+ * @private
+ * @type {function(): Hook}
+ */
+ this._hook = hook;
+ }
+
+ /**
+ * @this {StatsHook}
+ * @returns {{ name: string, hook: Hook }}
+ */
+ StatsHook.prototype.hook = function () {
+ return {
+ name: this.name,
+ hook: this._hook()
+ };
+ };
+
+ const _quests = new Map([
+ // Act 1
+ ["Den", new QuestHook("Den", sdk.quest.id.DenofEvil)],
+ ["Blood Raven", new QuestHook("Blood Raven", sdk.quest.id.SistersBurialGrounds)],
+ ["Tristram", new QuestHook("Tristram", sdk.quest.id.TheSearchForCain)],
+ ["Countess", new QuestHook("Countess", sdk.quest.id.ForgottenTower)],
+ ["Smith", new QuestHook("Smith", sdk.quest.id.ToolsoftheTrade, true)],
+ ["Andariel", new QuestHook("Andariel", sdk.quest.id.SistersToTheSlaughter)],
+ // Act 2
+ ["Cube", new QuestHook("Cube", sdk.quest.id.TheHoradricStaff)],
+ ["Radament", new QuestHook("Radament", sdk.quest.id.RadamentsLair)],
+ ["Staff", new QuestHook("Staff", sdk.quest.id.TheHoradricStaff)],
+ ["Amulet", new QuestHook("Amulet", sdk.quest.id.TheTaintedSun)],
+ ["Summoner", new QuestHook("Summoner", sdk.quest.id.TheSummoner)],
+ ["Duriel", new QuestHook("Duriel", sdk.quest.id.TheSevenTombs)],
+ // Act 3
+ ["Golden Bird", new QuestHook("Golden Bird", sdk.quest.id.TheGoldenBird)],
+ ["Khalim's Will", new QuestHook("Khalim's Will", sdk.quest.id.KhalimsWill)],
+ ["Lam Esen", new QuestHook("Lam Esen", sdk.quest.id.LamEsensTome)],
+ ["Travincal", new QuestHook("Travincal", sdk.quest.id.TheBlackenedTemple)],
+ ["Mephisto", new QuestHook("Mephisto", sdk.quest.id.TheGuardian)],
+ // Act 4
+ ["Izual", new QuestHook("Izual", sdk.quest.id.TheFallenAngel)],
+ ["Hell Forge", new QuestHook("Hell Forge", sdk.quest.id.HellsForge, true)],
+ ["Diablo", new QuestHook("Diablo", sdk.quest.id.TerrorsEnd)],
+ // Act 5
+ ["Shenk", new QuestHook("Shenk", sdk.quest.id.SiegeOnHarrogath, true)],
+ ["Barbies", new QuestHook("Barbies", sdk.quest.id.RescueonMountArreat)],
+ ["Anya", new QuestHook("Anya", sdk.quest.id.PrisonofIce)],
+ ["Ancients", new QuestHook("Ancients", sdk.quest.id.RiteofPassage)],
+ ["Baal", new QuestHook("Baal", sdk.quest.id.EyeofDestruction)]
+ ]);
+
+ const _acts = new Map([
+ [1, ["Den", "Blood Raven", "Tristram", "Countess", "Smith", "Andariel"]],
+ [2, [/* "Cube", */"Radament", "Staff", "Amulet", "Summoner", "Duriel"]],
+ [3, ["Golden Bird", "Khalim's Will", "Lam Esen", "Travincal", "Mephisto"]],
+ [4, ["Izual", "Hell Forge", "Diablo"]],
+ [5, ["Shenk", "Barbies", "Anya", "Ancients", "Baal"]]
+ ]);
+
+ const _res = function () {
+ return (
+ "FR: ÿc1" + me.FR
+ + "ÿc4 CR: ÿc3" + me.CR
+ + "ÿc4 LR: ÿc9" + me.LR
+ + "ÿc4 PR: ÿc2" + me.PR
+ + "ÿc4 CurrentBuild: ÿc0" + Overlay.build
+ );
+ };
+
+ const _stats = function () {
+ return (
+ "MF: ÿc8" + me.getStat(sdk.stats.MagicBonus)
+ + "ÿc4 FHR: ÿc8" + (me.FHR)
+ + "ÿc4 FBR: ÿc8" + (me.FBR)
+ + "ÿc4 FCR: ÿc8" + (me.FCR)
+ + "ÿc4 IAS: ÿc8" + (me.IAS)
+ );
+ };
+
+ const _statsMap = new Map([
+ [
+ "stats",
+ new StatsHook("stats", _stats, () => new Text(_stats(), Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 60, 4, 13, 0))
+ ],
+ [
+ "res",
+ new StatsHook("res", _res, () => new Text(_res(), Overlay.dashboard.x, Overlay.dashboard.y + Overlay.resfix.y + 45, 4, 13, 0))
+ ],
+ [
+ "gold",
+ new StatsHook(
+ "gold",
+ function () {
+ return "ÿc6Goldÿc0: ÿc0" + me.gold;
+ },
+ function () {
+ let x = me.screensize ? 275 : 195;
+ let y = Overlay.resfix.y + 586;
+ return new Text("ÿc6Goldÿc0: ÿc0" + me.gold, x, y, 4, 6, 0);
+ }
+ )
+ ],
+ ]);
+
+ const _font = 12;
+ /** @type {Array<{ name: string, hook: Hook }>} */
+ const _qHooks = [];
+ /** @type {Array<{ name: string, hook: Hook }>} */
+ const _hooks = [];
+
+ return {
+ enabled: true,
+
+ /**
+ * @param {string} name
+ * @returns {void}
+ */
+ addQuest: function (name) {
+ const quest = _quests.get(name);
+ if (!quest) return;
+
+ _qHooks.push(quest.hook());
+ },
+
+ /**
+ * @param {string} name
+ * @returns {void}
+ */
+ updateQuest: function (name) {
+ const quest = _quests.get(name);
+ if (!quest) return;
+
+ const hook = this.getHook(name);
+ if (!hook) {
+ this.addQuest(name);
+ } else {
+ hook.hook.text = quest.name + ": " + quest.status();
+ }
+ },
+
+ /**
+ * @param {string} name
+ * @returns {void}
+ */
+ updateStats: function (name) {
+ const hook = this.getHook(name, _hooks);
+ if (!hook) {
+ const stat = _statsMap.get(name);
+ if (!stat) return;
+ _hooks.push(stat.hook());
+ } else {
+ hook.hook.text = _statsMap.get(name).status();
+ }
+ },
+
+ check: function () {
+ if (!this.enabled || !me.gameReady || !me.ingame || !me.area || me.dead) {
+ this.flush();
+
+ return;
+ }
+
+ this.updateStats("res");
+ this.updateStats("stats");
+ this.updateStats("gold");
+ !this.getHook("questheader") && this.add("questheader");
+
+ _acts.get(me.act).forEach((quest) => this.updateQuest(quest));
+
+ !this.getHook("questbox") && this.add("questbox");
+ },
+
+ /**
+ * @param {string} name
+ */
+ add: function (name) {
+ switch (name) {
+ case "questbox":
+ _qHooks.push({
+ name: "questbox",
+ hook: new Box(Overlay.quest.x - 8, Overlay.quest.y + Overlay.resfix.y - 17, 145, 10 + [0, 105, 90, 90, 60, 90][me.act], 0x0, 4, 0)
+ });
+
+ _qHooks.push({
+ name: "questframe",
+ hook: new Frame(Overlay.quest.x - 8, Overlay.quest.y + Overlay.resfix.y - 17, 145, 10 + [0, 105, 90, 90, 60, 90][me.act], 0)
+ });
+
+ this.getHook("questbox").hook.zorder = 0;
+
+ break;
+ case "questheader":
+ Overlay.quest.y = Overlay.qYMod[me.act];
+
+ _qHooks.push({
+ name: "questheader",
+ hook: new Text("Quests in Act: ÿc0" + me.act, Overlay.quest.x, Overlay.quest.y + Overlay.resfix.y, 4, 0, 0)
+ });
+
+ break;
+ }
+ },
+
+ /**
+ * @param {string} name
+ * @param {Array<{ name: string, hook: Hook }>} [hooks]
+ * @returns {{ name: string, hook: Hook } | false}
+ */
+ getHook: function (name, hooks) {
+ while (!me.gameReady || !me.ingame || !me.area) {
+ delay(500);
+ }
+
+ hooks === undefined && (hooks = _qHooks);
+
+ for (let i = 0; i < hooks.length; i += 1) {
+ if (hooks[i].name === name) return hooks[i];
+ }
+
+ return false;
+ },
+
+ flush: function () {
+ while (_hooks.length) {
+ _hooks.shift().hook.remove();
+ }
+
+ while (_qHooks.length) {
+ _qHooks.shift().hook.remove();
+ }
+ return true;
+ }
+ };
+ })(),
+
+ update: function (msg = false) {
+ function status () {
+ let hide = [
+ sdk.uiflags.Inventory, sdk.uiflags.StatsWindow, sdk.uiflags.QuickSkill, sdk.uiflags.SkillWindow,
+ sdk.uiflags.ChatBox, sdk.uiflags.EscMenu, sdk.uiflags.KeytotheCairnStonesScreen, sdk.uiflags.Shop,
+ sdk.uiflags.SubmitItem, sdk.uiflags.Quest, sdk.uiflags.Party, sdk.uiflags.Msgs, sdk.uiflags.Stash,
+ sdk.uiflags.Cube, sdk.uiflags.Help, sdk.uiflags.MercScreen
+ ];
+
+ if (!me.gameReady || !me.ingame || !me.area || me.dead) {
+ Overlay.disable(true);
+ } else {
+ while (!me.gameReady) {
+ delay(100);
+ }
+
+ for (let flag = 0; flag < hide.length; flag++) {
+ if (getUIFlag(hide[flag])) {
+ Overlay.text.flush();
+ Overlay.quests.flush();
+
+ while (getUIFlag(hide[flag])) {
+ delay(100);
+ }
+
+ Misc.poll(() => me.gameReady);
+ flag = 0;
+ } else {
+ Overlay.text.enabled = true;
+ }
+ }
+ }
+
+ Overlay.text.check();
+ if (Overlay.quests.enabled) {
+ Overlay.quests.check();
+ } else {
+ if (Overlay.timeOut > 0 && getTickCount() > Overlay.timeOut) {
+ Overlay.quests.enabled = true;
+ Overlay.timeOut = 0;
+ }
+ Overlay.quests.flush();
+ }
+ }
+
+ return msg ? true : (me.gameReady && me.ingame && !me.dead) ? status() : false;
+ },
+
+ disable: function (all = false) {
+ me.overhead("Disable");
+
+ if (all) {
+ me.overhead("Disable All");
+ Overlay.text.flush() && Overlay.quests.flush();
+ [Overlay.text.enabled, Overlay.quests.enabled] = [false, false];
+ Overlay.timeOut = getTickCount() + Time.seconds(15);
+ } else {
+ Overlay.quests.flush();
+ Overlay.quests.enabled = false;
+ console.log(Overlay.quests.enabled);
+ }
+
+ delay(100);
+
+ return true;
+ },
+
+ flush: function () {
+ return Overlay.quests.flush();
+ },
};
diff --git a/libs/SoloPlay/Tools/SoloIndex.js b/libs/SoloPlay/Tools/SoloIndex.js
index b4aa58e2..736d7e4a 100644
--- a/libs/SoloPlay/Tools/SoloIndex.js
+++ b/libs/SoloPlay/Tools/SoloIndex.js
@@ -12,714 +12,803 @@
*/
const SoloIndex = {
- doneList: [],
- retryList: [],
- goldScripts: ["bishibosh", "tristram", "treehead", "countess", "lowerkurast"],
+ doneList: [],
+ retryList: [],
+ goldScripts: ["bishibosh", "tristram", "treehead", "countess", "lowerkurast"],
- // this controls the order
- scripts: [
- // Act 1
- "corpsefire", "mausoleum", "den", "bishibosh", "bloodraven", "tristram", "treehead",
- "countess", "smith", "pits", "jail", "boneash", "andariel", "a1chests", "cows",
- // Act 2
- "cube", "radament", "amulet", "summoner", "maggotlair", "tombs", "ancienttunnels", "staff", "duriel",
- // Act 3
- "lamessen", "templeruns", "lowerkurast", "eye", "heart", "brain", "travincal", "mephisto",
- // Act 4
- "izual", "hellforge", "river", "hephasto", "diablo",
- // Act 5
- "shenk", "savebarby", "anya", "pindle", "ancients", "baal", "a5chests",
- ],
+ // this controls the order
+ scripts: [
+ // Act 1
+ "corpsefire", "mausoleum", "den", "bishibosh", "bloodraven", "tristram", "treehead",
+ "countess", "smith", "pits", "jail", "boneash", "andariel", "a1chests", "cows",
+ // Act 2
+ "cube", "radament", "creepingfeature", "beetleburst", "amulet", "summoner",
+ "fireeye", "maggotlair", "tombs", "ancienttunnels", "staff", "duriel",
+ // Act 3
+ "lamessen", "templeruns", "lowerkurast", "eye", "heart", "brain", "travincal", "mephisto",
+ // Act 4
+ "izual", "hellforge", "river", "hephasto", "diablo",
+ // Act 5
+ "shenk", "savebarby", "anya", "pindle", "nith", "ancients", "baal", "a5chests",
+ ],
- index: {
- "corpsefire": {
- preReq: function () {
- return (me.den && me.hell);
- },
- skipIf: function () {
- return (me.druid && me.paladin);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return (!me.andariel || Check.brokeAf());
- }
- },
- "mausoleum": {
- preReq: function () {
- return false;
- },
- skipIf: function () {
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "den": {
- skipIf: function () {
- return me.den;
- },
- shouldRun: function () {
- if (this.skipIf()) return false;
- return true;
- }
- },
- "bishibosh": {
- preReq: function () {
- // return me.normal && me.charlvl < 6;
- return (me.charlvl > 10); // figure out if this would be good to run
- },
- skipIf: function () {
- return me.sorceress;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return Check.brokeAf();
- }
- },
- "bloodraven": {
- skipIf: function () {
- if (me.hell) {
- // too many light immunes - although come back to this cause maybe just kill raven
- return (["Lightning", "Trapsin", "Javazon"].includes(SetUp.currentBuild) || (me.amazon && SetUp.currentBuild !== SetUp.finalBuild));
- }
- return false;
- },
- shouldRun: function () {
- switch (me.diff) {
- case sdk.difficulty.Normal:
- return !me.bloodraven || (!me.summoner && Check.brokeAf()) || (!me.tristram && me.barbarian);
- case sdk.difficulty.Nightmare:
- return !me.bloodraven;
- case sdk.difficulty.Hell:
- return !this.skipIf();
- }
- return false;
- }
- },
- "tristram": {
- skipIf: function () {
- switch (me.classid) {
- case sdk.player.class.Paladin:
- return Pather.accessToAct(3) || Attack.auradin || me.checkItem({name: sdk.locale.items.Enigma}).have;
- case sdk.player.class.Barbarian:
- return Pather.accessToAct(3) || me.checkItem({name: sdk.locale.items.Lawbringer}).have;
- default:
- if (me.hell && me.charlvl > 72 && Pather.accessToAct(2)) return true;
- return false;
- }
- },
- shouldRun: function () {
- switch (true) {
- case (me.normal && (!me.tristram || me.charlvl < (me.classic || !me.barbarian ? 12 : 6) || Check.brokeAf())):
- case (me.nightmare && ((!me.tristram && me.charlvl < 43) || Check.brokeAf())):
- case (me.hell && ((!me.tristram && me.diffCompleted) || !this.skipIf())):
- return true;
- }
- return false;
- }
- },
- "treehead": {
- preReq: function () {
- return (me.hell && !Pather.accessToAct(3));
- },
- skipIf: function () {
- return !me.paladin || Attack.auradin || me.checkItem({name: sdk.locale.items.Enigma}).have;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "countess": {
- skipIf: function () {
- return (me.hell && (me.classic || (me.sorceress && !me.onFinalBuild && !me.diffCompleted)));
- },
- shouldRun: function () {
- if (this.skipIf()) return false;
- let needRunes = Check.runes();
- switch (true) {
- case (me.normal && (needRunes || Check.brokeAf())): // todo - better determination for low gold
- case (me.barbarian && me.hell && me.checkItem({name: sdk.locale.items.Lawbringer}).have):
- case (!me.normal && (Pather.canTeleport() || me.charlvl < 60)):
- return true;
- }
- return false;
- }
- },
- "smith": {
- preReq: function () {
- return me.charlvl > 8;
- },
- skipIf: function () {
- // todo - test leveling/experience potential
- return (!!Misc.checkQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete) || me.smith);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "pits": {
- preReq: function () {
- return me.hell;
- },
- skipIf: function () {
- switch (me.classid) {
- case sdk.player.class.Amazon:
- return SetUp.currentBuild !== SetUp.finalBuild || me.charlvl < 85;
- case sdk.player.class.Sorceress:
- return me.charlvl < 85;
- case sdk.player.class.Paladin:
- case sdk.player.class.Druid:
- return !Check.currentBuild().caster;
- }
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "jail": {
- preReq: function () {
- return (me.hell && me.amazon && !me.mephisto);
- },
- skipIf: function () {
- return (SetUp.currentBuild === SetUp.finalBuild);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "boneash": {
- preReq: function () {
- return true;
- },
- skipIf: function () {
- return (me.charlvl < 10);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case (me.charlvl < 12):
- case (Check.brokeAf()):
- case (me.classic && me.hell && !me.diablo):
- return true;
- }
- return false;
- }
- },
- "andariel": {
- skipIf: function () {
- if (me.charlvl < 12) return true;
- if (!me.andariel) return false;
- if (me.hell && me.amazon && SetUp.currentBuild !== SetUp.finalBuild) return true;
- return false;
- },
- shouldRun: function () {
- if (this.skipIf()) return false;
- switch (true) {
- case (!me.andariel):
- case (me.normal && Check.brokeAf()):
- case (me.classic && me.hell):
- case (!me.normal && (Pather.canTeleport() || me.charlvl < 60)):
- return true;
- }
- return false;
- }
- },
- "a1chests": {
- preReq: function () {
- return (!me.classic && !me.normal);
- },
- skipIf: function () {
- if (me.barbarian && (!me.hell || Pather.accessToAct(3)
- || (Item.getEquippedItem(sdk.body.LeftArm).tier > 1270
- || me.checkItem({name: sdk.locale.items.Lawbringer}).have))) {
- return true;
- }
-
- return (me.charlvl < 70 || !Pather.canTeleport());
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "cows": {
- preReq: function () {
- return !me.cows && me.diffCompleted;
- },
- skipIf: function () {
- if (me.normal && !Check.brokeAf()) return true;
- switch (me.classid) {
- case sdk.player.class.Barbarian:
- return ["Whirlwind", "Immortalwhirl", "Singer"].indexOf(SetUp.currentBuild) === -1;
- case sdk.player.class.Druid:
- return me.nightmare && me.charlvl > 65;
- case sdk.player.class.Sorceress:
- return me.nightmare && me.expansion && me.charlvl > 62;
- }
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "cube": {
- preReq: function () {
- return Pather.accessToAct(2);
- },
- skipIf: function () {
- return me.cube;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "radament": {
- preReq: function () {
- return Pather.accessToAct(2);
- },
- shouldRun: function () {
- if (!this.preReq()) return false;
- switch (true) {
- case (!me.radament):
- case (me.normal && Check.brokeAf()):
- case (me.hell && me.amazon && SetUp.currentBuild !== SetUp.finalBuild):
- case (me.hell && me.sorceress && me.classic && !me.diablo):
- return true;
- }
- return false;
- }
- },
- "staff": {
- preReq: function () {
- return Pather.accessToAct(2);
- },
- skipIf: function () {
- return (me.horadricstaff || me.shaft || me.completestaff);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "amulet": {
- preReq: function () {
- return Pather.accessToAct(2);
- },
- skipIf: function () {
- return (me.horadricstaff || me.amulet || me.completestaff);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "ancienttunnels": {
- preReq: function () {
- return (me.hell && Pather.accessToAct(2));
- },
- skipIf: function () {
- switch (me.classid) {
- case sdk.player.class.Amazon:
- return SetUp.currentBuild !== SetUp.finalBuild;
- default:
- return Attack.getSkillElement(Config.AttackSkill[3]) === "magic";
- }
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "beetleburst": {
- preReq: function () {
- return (Pather.accessToAct(2));
- },
- skipIf: function () {
- return (me.charlvl < 12 || me.charlvl > 20);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "summoner": {
- preReq: function () {
- return Pather.accessToAct(2);
- },
- skipIf: function () {
- return me.summoner;
- },
- shouldRun: function () {
- // does summoner have leveling potential?
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "maggotlair": {
- preReq: function () {
- return Pather.accessToAct(2) && Pather.canTeleport();
- },
- skipIf: function () {
- return (!me.normal || me.charlvl > 21);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "tombs": {
- preReq: function () {
- return Pather.accessToAct(2);
- },
- skipIf: function () {
- return (!me.normal || me.charlvl > 22);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "duriel": {
- preReq: function () {
- return Pather.accessToAct(2) && (me.horadricstaff || me.completestaff || (me.amulet && me.shaft));
- },
- skipIf: function () {
- return me.duriel;
- },
- shouldRun: function () {
- // does duriel have leveling potential?
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "eye": {
- preReq: function () {
- return Pather.accessToAct(3);
- },
- skipIf: function () {
- return (me.eye || me.khalimswill || me.travincal);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "templeruns": {
- preReq: function () {
- return Pather.accessToAct(3);
- },
- skipIf: function () {
- return ((me.paladin && Check.currentBuild().caster) || (me.hell && me.sorceress && me.charlvl < 90));
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case (me.normal && ((me.charlvl > 18 && me.charlvl < 25) || (me.charlvl >= 25 && !me.diffCompleted && Check.brokeAf()))):
- case (me.nightmare && me.charlvl < 50):
- case (me.hell && !me.classic && me.charlvl > 80):
- return true;
- }
- return false;
- }
- },
- "lamessen": {
- preReq: function () {
- return Pather.accessToAct(3);
- },
- skipIf: function () {
- return (me.lamessen);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "lowerkurast": {
- preReq: function () {
- return Pather.accessToAct(3);
- },
- skipIf: function () {
- return (!me.barbarian);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return (me.nightmare && me.charlvl >= 50 && !me.checkItem({name: sdk.locale.items.VoiceofReason}).have);
- }
- },
- "heart": {
- preReq: function () {
- return Pather.accessToAct(3);
- },
- skipIf: function () {
- return (me.heart || me.khalimswill || me.travincal);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "brain": {
- preReq: function () {
- return Pather.accessToAct(3);
- },
- skipIf: function () {
- return (me.brain || me.khalimswill || me.travincal);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "travincal": {
- preReq: function () {
- return Pather.accessToAct(3);
- },
- skipIf: function () {
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case !me.travincal:
- case (me.charlvl < 25 || (me.charlvl >= 25 && me.normal && !me.diffCompleted && Check.brokeAf())):
- case (me.nightmare && !me.diablo && me.barbarian && !me.checkItem({name: sdk.locale.items.Lawbringer}).have):
- case (me.hell && me.paladin && me.charlvl > 85 && (!Attack.auradin || !me.checkItem({name: sdk.locale.items.Enigma}).have)):
- return true;
- }
- return false;
- }
- },
- "mephisto": {
- preReq: function () {
- return (Pather.accessToAct(3) && me.travincal);
- },
- skipIf: function () {
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- const canTele = Pather.canTeleport();
- switch (true) {
- case !me.mephisto:
- case (me.normal && (Check.brokeAf() || ((canTele && !me.diablo) || !me.izual))):
- case (me.nightmare && (canTele || me.charlvl <= 65)):
- case (me.hell && (canTele || !me.hardcore)):
- return true;
- }
- return false;
- }
- },
- "izual": {
- preReq: function () {
- return Pather.accessToAct(4);
- },
- skipIf: function () {
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case !me.izual:
- case (me.normal && !me.diablo):
- return true;
- }
- return false;
- }
- },
- "river": {
- preReq: function () {
- const cLvl = me.charlvl;
- return (Pather.accessToAct(4) && ((me.normal && cLvl >= 24) || (me.nightmare && cLvl >= 40) || (me.hell && cLvl >= 80)));
- },
- skipIf: function () {
- return (me.diablo || me.normal);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case (me.barbarian && !me.checkItem({name: sdk.locale.items.Lawbringer}).have):
- case (me.sorceress && me.classic):
- return true;
- }
- return false;
- }
- },
- "hephasto": {
- preReq: function () {
- const cLvl = me.charlvl;
- return (Pather.accessToAct(4) && ((me.normal && cLvl >= 24) || (me.nightmare && cLvl >= 40) || (me.hell && cLvl >= 80)));
- },
- skipIf: function () {
- return (!me.barbarian || me.normal || me.diablo);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case (me.charlvl <= 70 && !me.checkItem({name: sdk.locale.items.Lawbringer}).have):
- return true;
- }
- return false;
- }
- },
- "hellforge": {
- preReq: function () {
- const cLvl = me.charlvl;
- return (Pather.accessToAct(4) && ((me.normal && cLvl >= 24) || (me.nightmare && cLvl >= 40) || (me.hell && cLvl >= 80)));
- },
- skipIf: function () {
- return (me.hellforge || me.getQuest(sdk.quest.id.HellsForge, sdk.quest.states.ReqComplete));
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "diablo": {
- preReq: function () {
- const cLvl = me.charlvl;
- return (Pather.accessToAct(4) && ((me.normal && cLvl >= 24) || (me.nightmare && cLvl >= 40) || (me.hell && cLvl >= 80)));
- },
- skipIf: function () {
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case !me.diablo:
- case (me.normal && me.classic):
- case (me.normal && me.expansion && (me.charlvl < 30 || !me.diffCompleted)):
- case (me.nightmare && (Pather.canTeleport() || me.charlvl <= 65)):
- case (me.hell):
- return true;
- }
- return false;
- }
- },
- "shenk": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5));
- },
- skipIf: function () {
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case !me.shenk:
- case (!me.druid || me.charlvl <= 70):
- return true;
- }
- return false;
- }
- },
- "savebarby": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5));
- },
- skipIf: function () {
- return me.savebarby;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- switch (true) {
- case Runewords.checkRune(sdk.items.runes.Tal, sdk.items.runes.Ral, sdk.items.runes.Ort):
- return true;
- }
- return false;
- }
- },
- "anya": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5));
- },
- skipIf: function () {
- return me.anya;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "pindle": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5) && me.anya);
- },
- skipIf: function () {
- return false;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "ancients": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5));
- },
- skipIf: function () {
- return me.ancients;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "baal": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5));
- },
- skipIf: function () {
- return !me.ancients;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "a5chests": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5) && me.baal);
- },
- skipIf: function () {
- return me.normal;
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "getkeys": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5) && me.hell);
- },
- skipIf: function () {
- return (["Zealer", "Smiter", "Uberconc"].indexOf(SetUp.currentBuild) === -1);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- "orgtorch": {
- preReq: function () {
- return (me.expansion && Pather.accessToAct(5) && me.hell);
- },
- skipIf: function () {
- return (["Zealer", "Smiter", "Uberconc"].indexOf(SetUp.currentBuild) === -1);
- },
- shouldRun: function () {
- if (!this.preReq() || this.skipIf()) return false;
- return true;
- }
- },
- }
+ index: {
+ "corpsefire": {
+ preReq: function () {
+ return (me.den && me.hell);
+ },
+ skipIf: function () {
+ return (me.druid && me.paladin);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return (!me.andariel || Check.brokeAf());
+ }
+ },
+ "mausoleum": {
+ preReq: function () {
+ return false;
+ },
+ skipIf: function () {
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "den": {
+ skipIf: function () {
+ /* xp is bad in den so only run it once we can blast through*/
+ return me.den || (me.charlvl > 8 && me.charlvl < (me.sorceress ? 18 : 12));
+ },
+ shouldRun: function () {
+ if (this.skipIf()) return false;
+ return true;
+ }
+ },
+ "bishibosh": {
+ preReq: function () {
+ // return me.normal && me.charlvl < 6;
+ return (me.charlvl > 10); // figure out if this would be good to run
+ },
+ skipIf: function () {
+ return me.sorceress;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return Check.brokeAf();
+ }
+ },
+ "bloodraven": {
+ skipIf: function () {
+ if (me.hell) {
+ // too many light immunes - although come back to this cause maybe just kill raven
+ return (
+ ["Lightning", "Trapsin", "Javazon"].includes(SetUp.currentBuild)
+ || (me.amazon && SetUp.currentBuild !== SetUp.finalBuild)
+ );
+ }
+ return false;
+ },
+ shouldRun: function () {
+ switch (me.diff) {
+ case sdk.difficulty.Normal:
+ return !me.bloodraven || (!me.summoner && Check.brokeAf()) || (!me.tristram && me.barbarian);
+ case sdk.difficulty.Nightmare:
+ return !me.bloodraven;
+ case sdk.difficulty.Hell:
+ return !this.skipIf();
+ }
+ return false;
+ }
+ },
+ "tristram": {
+ skipIf: function () {
+ if (me.charlvl < 6 && (getTickCount() - me.gamestarttime) > Time.minutes(15)) {
+ // re-run cave in the next game as underground passage can be tough
+ return true;
+ }
+ switch (me.classid) {
+ case sdk.player.class.Paladin:
+ return me.accessToAct(3) || Attack.auradin || me.checkItem({ name: sdk.locale.items.Enigma }).have;
+ case sdk.player.class.Barbarian:
+ return me.accessToAct(3) || me.checkItem({ name: sdk.locale.items.Lawbringer }).have;
+ default:
+ if (me.hell && me.charlvl > 72 && me.accessToAct(2)) return true;
+ return false;
+ }
+ },
+ shouldRun: function () {
+ switch (true) {
+ case (me.normal && (!me.tristram || me.charlvl < (me.classic || !me.barbarian ? 12 : 8) || Check.brokeAf())):
+ case (me.nightmare && ((!me.tristram && me.charlvl < 43) || Check.brokeAf())):
+ case (me.hell && ((!me.tristram && me.diffCompleted) || !this.skipIf())):
+ return true;
+ }
+ return false;
+ }
+ },
+ "treehead": {
+ preReq: function () {
+ return (me.hell && !me.accessToAct(3));
+ },
+ skipIf: function () {
+ return !me.paladin || Attack.auradin || me.checkItem({ name: sdk.locale.items.Enigma }).have;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "countess": {
+ skipIf: function () {
+ return (me.hell && (me.classic || (me.sorceress && !me.onFinalBuild && !me.diffCompleted)));
+ },
+ shouldRun: function () {
+ if (this.skipIf()) return false;
+ let needRunes = Check.runes();
+ switch (true) {
+ case (me.normal && (needRunes || Check.brokeAf())): // todo - better determination for low gold
+ case (me.barbarian && me.charlvl > 8 && me.charlvl < 12): // better for barb than trist runs
+ case (me.barbarian && me.hell && me.checkItem({ name: sdk.locale.items.Lawbringer }).have):
+ case (!me.normal && (Pather.canTeleport() || me.charlvl < 60)):
+ return true;
+ }
+ return false;
+ }
+ },
+ "smith": {
+ preReq: function () {
+ return me.charlvl > 6;
+ },
+ skipIf: function () {
+ // todo - test leveling/experience potential
+ return (!!Misc.checkQuest(sdk.quest.id.ToolsoftheTrade, sdk.quest.states.ReqComplete) || me.smith);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "pits": {
+ preReq: function () {
+ return me.hell;
+ },
+ skipIf: function () {
+ switch (me.classid) {
+ case sdk.player.class.Amazon:
+ return SetUp.currentBuild !== SetUp.finalBuild || me.charlvl < 85;
+ case sdk.player.class.Sorceress:
+ return me.charlvl < 85;
+ case sdk.player.class.Paladin:
+ case sdk.player.class.Druid:
+ return !Check.currentBuild().caster;
+ }
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "jail": {
+ preReq: function () {
+ return (me.hell && me.amazon && !me.mephisto);
+ },
+ skipIf: function () {
+ return (SetUp.currentBuild === SetUp.finalBuild);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "boneash": {
+ preReq: function () {
+ return true;
+ },
+ skipIf: function () {
+ return (me.charlvl < 10);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ switch (true) {
+ case (me.charlvl < 12):
+ case (Check.brokeAf()):
+ case (me.classic && me.hell && !me.diablo):
+ return true;
+ }
+ return false;
+ }
+ },
+ "andariel": {
+ skipIf: function () {
+ if (me.charlvl < 11) return true;
+ if (!me.andariel) return false;
+ // nith gives better xp/min at this point
+ if (me.nightmare && me.sorceress && me.anya) return true;
+ if (me.hell && me.amazon && SetUp.currentBuild !== SetUp.finalBuild) return true;
+ return false;
+ },
+ shouldRun: function () {
+ if (this.skipIf()) return false;
+ switch (true) {
+ case (!me.andariel):
+ case (me.normal && Check.brokeAf()):
+ case (me.classic && me.hell):
+ case (!me.normal && (Pather.canTeleport() || me.charlvl < 60)):
+ return true;
+ }
+ return false;
+ }
+ },
+ "a1chests": {
+ preReq: function () {
+ return (!me.classic && !me.normal);
+ },
+ skipIf: function () {
+ if (me.barbarian && (!me.hell || me.accessToAct(3)
+ || (me.equipped.get(sdk.body.LeftArm).tier > 1270
+ || me.checkItem({ name: sdk.locale.items.Lawbringer }).have))) {
+ return true;
+ }
+
+ return (me.charlvl < 70 || !Pather.canTeleport());
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "cows": {
+ preReq: function () {
+ return !me.cows && me.diffCompleted;
+ },
+ skipIf: function () {
+ if (me.normal && !Check.brokeAf()) return true;
+ switch (me.classid) {
+ case sdk.player.class.Barbarian:
+ return ["Whirlwind", "Immortalwhirl", "Singer"].indexOf(SetUp.currentBuild) === -1;
+ case sdk.player.class.Druid:
+ return me.nightmare && me.charlvl > 65;
+ case sdk.player.class.Sorceress:
+ return me.nightmare && me.expansion && me.charlvl > 50;
+ }
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "creepingfeature": {
+ preReq: function () {
+ return (me.accessToAct(2));
+ },
+ skipIf: function () {
+ return (me.charlvl < 12 || me.charlvl > 20);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "cube": {
+ preReq: function () {
+ return me.accessToAct(2);
+ },
+ skipIf: function () {
+ return me.cube || (me.sorceress && me.charlvl < 18/* Wait until we have tele*/);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "radament": {
+ preReq: function () {
+ return me.accessToAct(2);
+ },
+ shouldRun: function () {
+ if (!this.preReq()) return false;
+ switch (true) {
+ case (!me.radament):
+ case (me.normal && Check.brokeAf()):
+ case (me.hell && me.amazon && SetUp.currentBuild !== SetUp.finalBuild):
+ case (me.hell && me.sorceress && me.classic && !me.diablo):
+ return true;
+ }
+ return false;
+ }
+ },
+ "staff": {
+ preReq: function () {
+ return me.accessToAct(2);
+ },
+ skipIf: function () {
+ return (me.horadricstaff || me.shaft || me.completestaff);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "amulet": {
+ preReq: function () {
+ return me.accessToAct(2);
+ },
+ skipIf: function () {
+ return (me.horadricstaff || me.amulet || me.completestaff);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "ancienttunnels": {
+ preReq: function () {
+ return (me.hell && me.accessToAct(2));
+ },
+ skipIf: function () {
+ switch (me.classid) {
+ case sdk.player.class.Amazon:
+ return SetUp.currentBuild !== SetUp.finalBuild;
+ default:
+ return Attack.getSkillElement(Config.AttackSkill[3]) === "magic";
+ }
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "beetleburst": {
+ preReq: function () {
+ return (me.accessToAct(2));
+ },
+ skipIf: function () {
+ return (me.charlvl < 12 || me.charlvl > 20);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "fireeye": {
+ preReq: function () {
+ return me.accessToAct(2) && me.getQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.Completed) === 1;
+ },
+ skipIf: function () {
+ return SoloIndex.doneList.includes("summoner")
+ || (me.charlvl < 16 || me.charlvl > 23);
+ },
+ shouldRun: function () {
+ // does summoner have leveling potential?
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "summoner": {
+ preReq: function () {
+ return me.accessToAct(2) && me.getQuest(sdk.quest.id.TheTaintedSun, sdk.quest.states.Completed) === 1;
+ },
+ skipIf: function () {
+ return me.summoner;
+ },
+ shouldRun: function () {
+ // does summoner have leveling potential?
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "maggotlair": {
+ preReq: function () {
+ return me.accessToAct(2) && Pather.canTeleport();
+ },
+ skipIf: function () {
+ return (!me.normal || me.charlvl > 21);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "tombs": {
+ preReq: function () {
+ return me.accessToAct(2) && me.summoner;
+ },
+ skipIf: function () {
+ return (!me.normal || me.charlvl > 22);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "duriel": {
+ preReq: function () {
+ return me.accessToAct(2) && (me.horadricstaff || me.completestaff || (me.amulet && me.shaft));
+ },
+ skipIf: function () {
+ return me.duriel;
+ },
+ shouldRun: function () {
+ // does duriel have leveling potential?
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "eye": {
+ preReq: function () {
+ return me.accessToAct(3);
+ },
+ skipIf: function () {
+ return (me.eye || me.khalimswill || me.travincal);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "templeruns": {
+ preReq: function () {
+ return me.accessToAct(3);
+ },
+ skipIf: function () {
+ return ((me.paladin && Check.currentBuild().caster) || (me.hell && me.sorceress && me.charlvl < 90));
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ switch (true) {
+ case (me.normal && (
+ (me.charlvl > 18 && me.charlvl < 25)
+ || (me.charlvl >= 25 && !me.diffCompleted && Check.brokeAf())
+ )):
+ case (me.nightmare && me.charlvl < 50):
+ case (me.hell && !me.classic && me.charlvl > 80):
+ return true;
+ }
+ return false;
+ }
+ },
+ "lamessen": {
+ preReq: function () {
+ return me.accessToAct(3);
+ },
+ skipIf: function () {
+ return (me.lamessen);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "lowerkurast": {
+ preReq: function () {
+ return me.accessToAct(3);
+ },
+ skipIf: function () {
+ return (!me.barbarian && !me.sorceress);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ if (me.sorceress && me.hell && me.charlvl < 90) return true;
+ if (me.barbarian && me.nightmare && me.charlvl >= 50) {
+ return !me.checkItem({ name: sdk.locale.items.VoiceofReason }).have;
+ }
+ return false;
+ }
+ },
+ "heart": {
+ preReq: function () {
+ return me.accessToAct(3);
+ },
+ skipIf: function () {
+ return (me.heart || me.khalimswill || me.travincal);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "brain": {
+ preReq: function () {
+ return me.accessToAct(3);
+ },
+ skipIf: function () {
+ return (me.brain || me.khalimswill || me.travincal);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "travincal": {
+ preReq: function () {
+ return me.accessToAct(3);
+ },
+ skipIf: function () {
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ switch (true) {
+ case !me.travincal:
+ case (me.charlvl < 25 || (me.charlvl >= 25 && me.normal && !me.diffCompleted && Check.brokeAf())):
+ case (me.nightmare && !me.diablo && me.barbarian && !me.checkItem({ name: sdk.locale.items.Lawbringer }).have):
+ case (me.hell && me.paladin && me.charlvl > 85 && (
+ !Attack.auradin || !me.checkItem({ name: sdk.locale.items.Enigma }).have
+ )):
+ return true;
+ }
+ return false;
+ }
+ },
+ "mephisto": {
+ preReq: function () {
+ return (me.accessToAct(3) && me.travincal);
+ },
+ skipIf: function () {
+ if (!me.mephisto) return false;
+ // nith gives better xp/min at this point
+ if (me.nightmare && me.sorceress && me.anya) return true;
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ const canTele = Pather.canTeleport();
+ switch (true) {
+ case !me.mephisto:
+ case (me.normal && (Check.brokeAf() || ((canTele && !me.diablo) || !me.izual))):
+ case (me.nightmare && (canTele || me.charlvl <= 65)):
+ case (me.hell && (canTele || !me.hardcore)):
+ return true;
+ }
+ return false;
+ }
+ },
+ "izual": {
+ preReq: function () {
+ return me.accessToAct(4);
+ },
+ skipIf: function () {
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ switch (true) {
+ case !me.izual:
+ case (me.normal && !me.diablo):
+ return true;
+ }
+ return false;
+ }
+ },
+ "river": {
+ preReq: function () {
+ if (!me.accessToAct(4)) return false;
+ const cLvl = me.charlvl;
+ return (
+ (me.normal && cLvl >= 24)
+ || (me.nightmare && cLvl >= 40)
+ || (me.hell && cLvl >= 80)
+ );
+ },
+ skipIf: function () {
+ return (me.diablo || me.normal);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ switch (true) {
+ case (me.barbarian && !me.checkItem({ name: sdk.locale.items.Lawbringer }).have):
+ case (me.sorceress && me.classic):
+ return true;
+ }
+ return false;
+ }
+ },
+ "hephasto": {
+ preReq: function () {
+ if (!me.accessToAct(4)) return false;
+ const cLvl = me.charlvl;
+ return (
+ (me.normal && cLvl >= 24)
+ || (me.nightmare && cLvl >= 40)
+ || (me.hell && cLvl >= 80)
+ );
+ },
+ skipIf: function () {
+ return (!me.barbarian || me.normal || me.diablo);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ switch (true) {
+ case (me.charlvl <= 70 && !me.checkItem({ name: sdk.locale.items.Lawbringer }).have):
+ return true;
+ }
+ return false;
+ }
+ },
+ "hellforge": {
+ preReq: function () {
+ if (!me.accessToAct(4)) return false;
+ const cLvl = me.charlvl;
+ return (
+ (me.classic || me.anya)
+ && (
+ (me.normal && cLvl >= 24)
+ || (me.nightmare && cLvl >= 40)
+ || (me.hell && cLvl >= 80)
+ )
+ );
+ },
+ skipIf: function () {
+ return (me.hellforge || me.getQuest(sdk.quest.id.HellsForge, sdk.quest.states.ReqComplete));
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "diablo": {
+ preReq: function () {
+ if (!me.accessToAct(4)) return false;
+ const cLvl = me.charlvl;
+ return (
+ (me.normal && cLvl >= 24)
+ || (me.nightmare && cLvl >= 40)
+ || (me.hell && cLvl >= (me.sorceress ? 85 : 80))
+ );
+ },
+ skipIf: function () {
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ if (!me.diablo) return true;
+ if (me.normal) {
+ if (me.classic || me.charlvl < 30) return true;
+ return !me.diffCompleted && me.data.finalBuild !== "Bumper";
+ } else if (me.nightmare) {
+ return (Pather.canTeleport() || me.charlvl <= 65);
+ } else if (me.hell) {
+ return true;
+ }
+ return false;
+ }
+ },
+ "shenk": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5));
+ },
+ skipIf: function () {
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ switch (true) {
+ case !me.shenk:
+ case (!me.druid || me.charlvl <= 70):
+ return true;
+ }
+ return false;
+ }
+ },
+ "savebarby": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5));
+ },
+ skipIf: function () {
+ return me.savebarby;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ switch (true) {
+ case Runewords.checkRune(sdk.items.runes.Tal, sdk.items.runes.Ral, sdk.items.runes.Ort):
+ return true;
+ }
+ return false;
+ }
+ },
+ "anya": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5));
+ },
+ skipIf: function () {
+ return me.anya;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "pindle": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5) && me.anya);
+ },
+ skipIf: function () {
+ return false;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "nith": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5) && me.anya);
+ },
+ skipIf: function () {
+ if (!Pather.canTeleport()) return true;
+ if (me.normal && me.charlvl < 30) return true;
+ // for now only run in norm/nightmare
+ return me.hell;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "ancients": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5));
+ },
+ skipIf: function () {
+ return me.ancients;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "baal": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5));
+ },
+ skipIf: function () {
+ return !me.ancients;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "a5chests": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5) && me.baal);
+ },
+ skipIf: function () {
+ return me.normal;
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "getkeys": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5) && me.hell);
+ },
+ skipIf: function () {
+ return (["Zealer", "Smiter", "Uberconc"].indexOf(SetUp.currentBuild) === -1);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ "orgtorch": {
+ preReq: function () {
+ return (me.expansion && me.accessToAct(5) && me.hell);
+ },
+ skipIf: function () {
+ return (["Zealer", "Smiter", "Uberconc"].indexOf(SetUp.currentBuild) === -1);
+ },
+ shouldRun: function () {
+ if (!this.preReq() || this.skipIf()) return false;
+ return true;
+ }
+ },
+ }
};
diff --git a/libs/SoloPlay/Tools/Tracker.js b/libs/SoloPlay/Tools/Tracker.js
index b7d5fe41..27e7b50a 100644
--- a/libs/SoloPlay/Tools/Tracker.js
+++ b/libs/SoloPlay/Tools/Tracker.js
@@ -5,185 +5,262 @@
*
*/
+includeIfNotIncluded("core/experience.js");
includeIfNotIncluded("SoloPlay/Tools/Developer.js");
-includeIfNotIncluded("SoloPlay/Functions/PrototypeOverrides.js");
-includeIfNotIncluded("SoloPlay/Functions/MiscOverrides.js");
const Tracker = {
- GTPath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-GameTime.json",
- LPPath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-LevelingPerformance.csv",
- SPPath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-ScriptPerformance.csv",
- // Leveling Performance
- LPHeader: "Total Time,InGame Time,Split Time,Area,Character Level,Gained EXP,Gained EXP/Minute,Difficulty,Gold,Fire Resist,Cold Resist,Light Resist,Poison Resist,Current Build" + "\n",
- // Script Performance
- SPHeader: "Total Time,InGame Time,Sequence Time,Sequence,Character Level,Gained EXP,Gained EXP/Minute,EXP Gain %,Difficulty,Gold,Fire Resist,Cold Resist,Light Resist,Poison Resist,Current Build" + "\n",
- tick: 0,
- default: {
- "Total": 0,
- "InGame": 0,
- "OOG": 0,
- "LastLevel": 0,
- "LastSave": getTickCount()
- },
-
- initialize: function () {
- const GameTracker = Object.assign({}, this.default);
-
- // Create Files
- if (!FileTools.exists("libs/SoloPlay/Data/" + me.profile)) {
- let folder = dopen("libs/SoloPlay/Data");
- folder.create(me.profile);
- }
-
- !FileTools.exists(this.GTPath) && Developer.writeObj(GameTracker, this.GTPath);
- !FileTools.exists(this.LPPath) && Misc.fileAction(this.LPPath, 1, this.LPHeader);
- !FileTools.exists(this.SPPath) && Misc.fileAction(this.SPPath, 1, this.SPHeader);
-
- return true;
- },
-
- resetGameTime: function () {
- Developer.writeObj(Object.assign({}, this.default), this.GTPath);
- },
-
- reset: function () {
- this.resetGameTime();
- // for now just re-init the header so it's easier to look at the file and see where we restarted
- // might later save the files to a sub folder and re-init a new one
- FileTools.exists(this.LPPath) && Misc.fileAction(this.LPPath, 2, this.LPHeader);
- FileTools.exists(this.SPPath) && Misc.fileAction(this.SPPath, 2, this.SPHeader);
- },
-
- checkValidity: function () {
- const GameTracker = Developer.readObj(this.GTPath);
- let found = false;
- GameTracker && Object.keys(GameTracker).forEach(function (key) {
- if (GameTracker[key] < 0) {
- console.debug("Negative value found");
- GameTracker[key] = 0;
- found = true;
- }
- });
- found && Developer.writeObj(GameTracker, this.GTPath);
- },
-
- logLeveling: function (obj) {
- if (typeof obj === "object" && obj.hasOwnProperty("event") && obj.event === "level up") {
- Tracker.leveling();
- }
- },
-
- script: function (starttime, subscript, startexp) {
- const GameTracker = Developer.readObj(Tracker.GTPath);
-
- // GameTracker
- // this seems to happen when my pc restarts so set last save equal to current tick count and then continue
- GameTracker.LastSave > getTickCount() && (GameTracker.LastSave = getTickCount());
-
- const newTick = me.gamestarttime >= GameTracker.LastSave ? me.gamestarttime : GameTracker.LastSave;
- GameTracker.InGame += Developer.timer(newTick);
- GameTracker.Total += Developer.timer(newTick);
- GameTracker.LastSave = getTickCount();
- Developer.writeObj(GameTracker, Tracker.GTPath);
-
- // csv file
- const scriptTime = Developer.timer(starttime);
- const currLevel = me.charlvl;
- const diffString = sdk.difficulty.nameOf(me.diff);
- const gainAMT = me.getStat(sdk.stats.Experience) - startexp;
- const gainTime = gainAMT / (scriptTime / 60000);
- const gainPercent = currLevel === 99 ? 0 : (gainAMT * 100 / Experience.nextExp[currLevel]).toFixed(6);
- const currentBuild = SetUp.currentBuild;
- const [GOLD, FR, CR, LR, PR] = [me.gold, me.realFR, me.realCR, me.realLR, me.realPR];
- const string = (
- Developer.formatTime(GameTracker.Total) + "," + Developer.formatTime(GameTracker.InGame) + "," + Developer.formatTime(scriptTime)
- + "," + subscript + "," + currLevel + "," + gainAMT + "," + gainTime + "," + gainPercent + "," + diffString
- + "," + GOLD + "," + FR + "," + CR + "," + LR + "," + PR + "," + currentBuild + "\n"
- );
-
- Misc.fileAction(Tracker.SPPath, 2, string);
- this.tick = GameTracker.LastSave;
-
- return true;
- },
-
- leveling: function () {
- const GameTracker = Developer.readObj(this.GTPath);
-
- // GameTracker
- // this seems to happen when my pc restarts so set last save equal to current tick count and then continue
- GameTracker.LastSave > getTickCount() && (GameTracker.LastSave = getTickCount());
-
- const newSave = getTickCount();
- const newTick = me.gamestarttime > GameTracker.LastSave ? me.gamestarttime : GameTracker.LastSave;
- const splitTime = Developer.timer(GameTracker.LastLevel);
- GameTracker.InGame += Developer.timer(newTick);
- GameTracker.Total += Developer.timer(newTick);
- GameTracker.LastLevel = newSave;
- GameTracker.LastSave = newSave;
- Developer.writeObj(GameTracker, Tracker.GTPath);
-
- // csv file
- const diffString = sdk.difficulty.nameOf(me.diff);
- const areaName = Pather.getAreaName(me.area);
- const currentBuild = SetUp.currentBuild;
- const gainAMT = me.getStat(sdk.stats.Experience) - Experience.totalExp[me.charlvl - 1];
- const gainTime = gainAMT / (splitTime / 60000);
- const [GOLD, FR, CR, LR, PR] = [me.gold, me.realFR, me.realCR, me.realLR, me.realPR];
- const string = (
- Developer.formatTime(GameTracker.Total) + "," + Developer.formatTime(GameTracker.InGame) + "," + Developer.formatTime(splitTime) + ","
- + areaName + "," + me.charlvl + "," + gainAMT + "," + gainTime + "," + diffString + ","
- + GOLD + "," + FR + "," + CR + "," + LR + "," + PR + "," + currentBuild + "\n"
- );
-
- Misc.fileAction(Tracker.LPPath, 2, string);
- this.tick = GameTracker.LastSave;
-
- return true;
- },
-
- update: function (oogTick = 0) {
- let heartBeat = getScript("tools/heartbeat.js");
- if (!heartBeat) {
- console.debug("Couldn't find heartbeat");
- return false;
- }
- if (!me.ingame) {
- console.debug("Not in game");
- return false;
- }
-
- const GameTracker = Developer.readObj(this.GTPath);
-
- // this seems to happen when my pc restarts so set last save equal to current tick count and then continue
- GameTracker.LastSave > getTickCount() && (GameTracker.LastSave = getTickCount());
-
- // make sure we aren't attempting to use a corrupted file (only way we get negative values)
- const newTick = me.gamestarttime > GameTracker.LastSave ? me.gamestarttime : GameTracker.LastSave;
-
- GameTracker.OOG += oogTick;
- GameTracker.InGame += Developer.timer(newTick);
- GameTracker.Total += (Developer.timer(newTick) + oogTick);
- GameTracker.LastSave = getTickCount();
- Developer.writeObj(GameTracker, Tracker.GTPath);
- this.tick = GameTracker.LastSave;
-
- return true;
- }
-};
+ GTPath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-GameTime.json",
+ LPPath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-LevelingPerformance.csv",
+ SPPath: "libs/SoloPlay/Data/" + me.profile + "/" + me.profile + "-ScriptPerformance.csv",
+ // Leveling Performance
+ LPHeader: [
+ "Total Time",
+ "InGame",
+ "Split Time",
+ "Area",
+ "Charlevel",
+ "Gained EXP",
+ "EXP/Minute",
+ "Difficulty",
+ "Gold",
+ "Fire Resist",
+ "Cold Resist",
+ "Light Resist",
+ "Poison Resist",
+ "Current Build"
+ ].join(",") + "\n",
+ // Script Performance
+ SPHeader: [
+ "Total Time",
+ "InGame",
+ "Sequence",
+ "Script",
+ "Charlevel",
+ "Gained EXP",
+ "EXP/Minute",
+ "EXP Gain %",
+ "Difficulty",
+ "Gold",
+ "Fire Resist",
+ "Cold Resist",
+ "Light Resist",
+ "Poison Resist",
+ "Current Build"
+ ].join(",") + "\n",
+ tick: 0,
+ /**
+ * @typedef {Object} GameTracker
+ * @property {number} Total - Total time spent in game
+ * @property {number} InGame - Total time spent in game
+ * @property {number} OOG - Total time spent out of game
+ * @property {number} LastLevel - Time Last level reached
+ * @property {number} LastSave - Time Last save occurred
+ */
+ _default: {
+ "Total": 0,
+ "InGame": 0,
+ "OOG": 0,
+ "LastLevel": 0,
+ "LastSave": getTickCount()
+ },
+
+ initialize: function () {
+ const GameTracker = Object.assign({}, this._default);
+
+ // Create Files
+ if (!FileTools.exists("libs/SoloPlay/Data/" + me.profile)) {
+ let folder = dopen("libs/SoloPlay/Data");
+ folder.create(me.profile);
+ }
+
+ !FileTools.exists(this.GTPath) && Tracker.writeObj(GameTracker, this.GTPath);
+ !FileTools.exists(this.LPPath) && FileAction.write(this.LPPath, this.LPHeader);
+ !FileTools.exists(this.SPPath) && FileAction.write(this.SPPath, this.SPHeader);
+
+ return true;
+ },
+
+ /**
+ * @param {string} path
+ * @returns {GameTracker}
+ */
+ getObj: function (path) {
+ let obj, OBJstring = FileAction.read(path);
+
+ try {
+ obj = JSON.parse(OBJstring);
+ } catch (e) {
+ // If we failed, file might be corrupted, so create a new one
+ Misc.errorReport(e, "Tracker");
+ FileTools.remove(path);
+ Tracker.initialize();
+ OBJstring = FileAction.read(path);
+ obj = JSON.parse(OBJstring);
+ }
+
+ if (obj) {
+ return obj;
+ }
+
+ console.error("ÿc8Kolbot-SoloPlayÿc0: Failed to read Obj. (Tracker.getObj)");
+
+ return false;
+ },
+
+ /**
+ * @param {string} jsonPath
+ * @returns {GameTracker}
+ */
+ readObj: function (jsonPath) {
+ let obj = this.getObj(jsonPath);
+ return clone(obj);
+ },
+
+ writeObj: function (obj, path) {
+ let string;
+ try {
+ string = JSON.stringify(obj, null, 2);
+ // try to parse the string to ensure it converted correctly
+ JSON.parse(string);
+ // JSON.parse throws an error if it fails so if we are here now we are good
+ FileAction.write(path, string);
+ } catch (e) {
+ console.warn("Malformed JSON object");
+ console.error(e);
+ return false;
+ }
+
+ return true;
+ },
+
+ resetGameTime: function () {
+ Tracker.writeObj(Object.assign({}, this._default), this.GTPath);
+ },
+
+ reset: function () {
+ this.resetGameTime();
+ // for now just re-init the header so it's easier to look at the file and see where we restarted
+ // might later save the files to a sub folder and re-init a new one
+ FileTools.exists(this.LPPath) && FileAction.append(this.LPPath, this.LPHeader);
+ FileTools.exists(this.SPPath) && FileAction.append(this.SPPath, this.SPHeader);
+ },
+
+ checkValidity: function () {
+ const GameTracker = Tracker.readObj(this.GTPath);
+ let found = false;
+ GameTracker && Object.keys(GameTracker).forEach(function (key) {
+ if (GameTracker[key] < 0) {
+ console.debug("Negative value found");
+ GameTracker[key] = 0;
+ found = true;
+ }
+ });
+ found && Tracker.writeObj(GameTracker, this.GTPath);
+ },
+
+ totalDays: function (milliseconds) {
+ let days = Math.floor(milliseconds / 86.4e6).toFixed(0);
+ return days.toString().padStart(1, "0");
+ },
+
+ script: function (starttime, subscript, startexp) {
+ const GameTracker = Tracker.readObj(Tracker.GTPath);
-if (Developer.logPerformance && getScript(true).name.toString() === "libs\\soloplay\\soloplay.js") {
- const Worker = require("../../modules/Worker");
-
- Worker.runInBackground.intervalUpdate = function () {
- if (getTickCount() - Tracker.tick < 3 * 60000) return true;
- Tracker.tick = getTickCount();
- try {
- Tracker.update();
- } catch (e) {
- console.warn(e.message);
- }
-
- return true;
- };
-}
+ // GameTracker
+ // this seems to happen when my pc restarts so set last save equal to current tick count and then continue
+ GameTracker.LastSave > getTickCount() && (GameTracker.LastSave = getTickCount());
+
+ const newTick = me.gamestarttime >= GameTracker.LastSave ? me.gamestarttime : GameTracker.LastSave;
+ GameTracker.InGame += Time.elapsed(newTick);
+ GameTracker.Total += Time.elapsed(newTick);
+ GameTracker.LastSave = getTickCount();
+ Tracker.writeObj(GameTracker, Tracker.GTPath);
+
+ // csv file
+ const scriptTime = Time.elapsed(starttime);
+ const currLevel = me.charlvl;
+ const diffString = sdk.difficulty.nameOf(me.diff);
+ const gainAMT = me.getStat(sdk.stats.Experience) - startexp;
+ const gainTime = (gainAMT / (scriptTime / 60000)).toFixed(2);
+ const gainPercent = currLevel === 99
+ ? 0
+ : (gainAMT * 100 / Experience.nextExp[currLevel]).toFixed(2);
+ const currentBuild = SetUp.currentBuild;
+ const [GOLD, FR, CR, LR, PR] = [me.gold, me.realFR, me.realCR, me.realLR, me.realPR];
+ const string = (
+ Time.format(GameTracker.Total) + "," + Time.format(GameTracker.InGame) + "," + Time.format(scriptTime)
+ + "," + subscript + "," + currLevel + "," + (gainAMT).toFixed(2)
+ + "," + gainTime + "," + gainPercent + "," + diffString
+ + "," + GOLD + "," + FR + "," + CR + "," + LR + "," + PR + "," + currentBuild + "\n"
+ );
+
+ FileAction.append(Tracker.SPPath, string);
+ Tracker.tick = GameTracker.LastSave;
+
+ return true;
+ },
+
+ leveling: function () {
+ const GameTracker = Tracker.readObj(this.GTPath);
+
+ // GameTracker
+ // this seems to happen when my pc restarts so set last save equal to current tick count and then continue
+ GameTracker.LastSave > getTickCount() && (GameTracker.LastSave = getTickCount());
+
+ const newSave = getTickCount();
+ const newTick = me.gamestarttime > GameTracker.LastSave ? me.gamestarttime : GameTracker.LastSave;
+ const splitTime = Time.elapsed(GameTracker.LastLevel);
+ GameTracker.InGame += Time.elapsed(newTick);
+ GameTracker.Total += Time.elapsed(newTick);
+ GameTracker.LastLevel = newSave;
+ GameTracker.LastSave = newSave;
+ Tracker.writeObj(GameTracker, Tracker.GTPath);
+
+ // csv file
+ const diffString = sdk.difficulty.nameOf(me.diff);
+ const areaName = getAreaName(me.area);
+ const currentBuild = SetUp.currentBuild;
+ const gainAMT = me.getStat(sdk.stats.Experience) - Experience.totalExp[me.charlvl - 1];
+ const gainTime = gainAMT / (splitTime / 60000);
+ const [GOLD, FR, CR, LR, PR] = [me.gold, me.realFR, me.realCR, me.realLR, me.realPR];
+ const string = (
+ Time.format(GameTracker.Total) + "," + Time.format(GameTracker.InGame) + "," + Time.format(splitTime) + ","
+ + areaName + "," + me.charlvl + "," + gainAMT + "," + gainTime + "," + diffString + ","
+ + GOLD + "," + FR + "," + CR + "," + LR + "," + PR + "," + currentBuild + "\n"
+ );
+
+ FileAction.append(Tracker.LPPath, string);
+ Tracker.tick = GameTracker.LastSave;
+
+ return true;
+ },
+
+ update: function (oogTick = 0) {
+ let heartBeat = getScript("threads/heartbeat.js");
+ if (!heartBeat) {
+ console.debug("Couldn't find heartbeat");
+ return false;
+ }
+ if (!me.ingame) {
+ console.debug("Not in game");
+ return false;
+ }
+
+ const GameTracker = Tracker.readObj(this.GTPath);
+
+ // this seems to happen when my pc restarts so set last save equal to current tick count and then continue
+ GameTracker.LastSave > getTickCount() && (GameTracker.LastSave = getTickCount());
+
+ // make sure we aren't attempting to use a corrupted file (only way we get negative values)
+ const newTick = me.gamestarttime > GameTracker.LastSave ? me.gamestarttime : GameTracker.LastSave;
+
+ GameTracker.OOG += oogTick;
+ GameTracker.InGame += Time.elapsed(newTick);
+ GameTracker.Total += (Time.elapsed(newTick) + oogTick);
+ GameTracker.LastSave = getTickCount();
+ Tracker.writeObj(GameTracker, Tracker.GTPath);
+ Tracker.tick = GameTracker.LastSave;
+
+ return true;
+ }
+};
diff --git a/libs/SoloPlay/Utils/General.js b/libs/SoloPlay/Utils/General.js
new file mode 100644
index 00000000..0e0b055f
--- /dev/null
+++ b/libs/SoloPlay/Utils/General.js
@@ -0,0 +1,173 @@
+(function (module) {
+ // these builds are not possible to do on classic
+ const impossibleClassicBuilds = [
+ "Bumper", "Socketmule", "Witchyzon",
+ "Auradin", "Torchadin", "Immortalwhirl",
+ "Sancdreamer", "Faithbowzon", "Wfzon"
+ ];
+ // these builds are not possible to do without ladder runewords
+ const impossibleNonLadderBuilds = ["Auradin", "Sancdreamer", "Faithbowzon"];
+
+ // SoloPlay general gameplay items
+ const nipItems = {
+ General: [
+ "[name] == tomeoftownportal",
+ "[name] == tomeofidentify",
+ "[name] == gold # [gold] >= me.charlvl * 3 * me.diff",
+ "[name] == gold && [distance] < 5 # [gold] >= 1",
+ "(me.charlvl < 20 || me.gold < 500) && [name] == minorhealingpotion",
+ "(me.charlvl < 25 || me.gold < 2000) && [name] == lighthealingpotion",
+ "(me.charlvl < 29 || me.gold < 5000) && [name] == healingpotion",
+ "[name] == greaterhealingpotion",
+ "[name] == superhealingpotion",
+ "(me.charlvl < 20 || me.gold < 1000) && [name] == minormanapotion",
+ "[name] == lightmanapotion",
+ "[name] == manapotion",
+ "[name] == greatermanapotion",
+ "[name] == supermanapotion",
+ "[name] == rejuvenationpotion",
+ "[name] == fullrejuvenationpotion",
+ "[name] == scrolloftownportal # # [maxquantity] == 20",
+ "[name] == scrollofidentify # # [maxquantity] == 20",
+ "[name] == key # # [maxquantity] == 12",
+ ],
+
+ Quest: [
+ "[name] == mephisto'ssoulstone",
+ "[name] == hellforgehammer",
+ "[name] == scrollofinifuss",
+ "[name] == keytothecairnstones",
+ "[name] == bookofskill",
+ "[name] == horadriccube",
+ "[name] == shaftofthehoradricstaff",
+ "[name] == topofthehoradricstaff",
+ "[name] == horadricstaff",
+ "[name] == ajadefigurine",
+ "[name] == thegoldenbird",
+ "[name] == potionoflife",
+ "[name] == lamesen'stome",
+ "[name] == khalim'seye",
+ "[name] == khalim'sheart",
+ "[name] == khalim'sbrain",
+ "[name] == khalim'sflail",
+ "[name] == khalim'swill",
+ "[name] == scrollofresistance",
+ ],
+ };
+
+ /**
+ * @param {number} classid
+ * @param {number[]} socketWith
+ * @param {number[]} temp
+ * @param {boolean} useSocketQuest
+ * @param {(item?: ItemUnit) => boolean | void} condition
+ * @returns {{ classid: number, socketWith: number[], temp: number[], useSocketQuest: boolean, condition: (item?: ItemUnit) => boolean | void }}
+ */
+ const addSocketableObj = (classid, socketWith = [], temp = [], useSocketQuest = false, condition = () => {}) => ({
+ classid: classid,
+ socketWith: socketWith,
+ temp: temp,
+ useSocketQuest: useSocketQuest,
+ condition: condition
+ });
+ const basicSocketables = {
+ caster: [],
+ all: [],
+ };
+ // insight base
+ basicSocketables.all.push(addSocketableObj(sdk.items.Bill, [], [], true, (item) =>
+ me.nightmare && item.ilvl >= 26 && item.isBaseType && item.ethereal
+ ));
+ // insight base
+ basicSocketables.all
+ .push(addSocketableObj(sdk.items.ColossusVoulge, [], [], true, (item) =>
+ me.nightmare && item.ilvl >= 26 && item.isBaseType && item.ethereal
+ ));
+ // Crown of Ages
+ basicSocketables.caster
+ .push(addSocketableObj(sdk.items.Corona, [sdk.items.runes.Ber, sdk.items.runes.Um], [sdk.items.gems.Perfect.Ruby],
+ false, (item) => item.unique
+ ));
+ // Moser's
+ basicSocketables.caster
+ .push(addSocketableObj(sdk.items.RoundShield, [sdk.items.runes.Um], [sdk.items.gems.Perfect.Diamond],
+ false, (item) => item.unique && !item.ethereal && me.equipped.get(sdk.body.LeftArm).tier < NTIP.MAX_TIER
+ ));
+ // Spirit Forge
+ basicSocketables.caster
+ .push(addSocketableObj(sdk.items.LinkedMail, [sdk.items.runes.Shael], [sdk.items.gems.Perfect.Ruby],
+ false, (item) => item.unique && !item.ethereal
+ ));
+ // Dijjin Slayer
+ basicSocketables.caster
+ .push(addSocketableObj(sdk.items.Ataghan, [sdk.items.runes.Amn], [sdk.items.gems.Perfect.Skull],
+ false, (item) => !Check.currentBuild().caster && item.unique && !item.ethereal
+ ));
+ // Bone Hew - for merc
+ basicSocketables.caster
+ .push(addSocketableObj(sdk.items.OgreAxe,
+ [sdk.items.runes.Hel, sdk.items.runes.Amn], [sdk.items.gems.Perfect.Skull],
+ false, (item) => item.unique
+ ));
+ // spirit base
+ basicSocketables.caster
+ .push(addSocketableObj(sdk.items.BroadSword, [], [], true,
+ /** @param {ItemUnit} item */
+ function (item) {
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Sword,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+ return me.normal && !me.getOwned(wanted).length
+ && !me.checkItem({ name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Sword }).have
+ && item.ilvl >= 26 && item.isBaseType && !item.ethereal;
+ }
+ ));
+ // spirit base
+ basicSocketables.caster
+ .push(addSocketableObj(sdk.items.CrystalSword, [], [], true,
+ /** @param {ItemUnit} item */
+ function (item) {
+ /** @type {GetOwnedSettings} */
+ const wanted = {
+ itemType: sdk.items.type.Sword,
+ mode: sdk.items.mode.inStorage,
+ sockets: 4,
+ /** @param {ItemUnit} item */
+ cb: function (item) {
+ return item.isBaseType;
+ }
+ };
+ return me.normal && !me.getOwned(wanted).length
+ && !me.checkItem({ name: sdk.locale.items.Spirit, itemtype: sdk.items.type.Sword }).have
+ && item.ilvl >= 26 && item.ilvl <= 40 && item.isBaseType && !item.ethereal;
+ }
+ ));
+ // Lidless
+ basicSocketables.caster
+ .push(addSocketableObj(sdk.items.GrimShield,
+ [sdk.items.runes.Um], [sdk.items.gems.Perfect.Diamond], !me.hell,
+ (item) => item.unique && (item.isInStorage || (item.isEquipped && !item.isOnSwap)) && !item.ethereal
+ ));
+
+ const buildAutoBuildTempObj = (update = () => {}) => ({
+ SkillPoints: [-1],
+ StatPoints: [-1, -1, -1, -1, -1],
+ Update: update
+ });
+
+ module.exports = {
+ impossibleClassicBuilds: impossibleClassicBuilds,
+ impossibleNonLadderBuilds: impossibleNonLadderBuilds,
+ nipItems: nipItems,
+ basicSocketables: basicSocketables,
+ addSocketableObj: addSocketableObj,
+ buildAutoBuildTempObj: buildAutoBuildTempObj,
+ };
+})(module);
diff --git a/libs/SoloPlay/Utils/Init.js b/libs/SoloPlay/Utils/Init.js
new file mode 100644
index 00000000..c6a3f2ce
--- /dev/null
+++ b/libs/SoloPlay/Utils/Init.js
@@ -0,0 +1,81 @@
+/**
+* @filename Init.js
+* @author theBGuy
+* @desc Initialization process for main soloplay thread
+*
+*/
+
+(function () {
+ // Only load this in global scope
+ if (getScript(true).name.toLowerCase() === "libs\\soloplay\\soloplay.js") {
+ myPrint("start setup");
+ const { nipItems, impossibleClassicBuilds, impossibleNonLadderBuilds } = require("../Utils/General");
+ NTIP.buildList(nipItems.Quest, nipItems.General);
+
+ try {
+ if (impossibleClassicBuilds.includes(SetUp.finalBuild) && me.classic) {
+ throw new Error("Kolbot-SoloPlay: " + SetUp.finalBuild + " cannot be used in classic. Change the info tag or remake as an expansion character...Shutting down");
+ }
+
+ if (impossibleNonLadderBuilds.includes(SetUp.finalBuild) && !Developer.addLadderRW) {
+ throw new Error("Kolbot-SoloPlay: " + SetUp.finalBuild + " cannot be used in non-ladder as they require ladder runewords. Change the info tag or remake as an ladder character...Shutting down");
+ }
+ } catch (e) {
+ D2Bot.printToConsole(e, sdk.colors.D2Bot.Red);
+ FileTools.remove("data/" + me.profile + ".json");
+ FileTools.remove("libs/SoloPlay/Data/" + me.profile + ".GameTime" + ".json");
+ D2Bot.stop();
+ }
+
+ if (me.charlvl === 1) {
+ let buckler = me.getItem(sdk.items.Buckler);
+ !!buckler && buckler.isEquipped && buckler.drop();
+ }
+
+ Town.heal() && me.cancelUIFlags();
+ Check.checkSpecialCase();
+
+ // check if any of our currently equipped items are no longer usable - can happen after respec
+ me.getItemsEx()
+ .filter(item => item.isEquipped)
+ .forEach(function (item) {
+ if (me.getStat(sdk.stats.Strength) < item.strreq
+ || me.getStat(sdk.stats.Dexterity) < item.dexreq
+ || item.ethereal && item.isBroken) {
+ myPrint("No longer able to use: " + item.fname);
+ Item.removeItem(null, item);
+ } else if (sdk.quest.items.includes(item.classid)) {
+ myPrint("Removing Quest Item: " + item.fname);
+ Item.removeItem(null, item);
+ } else if (me.charlvl >= 16 && item.isOnSwap
+ && [
+ sdk.items.type.AmazonBow, sdk.items.type.Bow,
+ sdk.items.type.Crossbow, sdk.items.type.BowQuiver, sdk.items.type.CrossbowQuiver
+ ].includes(item.itemType)) {
+ myPrint("Removing old swap Item: " + item.fname);
+ try {
+ me.switchWeapons(sdk.player.slot.Secondary);
+ item.drop();
+ CharData.skillData.bow.resetBowData();
+ } finally {
+ me.switchWeapons(sdk.player.slot.Main);
+ }
+ }
+ });
+
+ me.getItemsEx()
+ .filter(item => (
+ item.isInInventory
+ && sdk.quest.items.includes(item.classid)
+ && item.classid !== sdk.quest.item.Cube
+ ))
+ .forEach(item => {
+ Quest.stashItem(item);
+ });
+
+ me.cancelUIFlags();
+ // initialize final charms if we have any
+ CharmEquip.init();
+ }
+ return true;
+})();
diff --git a/libs/SoloPlay/Workers/EventEmitter.js b/libs/SoloPlay/Workers/EventEmitter.js
new file mode 100644
index 00000000..30ab652b
--- /dev/null
+++ b/libs/SoloPlay/Workers/EventEmitter.js
@@ -0,0 +1,78 @@
+/**
+ * @filename EventEmitter.js
+ * @author theBGuy
+ * @credit jaenster
+ * @desc Global modifying UMD module to handle emitting events
+ *
+ */
+
+(function (factory) {
+ if (typeof module === "object" && typeof module.exports === "object") {
+ let v = factory(require, exports);
+ if (v !== undefined) module.exports = v;
+ } else if (typeof define === "function" && define.amd) {
+ define(["require", "exports", "../../modules/Worker", "../Modules/Events"], factory);
+ }
+})(function (require, exports) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+
+ const Worker = require("../../modules/Worker");
+ require("../Modules/Events");
+ const old = {
+ level: me.charlvl,
+ };
+ const gainedLevels = function () {
+ return me.charlvl - old.level;
+ };
+
+ let levelTimeout = getTickCount();
+
+ const _AutoBuild = new function () {
+ this.enabled = true;
+
+ this.run = function () {
+ if (!this.enabled) return;
+
+ try {
+ let levels = gainedLevels();
+
+ if (levels > 0 && (Config.AutoSkill.Enabled || Config.AutoStat.Enabled)) {
+ scriptBroadcast("toggleQuitlist");
+ AutoBuild.print("Level up detected (", old.level, "-->", me.charlvl, ")");
+ AutoSkill.init(Config.AutoSkill.Build, Config.AutoSkill.Save);
+ AutoStat.init(Config.AutoStat.Build, Config.AutoStat.Save, Config.AutoStat.BlockChance, Config.AutoStat.UseBulk);
+ scriptBroadcast({ event: "level up" });
+ AutoBuild.applyConfigUpdates(); // scriptBroadcast() won't trigger listener on this thread.
+
+ AutoBuild.print("Incrementing cached character level to", old.level + 1);
+ Tracker.leveling();
+
+ // prevLevel doesn't get set to me.charlvl because
+ // we may have gained multiple levels at once
+ old.level += 1;
+
+ scriptBroadcast("toggleQuitlist");
+ }
+ } catch (e) {
+ this.enabled = false;
+ console.error(e);
+ console.warn("Something broke! StackWalk :: ", e.stack);
+ }
+ };
+ };
+
+ // Start
+ Worker.runInBackground.EventWatcher = function () {
+ // AutoBuild
+ if (getTickCount() - levelTimeout > 1000) {
+ levelTimeout = getTickCount();
+ _AutoBuild.run();
+ }
+
+ return true;
+ };
+
+ AutoBuild.print("Loaded AutoBuild");
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Start AutoBuild");
+});
diff --git a/libs/SoloPlay/Workers/EventHandler.js b/libs/SoloPlay/Workers/EventHandler.js
new file mode 100644
index 00000000..4c0d22b4
--- /dev/null
+++ b/libs/SoloPlay/Workers/EventHandler.js
@@ -0,0 +1,119 @@
+/**
+* @filename EventHandler.js
+* @author theBGuy
+* @desc worker thread to handle in game events for Kolbot-SoloPlay
+*
+*/
+
+(function (module, require, Worker) {
+ // Only load this in global scope
+ if (getScript(true).name.toLowerCase() === "libs\\soloplay\\soloplay.js") {
+
+ let tickDelay = 0;
+ let [actions, profiles] = [[], []];
+
+ me.on("soloEvent", function msgEvent (msg) {
+ switch (msg) {
+ case "testing":
+ console.debug(msg, actions);
+
+ break;
+ case "finishDen":
+ case "dodge":
+ case "skip":
+ case "killdclone":
+ actions.push(msg);
+ console.debug(actions);
+
+ break;
+ }
+ });
+
+ me.on("processProfileEvent", function copyDataProcessing (id, info) {
+ console.debug(id, info);
+ // Torch
+ if (id === 55) {
+ let { profile, ladder, torchType } = JSON.parse(info);
+ console.log("Mesage recived for torch...processing");
+
+ if (profile !== me.profile && (me.hell || (me.nightmare && me.baal)) && me.ladder === ladder) {
+ if (torchType === me.classid && !me.findItem(604, 0, null, 7)) {
+ console.log("Sent Response");
+ SoloEvents.sendToProfile(profile, { profile: me.profile, level: me.charlvl, event: 604 });
+ }
+ }
+
+ return;
+ }
+
+ // Annhilus
+ if (id === 60) {
+ let { profile, ladder } = JSON.parse(info);
+ console.log("Mesage recived for Annhilus...processing");
+
+ if (profile !== me.profile && (me.hell || (me.nightmare && me.baal)) && me.ladder === ladder) {
+ if (!me.findItem(603, 0, null, 7)) {
+ console.log("Sent Response");
+ SoloEvents.sendToProfile(profile, { profile: me.profile, level: me.charlvl, event: 603 });
+ }
+ }
+
+ return;
+ }
+
+ if (id === 65) {
+ let { profile, level, event } = JSON.parse(info);
+
+ console.log("Sucess: profile that contacted me: " + profile + " level of char: " + level);
+ SoloEvents.profileResponded = true;
+ profiles.push({ profile: profile, level: level, event: event });
+ tickDelay += 1000;
+ }
+ });
+
+ let waitTick = getTickCount();
+
+ // Start
+ Worker.runInBackground.EventWorker = function () {
+ if (getTickCount() - waitTick < 100 || SoloEvents.townChicken.running) return true;
+ waitTick = getTickCount();
+
+ try {
+ while (actions.length) {
+ let wasDisabled = SoloEvents.townChicken.disabled;
+ try {
+ SoloEvents[actions.shift()]();
+ } catch (e) {
+ console.warn(e);
+ } finally {
+ // if we disabled townchicken, re-enable it
+ if (!wasDisabled && SoloEvents.townChicken.disabled) {
+ SoloEvents.townChicken.disabled = false;
+ }
+ }
+ }
+
+ if (profiles.length > 0) {
+ let tick = getTickCount();
+
+ while (getTickCount() - tick < tickDelay) {
+ delay(500);
+ }
+
+ let lowestLevelProf = profiles.sort((a, b) => a.level - b.level).first();
+
+ SoloEvents.sendToProfile(lowestLevelProf.profile, lowestLevelProf.event, 70);
+ D2Bot.joinMe(lowestLevelProf.profile, me.gamename.toLowerCase(), "", me.gamepassword.toLowerCase(), true);
+ profiles = [];
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ return true;
+ };
+
+ // should there be a heartbeat for the workers?
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Start EventHandler");
+ }
+})(module, require, typeof Worker === "object" && Worker || require("../../modules/Worker"));
diff --git a/libs/SoloPlay/Workers/TownChicken.js b/libs/SoloPlay/Workers/TownChicken.js
new file mode 100644
index 00000000..323f2aff
--- /dev/null
+++ b/libs/SoloPlay/Workers/TownChicken.js
@@ -0,0 +1,395 @@
+/**
+* @filename TownChicken.js
+* @author theBGuy
+* @desc TownChicken background worker thread
+*
+*/
+
+/**
+ * @todo
+ * - figure out how to deal with loss of reference to whatever it was we might of been targetting beforehand.
+ * - How many chickens is too many for a script? How to end a script it that amount is reached.
+ */
+(function (module, require, Worker) {
+ // Only load this in global scope
+ if (getScript(true).name.toLowerCase() === "libs\\soloplay\\soloplay.js") {
+ const getNearestMonster = function () {
+ let gid = null;
+ let monster = Game.getMonster();
+ let range = 30;
+
+ if (monster) {
+ do {
+ if (monster.attackable && !monster.getParent()) {
+ let distance = getDistance(me, monster);
+
+ if (distance < range) {
+ [range, gid] = [distance, monster.gid];
+ }
+ }
+ } while (monster.getNext());
+ }
+
+ gid && (Game.getMonster(-1, -1, gid));
+
+ if (monster) {
+ console.log("ÿc9TownChickenÿc0 :: Closest monster to me: " + monster.name + " | Monster classid: " + monster.classid);
+ return monster.classid;
+ }
+
+ return -1;
+ };
+
+ 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");
+
+ let 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) {
+ portal.distance > 21 && (me.inTown && me.act === 5 ? Town.move("portalspot") : Pather.moveNearUnit(portal, 20));
+ if (Packet.telekinesis(portal)
+ && Misc.poll(() => targetArea ? me.inArea(targetArea) : me.area !== preArea)) {
+ Pather.lastPortalTick = getTickCount();
+ delay(100);
+
+ return true;
+ }
+ } else {
+ 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(() => 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);
+ };
+
+ 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((p) => 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(p => 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;
+ };
+
+ const goToTown = function (act = 0, wpmenu = false) {
+ if (!me.inTown) {
+ const townArea = sdk.areas.townOf(me.act);
+ try {
+ !makePortal(true) && console.warn("Town.goToTown: Failed to make TP");
+ if (!me.inTown && !usePortal(townArea, me.name)) {
+ console.warn("Town.goToTown: Failed to take TP");
+ if (!me.inTown && !usePortal(sdk.areas.townOf(me.area))) throw new Error("Town.goToTown: Failed to take TP");
+ }
+ } catch (e) {
+ let tpTool = me.getTpTool();
+ if (!tpTool && Misc.getPlayerCount() <= 1) {
+ Misc.errorReport(new Error("Town.goToTown: Failed to go to town and no tps available. Restart."));
+ scriptBroadcast("quit");
+ } else {
+ if (!Misc.poll(() => {
+ 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;
+ };
+
+ 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();
+ } catch (e) {
+ return false;
+ }
+
+ const { x, y } = me;
+
+ Town.doChores();
+
+ console.debug("Current act: " + me.act + " Prev Act: " + preAct);
+ me.act !== preAct && goToTown(preAct);
+ Town.move("portalspot");
+ Pather.moveTo(x, y);
+
+ while (getTickCount() - tick < 4500) {
+ delay(10);
+ }
+
+ if (!usePortal(preArea, me.name)) {
+ try {
+ usePortal(null, me.name);
+ } catch (e) {
+ throw new Error("Town.visitTown: Failed to go back from town");
+ }
+ }
+
+ console.log("ÿc8End ÿc0:: ÿc8visitTown - currentArea: " + getAreaName(me.area));
+
+ return me.area === preArea;
+ };
+
+ let [townCheck] = [false, false];
+
+ me.on("townChicken", function townChickenEvent (msg) {
+ if (SoloEvents.townChicken.disabled) return;
+ if (typeof msg !== "string") return;
+ switch (msg) {
+ case "townCheck":
+ switch (me.area) {
+ case sdk.areas.ArreatSummit:
+ case sdk.areas.UberTristram:
+ console.warn("Don't tp from " + getAreaName(me.area));
+ return;
+ default:
+ console.log("townCheck message recieved. First check passed.");
+ townCheck = true;
+
+ return;
+ }
+ case "quit":
+ //quitFlag = true;
+ // Maybe stop townChicken thread? Would that keep us from the crash that happens when we try to leave game while townChickening
+ break;
+ default:
+ break;
+ }
+ });
+
+ Misc.townCheck = function () {
+ return false;
+ };
+
+ // const lastChickens = [];
+ const useHowl = Skill.canUse(sdk.skills.Howl);
+ const useTerror = Skill.canUse(sdk.skills.Terror);
+
+ Config.DebugMode.Stack = true;
+ let waitTick = getTickCount();
+ let potTick = getTickCount();
+ let _recursion = false;
+
+ // Start
+ Worker.runInBackground.TownChicken = function () {
+ if (getTickCount() - waitTick < 100 || SoloEvents.townChicken.disabled) 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 && Town.needKeys())) {
+ shouldChicken = true;
+ }
+ }
+
+ if (shouldChicken) {
+ let t4 = getTickCount();
+ try {
+ _recursion = true;
+ myPrint("ÿc8TownChicken :: ÿc0Going to town. Initial Gold :: " + me.gold);
+ [Attack.stopClear, SoloEvents.townChicken.running] = [true, true];
+
+ // determine if this is really worth it
+ if (useHowl || useTerror) {
+ if ([156, 211, 242, 243, 544, 571, 345].indexOf(getNearestMonster()) === -1) {
+ if (useHowl && Skill.getManaCost(130) < me.mp) {
+ Skill.cast(130, sdk.skills.hand.Right);
+ }
+
+ if (useTerror && Skill.getManaCost(77) < me.mp) {
+ Skill.cast(77, sdk.skills.hand.Right, getNearestMonster());
+ }
+ }
+ }
+
+ visitTown();
+ } catch (e) {
+ Misc.errorReport(e, "TownChicken.js");
+ scriptBroadcast("quit");
+
+ return false;
+ } finally {
+ _recursion = false;
+ Packet.flash(me.gid, 100);
+ console.log("ÿc8TownChicken :: Took: " + Time.format(getTickCount() - t4) + " to visit town. Ending Gold :: " + me.gold);
+ [Attack.stopClear, SoloEvents.townChicken.running, townCheck] = [false, false, false];
+ }
+ }
+
+ return true;
+ };
+
+ console.log("ÿc8Kolbot-SoloPlayÿc0: Start TownChicken");
+ }
+})(module, require, typeof Worker === "object" && Worker || require("../../modules/Worker"));
diff --git a/libs/SoloPlay/globals.d.ts b/libs/SoloPlay/globals.d.ts
new file mode 100644
index 00000000..5729895a
--- /dev/null
+++ b/libs/SoloPlay/globals.d.ts
@@ -0,0 +1,720 @@
+// @ts-nocheck
+declare global {
+ interface Math {
+ percentDifference(value1: number, value2: number): number;
+ }
+
+ interface Object {
+ mobCount(givenSettings?: {
+ range?: number,
+ coll?: number,
+ type: number,
+ ignoreClassids: number[],
+ }): number;
+ }
+
+ interface ItemUnit {
+ readonly isCharm: boolean;
+ readonly isGem: boolean;
+ readonly isInsertable: boolean;
+ readonly isRuneword: boolean;
+ readonly isBroken: boolean;
+ readonly isBaseType: boolean;
+ readonly upgradedStrReq: boolean;
+ readonly upgradedDexReq: boolean;
+ readonly upgradedLvlReq: boolean;
+ readonly allRes: boolean;
+ readonly quantityPercent: number;
+
+ getItemType(): string;
+ }
+
+ interface Monster {
+ readonly isStunned: boolean;
+ readonly isUnderCoS: boolean;
+ readonly isUnderLowerRes: boolean;
+ }
+
+ interface Unit {
+ getResPenalty(difficulty: number): number;
+ castChargedSkillEx(...args: any[]): boolean;
+ castSwitchChargedSkill(...args: any[]): boolean;
+ haveRunes(itemInfo: number[]): boolean;
+ }
+
+ type MercObj = {
+ classid: number,
+ skill: number,
+ skillName: string,
+ act: number,
+ difficulty: number,
+ };
+
+ interface Build {
+ caster: boolean;
+ skillstab: number;
+ wantedskills: number[];
+ usefulskills: number[];
+ precastSkills: number[];
+ wantedMerc: MercObj;
+ stats: Array<[string, number | "block" | "all"]>;
+ skills: Array<[number, number, boolean?]>;
+ charms: Record boolean;
+ }>;
+ AutoBuildTemplate: Record void }>;
+ respec: () => boolean;
+ active: () => boolean;
+ }
+
+ interface MyData {
+ initialized: boolean;
+ normal: {
+ respecUsed: boolean;
+ imbueUsed: boolean;
+ socketUsed: boolean;
+ };
+ nightmare: {
+ respecUsed: boolean;
+ imbueUsed: boolean;
+ socketUsed: boolean;
+ };
+ hell: {
+ respecUsed: boolean;
+ imbueUsed: boolean;
+ socketUsed: boolean;
+ };
+ task: string;
+ startTime: number;
+ charName: string;
+ classid: number;
+ level: number;
+ strength: number;
+ dexterity: number;
+ currentBuild: string;
+ finalBuild: string;
+ highestDifficulty: string;
+ setDifficulty: string;
+ charms: Record boolean; }>;
+ charmGids: number[];
+ merc: {
+ act: number;
+ classid: number;
+ difficulty: number;
+ strength: number;
+ dexterity: number;
+ skill: number;
+ skillName: string;
+ gear: number[];
+ };
+ }
+
+ interface EquippedItem extends ItemUnit {
+ location: number;
+ durability: number;
+ tier: number;
+ tierScore: number;
+ secondaryTier: number;
+ socketed: boolean;
+ twoHandedCheck: (strict?: boolean) => boolean;
+ }
+
+ type EquippedMap = Map;
+
+ type GetOwnedSettings = {
+ itemType?: number,
+ classid?: number,
+ mode?: number,
+ quality?: number,
+ sockets?: number,
+ location?: number,
+ ethereal?: boolean,
+ cb?: (item: ItemUnit) => boolean,
+ };
+
+ interface MeType {
+ readonly maxNearMonsters: number;
+ readonly dualWielding: boolean;
+ readonly realFR: number;
+ readonly realCR: number;
+ readonly realPR: number;
+ readonly realLR: number;
+ readonly FR: number;
+ readonly CR: number;
+ readonly LR: number;
+ readonly PR: number;
+ readonly onFinalBuild: boolean;
+ readonly trueStr: number;
+ readonly trueDex: number;
+
+ finalBuild: Build;
+ currentBuild: Build;
+ data: MyData;
+ equipped: {
+ get: (bodylocation: number) => EquippedItem | undefined;
+ has: (bodylocation: number) => boolean;
+ set: (bodylocation: number, item: ItemUnit) => void;
+ init: () => void;
+ };
+
+ switchToPrimary(): boolean;
+ switchToSecondary(): boolean;
+ canTpToTown(): boolean;
+ getMercEx(): MercUnit | null;
+ getEquippedItem(bodyLoc: number): ItemUnit | null;
+ getSkillTabs(classid: number): number[];
+ inDanger(checkLoc?: {x: number, y: number} | MeType, range?: number): boolean;
+ checkSkill(skillId: number, subId: number): boolean;
+ cleanUpInvoPotions(beltSize: number): boolean;
+ needPotions(): boolean;
+ needBeltPots(): boolean;
+ needBufferPots(): boolean;
+ getIdTool(): ItemUnit | null;
+ getTpTool(): ItemUnit | null;
+ getUnids(): ItemUnit[];
+ fieldID(): boolean;
+ getWeaponQuantity(weaponLoc: number): number;
+ getItemsForRepair(repairPercent: number, chargedItems?: boolean): ItemUnit[];
+ needRepair(): string[];
+ needMerc(): boolean;
+ clearBelt(): boolean;
+ sortInventory(): boolean;
+ cleanUpScrolls(tome: ItemUnit, scrollId: number): number;
+ update(): void;
+ getOwned(itemInfo: ItemUnit | GetOwnedSettings): ItemUnit[];
+ }
+
+ interface Container {
+ /**
+ * A function that checks if the cube is located at { x: 0, y: 0 } in the stash and moves it there if not
+ * @param name
+ */
+ CubeSpot(name: string): boolean;
+
+ /**
+ * A function that sorts items with optional priority
+ * @param itemIdsLeft
+ * @param itemIdsRight
+ */
+ SortItems(itemIdsLeft: number[], itemIdsRight: number[]): boolean;
+
+ /**
+ * A function that moves an item to a location in a container
+ * @param item
+ * @param reverseX
+ * @param reverseY
+ * @param priorityClassIds
+ */
+ MoveTo(item: ItemUnit, reverseX: boolean, reverseY: boolean, priorityClassIds: number[]): boolean
+
+ /**
+ * @param item
+ * @param location
+ * @param force
+ */
+ MakeSpot(item: ItemUnit, location: { x: number, y: number }, force: boolean): boolean;
+
+ /**
+ * @param item
+ * @param mX
+ * @param mY
+ */
+ MoveToSpot(item: ItemUnit, mX: number, mY: number): boolean;
+ }
+
+ class Merc {
+ constructor(classid: number, skill: number, act: number, difficulty?: number);
+ classid: number;
+ skill: number;
+ skillName: string;
+ act: number;
+ difficulty: number;
+ }
+
+ class MercData {
+ [sdk.skills.FireArrow]: Merc;
+ [sdk.skills.ColdArrow]: Merc;
+ [sdk.skills.Prayer]: Merc;
+ [sdk.skills.BlessedAim]: Merc;
+ [sdk.skills.Defiance]: Merc;
+ [sdk.skills.HolyFreeze]: Merc;
+ [sdk.skills.Might]: Merc;
+ [sdk.skills.Thorns]: Merc;
+ [sdk.skills.IceBlast]: Merc;
+ [sdk.skills.FireBall]: Merc;
+ [sdk.skills.Lightning]: Merc;
+ [sdk.skills.Bash]: Merc;
+ actMap: Map;
+ }
+
+ namespace Mercenary {
+ let minCost: number;
+
+ function getMercSkill(merc?: MercUnit): string | false;
+ function getMercDifficulty(merc?: MercUnit): number;
+ function getMercAct(merc?: MercUnit): number;
+ function getMercInfo(merc?: MercUnit): { classid: number, act: number, difficulty: number, type: string | false };
+ function checkMercSkill(wanted: string, merc?: MercUnit): boolean;
+ function hireMerc(): boolean;
+ }
+
+ namespace Misc {
+ let townEnabled: boolean;
+ let openChestsEnabled: boolean;
+ const shrineStates: number[];
+
+ function openChestsInArea(area: number, chestIds: number[], sort?: Function): boolean;
+ function getExpShrine(shrineLocs: number[]): boolean;
+ function unsocketItem(item: ItemUnit): boolean;
+ function checkItemsForSocketing(): ItemUnit | boolean;
+ function checkItemsForImbueing(): ItemUnit | boolean;
+ function addSocketablesToItem(item: ItemUnit, runes: ItemUnit[]): boolean;
+ function getSocketables(
+ item: ItemUnit,
+ itemInfo?: {
+ classid: number,
+ socketWith: number[],
+ temp: number[],
+ useSocketQuest: boolean,
+ condition: Function
+ }
+ ): boolean;
+ function checkSocketables(): void;
+ }
+
+ namespace Skill {
+ function switchCast(
+ skillId: number,
+ givenSettings: { hand?: number, x?: number, y?: number, switchBack?: boolean, oSkill?: boolean }
+ ): boolean;
+ }
+
+ type pathSettings = {
+ allowNodeActions?: boolean;
+ allowTeleport?: boolean;
+ allowClearing?: boolean;
+ allowTown?: boolean;
+ allowPicking?: boolean;
+ minDist?: number;
+ retry?: number;
+ pop?: boolean;
+ returnSpotOnError?: boolean;
+ callback?: Function;
+ clearSettings?: clearSettings;
+ };
+ type clearSettings = {
+ clearPath?: boolean;
+ range?: number;
+ specType?: number;
+ sort?: Function;
+ };
+
+ namespace Pather {
+ let initialized: boolean;
+ function canTeleport(): boolean;
+ function teleUsingCharges(x: number, y: number, maxRange: number): boolean;
+ function changeAct(act: number): boolean;
+ function checkWP(area: number, keepMenuOpen?: boolean): boolean;
+ function clearToExit(currentarea: number, targetarea: number, givenSettings: pathSettings): boolean;
+ }
+
+ namespace NodeAction {
+ const shrinesToIgnore: number[];
+ let enabled: boolean;
+
+ function go(arg: clearSettings): void;
+ }
+
+ namespace PathDebug {
+ function coordsInPath(path: PathNode[], x: number, y: number): boolean;
+ }
+
+ namespace Pickit {
+ function pickItem(
+ unit: ItemUnit,
+ status: PickitResult,
+ keptLine?: string,
+ givenSettings?: { allowClear: boolean, allowMove: boolean }
+ ): boolean;
+ }
+
+ namespace Attack {
+ function clearPos(
+ x: number,
+ y: number,
+ range?: number,
+ pickit?: boolean,
+ cb?: function(): boolean,
+ ): boolean;
+ function killTarget(name: Monster | string | number): boolean;
+ }
+
+ namespace ClassAttack {
+ function doAttack(unit: Monster): AttackResult;
+ function doAttack(unit: Monster, precast?: boolean): AttackResult;
+ function doAttack(unit: Monster, recheck?: boolean): AttackResult;
+ function doAttack(unit: Monster, precast?: boolean, once?: boolean): AttackResult;
+ function doCast(unit: Monster, timedSkill: number, untimedSkill: number): AttackResult;
+ function doCast(
+ unit: Monster,
+ choosenSkill: { have: boolean, skill: number, range: number, mana: number, timed: boolean }
+ ): AttackResult;
+ function afterAttack(pickit?: boolean): void;
+ }
+
+ namespace CollMap {
+ function checkColl(unitA: Unit, unitB: Unit, coll: number, thickness: number): boolean;
+ }
+
+ namespace Town {
+ function doChores(repair?: boolean, givenTasks?: extraTasks): boolean;
+ }
+
+ namespace Precast {
+ function checkCTA(): boolean;
+ }
+
+ namespace CharData {
+ const filePath: string;
+ const threads: string[];
+
+ namespace login {
+ function create(): any;
+ function getObj(): any;
+ function getStats(): any;
+ function updateData(arg: string, property: object | string, value: any): boolean;
+ }
+
+ // ignoring the sub objs for now
+ function updateConfig(): void;
+ function create(): MyData;
+ function getObj(): MyData;
+ function getStats(): MyData;
+ function updateData(arg: string, property: object | string, value: any): boolean;
+ /** @alias CharData.delete */
+ function _delete(deleteMain: boolean): boolean;
+ }
+
+ namespace Developer {
+ const plugyMode: boolean;
+ const logPerformance: boolean;
+ const overlay: boolean;
+ const displayClockInConsole: boolean;
+ const logEquipped: boolean;
+ const hideChickens: boolean;
+ const addLadderRW: boolean;
+ const forcePacketCasting: {
+ enabled: boolean;
+ excludeProfiles: string[];
+ };
+ const fillAccount: {
+ bumpers: boolean;
+ socketMules: boolean;
+ imbueMule: boolean;
+ };
+ const imbueStopLevel: number;
+ const stopAtLevel: {
+ enabled: boolean;
+ profiles: Array<[string, number]>;
+ };
+ const developerMode: {
+ enabled: boolean;
+ profiles: string[];
+ };
+ const testingMode: {
+ enabled: boolean;
+ profiles: string[];
+ };
+ const setEmail: {
+ enabled: boolean;
+ profiles: string[];
+ realms: string[];
+ };
+ const debugging: {
+ smallCharm: boolean;
+ largeCharm: boolean;
+ grandCharm: boolean;
+ baseCheck: boolean;
+ junkCheck: boolean;
+ autoEquip: boolean;
+ crafting: boolean;
+ pathing: boolean;
+ skills: boolean;
+ showStack: {
+ enabled: boolean;
+ profiles: string[];
+ };
+ };
+ }
+
+ namespace Tracker {
+ const GTPath: string;
+ const LPPath: string;
+ const SPPath: string;
+ const LPHeader: string;
+ const SPHeader: string;
+ const tick: number;
+ interface GameTracker {
+ Total: number;
+ InGame: number;
+ OOG: number;
+ LastLevel: number;
+ LastSave: number;
+ }
+ const _default: GameTracker;
+ function initialize(): boolean;
+ function getObj(path: string): GameTracker | false;
+ function readObj(jsonPath: string): GameTracker | false;
+ function writeObj(obj: GameTracker, path: string): boolean;
+ function resetGameTime(): void;
+ function reset(): void;
+ function checkValidity(): void;
+ function totalDays(milliseconds: number): string;
+ function script(starttime: number, subscript: string, startexp: number): boolean;
+ function leveling(): boolean;
+ function update(oogTick?: number): boolean;
+ }
+
+ namespace SetUp {
+ let mercEnabled: boolean;
+ const currentBuild: string;
+ const finalBuild: string;
+ const stopAtLevel: number | false;
+
+ function init(): void;
+ function include(): void;
+ function finalRespec(): number;
+ function getTemplate(): { buildType: string, template: string };
+ function specPush(specType: string): number[];
+ function makeNext(): void;
+ function belt(): void;
+ function buffers(): void;
+ function bowQuiver(): void;
+ function imbueItems(): string[];
+ function config(): void;
+ }
+
+ namespace Check {
+ let lowGold: boolean;
+
+ function gold(): boolean;
+ function brokeAf(): boolean;
+ function broken(): 0 | 1 | 2;
+ function brokeCheck(): boolean;
+ function resistance(): { Status: boolean, FR: number, CR: number, LR: number, PR: number };
+ function nextDifficulty(announce: boolean): string | false;
+ function runes(): boolean;
+ function haveItem(type: string | number, flag?: string | number, iName?: string): boolean;
+ function itemSocketables(type: string | number, quality: string | number, iName?: string): boolean;
+ function currentBuild(): Build;
+ function finalBuild(): Build;
+ }
+
+ namespace SoloWants {
+ }
+
+ namespace NPCAction {
+ function shopAt(npcName: string): boolean;
+ function buyPotions(): boolean;
+ function fillTome(classid: number, force?: boolean): boolean;
+ function cainID(force?: boolean): boolean;
+ function shopItems(force?: boolean): boolean;
+ function gamble(): boolean;
+ function repair(force?: boolean): boolean;
+ function reviveMerc(): boolean;
+ }
+
+ namespace AutoEquip {
+ }
+
+ type extraTasks = {
+ thawing?: boolean,
+ antidote?: boolean,
+ stamina?: boolean,
+ fullChores?: boolean,
+ };
+
+ namespace LocationAction {
+ function run(): void;
+ }
+
+ type PresetObjectUnit = {
+ x: number;
+ y: number;
+ area: number;
+ classid: number;
+ type: number;
+ };
+
+ class ShrineInstance {
+ constructor (shrine: ObjectUnit);
+
+ type: number;
+ classid: number;
+ state: number;
+ duration: number;
+ regenTime: number;
+ area: number;
+ x: number;
+ y: number;
+ gid: number;
+ interactedAt: number;
+
+ useable(): boolean;
+ }
+ class AreaDataInstance {
+ constructor (index: number);
+
+ LocaleString: string;
+ Index: number;
+ Act: number;
+ Level: number;
+ Size: {
+ x: number;
+ y: number;
+ };
+ SuperUnique: number[];
+ Monsters: number[];
+ MonsterDensity: number;
+ ChampionPacks: {
+ Min: number;
+ Max: number;
+ };
+ private _Waypoint: PresetObjectUnit | null;
+ Shrines: ShrineInstance[];
+ Chests: PresetObjectUnit[];
+
+ hasMonsterType (type: number): boolean;
+ forEachMonster (callback: (monster: number) => void): void;
+ forEachMonsterAndMinion (callback: (monster: number) => void): void;
+ canAccess(): boolean;
+ townArea(): AreaDataInstance;
+ getExits(): Exit[];
+ setWaypoint(wp: PresetUnit): void;
+ waypointCoords(): PresetObjectUnit | null;
+ haveWaypoint(): boolean;
+ hasWaypoint(): boolean;
+ nearestWaypointArea(): number;
+ nearestWaypointCoords(): PresetObjectUnit | null;
+ getChests(): PresetObjectUnit[];
+ addShrine(shrine: ObjectUnit): void;
+ updateShrine(shrine: ObjectUnit): void;
+ getShrines(): ShrineInstance[];
+ }
+
+ namespace AreaData {
+ /** @private */
+ const _map: Map;
+ const wps: Map;
+ const nextAreas: Map;
+ const previousAreas: Map;
+
+ function set(key: number, value: AreaDataInstance): void;
+ function get(key: number): AreaDataInstance | undefined;
+ function has(key: number): boolean;
+ function forEach(callback: (value: AreaDataInstance, key: number) => void): void;
+ /**
+ * @description returns a random non town wp area
+ */
+ function randomWpArea(checkValid: boolean): number;
+ function getAreasWithShrine(shrineType: number): AreaDataInstance[];
+ }
+
+ namespace GameData {
+ const myReference: Unit;
+ const townAreas: number[];
+
+ function monsterLevel(monsterID: number, areaID: number, adjustLevel: number): number;
+ function eliteExp(monsterID: number, areaID: number): number;
+ function monsterAvgHP(monsterID: number, areaID: number, adjustLevel: number): number;
+ function monsterMaxHP(monsterID: number, areaID: number, adjustLevel: number): number;
+ function eliteAvgHP(monsterID: number, areaID: number): number;
+ function monsterDamageModifier(): number;
+ function monsterMaxDmg(monsterID: number, areaID: number, adjustLevel: number): number;
+ function monsterAttack1AvgDmg(monsterID: number, areaID: number, adjustLevel: number): number;
+ function monsterAttack2AvgDmg(monsterID: number, areaID: number, adjustLevel: number): number;
+ function monsterSkill1AvgDmg(monsterID: number, areaID: number, adjustLevel: number): number;
+ function monsterAvgDmg(monsterID: number, areaID: number, adjustLevel: number): number;
+ function averagePackSize(monsterID: number): number;
+ function areaLevel(areaID: number): number;
+ function areaImmunites(areaID: number): string[];
+ function levelModifier(clvl: number, mlvl: number): number;
+ function multiplayerModifier(count: number): number;
+ function partyModifier(playerID: number): number;
+ function killExp(playerID: number, monsterID: number, areaID: number): number;
+ function baseLevel(...skillIDs: number[]): number;
+ function skillLevel(...skillIDs: number[]): number;
+ function skillCooldown(skillId: number): boolean;
+ function stagedDamage(
+ l: number,
+ a: number,
+ b: number,
+ c: number,
+ d: number,
+ e: number,
+ f: number,
+ hitshift: number,
+ mult: number,
+ ): number;
+ const damageTypes: string[];
+ const synergyCalc: Record;
+ const noMinSynergy: number[];
+ const skillMult: Record;
+ function baseSkillDamage(skillId: number): number;
+ const skillRadius: Record;
+ const novaLike: Record;
+ const wolfBanned: Record;
+ const bearBanned: Record;
+ const humanBanned: Record;
+ const nonDamage: Record;
+ function shiftState(): string;
+ function bestForm(skillID: number): number;
+ function physicalAttackDamage(skillID: number): number;
+ function dmgModifier(skillID: number, target: Monster): number;
+
+ interface SkillDamage {
+ type: string;
+ pmin: number;
+ pmax: number;
+ min: number;
+ max: number;
+ undeadOnly?: boolean;
+ }
+ function skillDamage(skillID: number, unit?: Monster): SkillDamage;
+ function avgSkillDamage(skillID: number, unit?: Monster): number;
+ function allSkillDamage(unit: Monster): SkillDamage[];
+ const convictionEligible: Record;
+ const lowerResistEligible: Record;
+ const resistMap: Record;
+ const masteryMap: Record;
+ const pierceMap: Record;
+ const ignoreSkill: Record;
+ const buffs: Record;
+ const preAttackable: number[];
+ function monsterResist(unit: Monster, type: string): number;
+ function getConviction(): number;
+ function getAmp(): number;
+ function monsterEffort(
+ unit: Monster,
+ areaID: number,
+ skillDamageInfo?: SkillDamage,
+ parent?: Monster,
+ preattack?: boolean,
+ all?: boolean,
+ ): { effort: number, skill: number, type: string, name?: string, cooldown?: boolean };
+ function effectiveMonsterEffort(
+ unit: Monster,
+ areaID: number
+ ): { effort: number, skill: number, type: string, name?: string, cooldown?: boolean };
+ function areaEffort(areaID: number, skills?: SkillDamage[]): number;
+ function areaSoloExp(areaID: number, skills?: SkillDamage[]): number;
+ function timeTillMissileImpact(skillId: number, monster: Monster): number;
+ function calculateKillableFallensByFrostNova(): number;
+ function calculateKillableSummonsByNova(): number;
+ function targetPointForSkill(skillId: number, monster: Monster): PathNode;
+ }
+}
+export{};
diff --git a/libs/SoloPlay/tsconfig.json b/libs/SoloPlay/tsconfig.json
new file mode 100644
index 00000000..640e779b
--- /dev/null
+++ b/libs/SoloPlay/tsconfig.json
@@ -0,0 +1,39 @@
+{
+ "compilerOptions": {
+ "module": "None",
+ "lib": [
+ "ES2015",
+ "es2015.collection",
+ "ES2015.Promise",
+ "ES2016.Array.Include",
+ "ES2017.Object",
+ "ScriptHost",
+ ],
+ "target": "ES6",
+ "allowJs": true,
+ "checkJs": false,
+ "moduleResolution": "classic",
+ "allowUmdGlobalAccess": true,
+ "esModuleInterop": true,
+ "noImplicitAny": true,
+ "noEmit": true,
+ "strict": true,
+ "baseUrl": "./",
+ // "rootDir": "./",
+ "outFile": "./out.js",
+ },
+ "include": [
+ "../../sdk/globals.d.ts",
+ "../../sdk/types/*.d.ts",
+ "globals.d.ts",
+ "*.js",
+ "./**/*.js",
+ ],
+ "exclude": [
+ "**/ClassAttackOverrides/",
+ "node_modules",
+ "**/node_modules/*",
+ "**/data/",
+ "**/Data/",
+ ],
+}