diff --git a/d2bs/kolbot/default.dbj b/d2bs/kolbot/default.dbj index 99f6bc937..f92e8f028 100644 --- a/d2bs/kolbot/default.dbj +++ b/d2bs/kolbot/default.dbj @@ -22,6 +22,7 @@ include("common/Precast.js"); include("common/Prototypes.js"); include("common/Runewords.js"); include("common/Storage.js"); +include("common/Mercenary.js"); include("common/Town.js"); function main() { diff --git a/d2bs/kolbot/libs/GameAction.js b/d2bs/kolbot/libs/GameAction.js index 51c8b845a..553c244d6 100644 --- a/d2bs/kolbot/libs/GameAction.js +++ b/d2bs/kolbot/libs/GameAction.js @@ -494,7 +494,7 @@ var GameAction = { if (this.LogMerc) { for (i = 0; i < 3; i += 1) { - merc = me.getMerc(); + merc = Mercenary.getMerc(); if (merc) { break; diff --git a/d2bs/kolbot/libs/MuleLogger.js b/d2bs/kolbot/libs/MuleLogger.js index 9ec7ef370..4f4d289ec 100644 --- a/d2bs/kolbot/libs/MuleLogger.js +++ b/d2bs/kolbot/libs/MuleLogger.js @@ -373,7 +373,7 @@ var MuleLogger = { if (this.LogMerc) { for (i = 0; i < 3; i += 1) { - merc = me.getMerc(); + merc = Mercenary.getMerc(); if (merc) { break; diff --git a/d2bs/kolbot/libs/common/Attack.js b/d2bs/kolbot/libs/common/Attack.js index c4138b8e6..842758ca4 100644 --- a/d2bs/kolbot/libs/common/Attack.js +++ b/d2bs/kolbot/libs/common/Attack.js @@ -164,7 +164,7 @@ var Attack = { var i, merc, item; for (i = 0; i < 3; i += 1) { - merc = me.getMerc(); + merc = Mercenary.getMerc(); if (merc) { break; diff --git a/d2bs/kolbot/libs/common/Attacks/Amazon.js b/d2bs/kolbot/libs/common/Attacks/Amazon.js index 113c2b37c..8d90a0639 100644 --- a/d2bs/kolbot/libs/common/Attacks/Amazon.js +++ b/d2bs/kolbot/libs/common/Attacks/Amazon.js @@ -11,7 +11,7 @@ var ClassAttack = { doAttack: function (unit, preattack) { var needRepair = Town.needRepair(); - if ((Config.MercWatch && Town.needMerc()) || needRepair.length > 0) { + if ((Config.MercWatch && Mercenary.needMerc()) || needRepair.length > 0) { Town.visitTown(!!needRepair.length); } @@ -74,7 +74,7 @@ var ClassAttack = { if (result === 2 && Config.TeleStomp && Attack.checkResist(unit, "physical") && !!me.getMerc()) { while (Attack.checkMonster(unit)) { - if (Town.needMerc()) { + if (Mercenary.needMerc()) { if (Config.MercWatch && mercRevive++ < 1) { Town.visitTown(); } else { @@ -213,4 +213,4 @@ var ClassAttack = { return 1; } -}; \ No newline at end of file +}; diff --git a/d2bs/kolbot/libs/common/Attacks/Assassin.js b/d2bs/kolbot/libs/common/Attacks/Assassin.js index 5f5d026c3..8c547b932 100644 --- a/d2bs/kolbot/libs/common/Attacks/Assassin.js +++ b/d2bs/kolbot/libs/common/Attacks/Assassin.js @@ -9,7 +9,7 @@ var ClassAttack = { trapRange: 20, doAttack: function (unit, preattack) { - if (Config.MercWatch && Town.needMerc()) { + if (Config.MercWatch && Mercenary.needMerc()) { Town.visitTown(); } @@ -98,7 +98,7 @@ var ClassAttack = { if (result === 2 && Config.TeleStomp && Attack.checkResist(unit, "physical") && !!me.getMerc()) { while (Attack.checkMonster(unit)) { - if (Town.needMerc()) { + if (Mercenary.needMerc()) { if (Config.MercWatch && mercRevive++ < 1) { Town.visitTown(); } else { diff --git a/d2bs/kolbot/libs/common/Attacks/Barbarian.js b/d2bs/kolbot/libs/common/Attacks/Barbarian.js index 4c338a4a6..8bee6b148 100644 --- a/d2bs/kolbot/libs/common/Attacks/Barbarian.js +++ b/d2bs/kolbot/libs/common/Attacks/Barbarian.js @@ -8,7 +8,7 @@ var ClassAttack = { doAttack: function (unit, preattack) { var needRepair = Town.needRepair(); - if ((Config.MercWatch && Town.needMerc()) || needRepair.length > 0) { + if ((Config.MercWatch && Mercenary.needMerc()) || needRepair.length > 0) { Town.visitTown(!!needRepair.length); } diff --git a/d2bs/kolbot/libs/common/Attacks/Druid.js b/d2bs/kolbot/libs/common/Attacks/Druid.js index 739c70388..d17663369 100644 --- a/d2bs/kolbot/libs/common/Attacks/Druid.js +++ b/d2bs/kolbot/libs/common/Attacks/Druid.js @@ -6,7 +6,7 @@ var ClassAttack = { doAttack: function (unit, preattack) { - if (Config.MercWatch && Town.needMerc()) { + if (Config.MercWatch && Mercenary.needMerc()) { Town.visitTown(); } @@ -77,7 +77,7 @@ var ClassAttack = { if (result === 2 && Config.TeleStomp && Attack.checkResist(unit, "physical") && !!me.getMerc()) { while (Attack.checkMonster(unit)) { - if (Town.needMerc()) { + if (Mercenary.needMerc()) { if (Config.MercWatch && mercRevive++ < 1) { Town.visitTown(); } else { diff --git a/d2bs/kolbot/libs/common/Attacks/Necromancer.js b/d2bs/kolbot/libs/common/Attacks/Necromancer.js index ca0fbe988..ddf92d1fe 100644 --- a/d2bs/kolbot/libs/common/Attacks/Necromancer.js +++ b/d2bs/kolbot/libs/common/Attacks/Necromancer.js @@ -74,7 +74,7 @@ var ClassAttack = { this.initCurses(); } - if (Config.MercWatch && Town.needMerc()) { + if (Config.MercWatch && Mercenary.needMerc()) { Town.visitTown(); } @@ -167,7 +167,7 @@ var ClassAttack = { this.explodeCorpses(unit); } else if (result === 2 && Config.TeleStomp && Attack.checkResist(unit, "physical") && !!me.getMerc()) { while (Attack.checkMonster(unit)) { - if (Town.needMerc()) { + if (Mercenary.needMerc()) { if (Config.MercWatch && mercRevive++ < 1) { Town.visitTown(); } else { diff --git a/d2bs/kolbot/libs/common/Attacks/Paladin.js b/d2bs/kolbot/libs/common/Attacks/Paladin.js index 59fc0f650..29b84a8ac 100644 --- a/d2bs/kolbot/libs/common/Attacks/Paladin.js +++ b/d2bs/kolbot/libs/common/Attacks/Paladin.js @@ -6,7 +6,7 @@ var ClassAttack = { doAttack: function (unit, preattack) { - if (Config.MercWatch && Town.needMerc()) { + if (Config.MercWatch && Mercenary.needMerc()) { print("mercwatch"); Town.visitTown(); } @@ -61,7 +61,7 @@ var ClassAttack = { if (result === 2 && Config.TeleStomp && Attack.checkResist(unit, "physical") && !!me.getMerc()) { while (Attack.checkMonster(unit)) { - if (Town.needMerc()) { + if (Mercenary.needMerc()) { if (Config.MercWatch && mercRevive++ < 1) { Town.visitTown(); } else { diff --git a/d2bs/kolbot/libs/common/Attacks/Sorceress.js b/d2bs/kolbot/libs/common/Attacks/Sorceress.js index 58245bd18..9c2ba4b37 100644 --- a/d2bs/kolbot/libs/common/Attacks/Sorceress.js +++ b/d2bs/kolbot/libs/common/Attacks/Sorceress.js @@ -6,7 +6,7 @@ var ClassAttack = { doAttack: function (unit, preattack) { - if (Config.MercWatch && Town.needMerc()) { + if (Config.MercWatch && Mercenary.needMerc()) { print("mercwatch"); Town.visitTown(); } @@ -114,7 +114,7 @@ var ClassAttack = { if (result === 2 && Config.TeleStomp && Attack.checkResist(unit, "physical") && !!me.getMerc() && Attack.validSpot(unit.x, unit.y)) { while (Attack.checkMonster(unit)) { - if (Town.needMerc()) { + if (Mercenary.needMerc()) { if (Config.MercWatch && mercRevive++ < 1) { Town.visitTown(); } else { diff --git a/d2bs/kolbot/libs/common/Attacks/Wereform.js b/d2bs/kolbot/libs/common/Attacks/Wereform.js index d792b9ce9..1969de54e 100644 --- a/d2bs/kolbot/libs/common/Attacks/Wereform.js +++ b/d2bs/kolbot/libs/common/Attacks/Wereform.js @@ -6,7 +6,7 @@ var ClassAttack = { doAttack: function (unit, preattack) { - if (Config.MercWatch && Town.needMerc()) { + if (Config.MercWatch && Mercenary.needMerc()) { Town.visitTown(); } @@ -69,7 +69,7 @@ var ClassAttack = { if (result === 2 && Config.TeleStomp && Attack.checkResist(unit, "physical") && !!me.getMerc()) { while (Attack.checkMonster(unit)) { - if (Town.needMerc()) { + if (Mercenary.needMerc()) { if (Config.MercWatch && mercRevive++ < 1) { Town.visitTown(); } else { diff --git a/d2bs/kolbot/libs/common/Mercenary.js b/d2bs/kolbot/libs/common/Mercenary.js new file mode 100644 index 000000000..05c3bfbd1 --- /dev/null +++ b/d2bs/kolbot/libs/common/Mercenary.js @@ -0,0 +1,420 @@ + +var Mercenary = { + variants: { + "271": [7, 11], + "338": [[99, 104, 108], [103, 114, 98]], // Normal/Hell, Nightmare + "359": [41, 55, 38], + "560": [126] + }, + + classIds: [ + -1, + 271, // Act 1 - Rogue + 338, // Act 2 - Guard + 359, // Act 3 - Iron Wolf + -1, + 560 // Act 5 - Barb + ], + + availableMercs: [], + + getMerc: function () { + if (!Config.UseMerc) { + return null; + } + + var merc = me.getMerc(); + + // me.getMerc() might return null if called right after taking a portal, that's why there's retry attempts + for (var i = 0; i < 3; i++) { + if (merc) { + if (merc.mode === 0 || merc.mode === 12) { + return null; + } + + break; + } + + delay(100); + merc = me.getMerc(); + } + + return merc; + }, + + getVariant: function () { + var merc = this.getMerc(); + + if (!merc) { + return null; + } + + for (var i = 0; i < this.variants[merc.classid].length; i++) { + if (Array.isArray(this.variants[merc.classid][i])) { + for (var j = 0; j < this.variants[merc.classid][i].length; j++) { + if (merc.getSkill(this.variants[merc.classid][i][j], 1)) { + return this.variants[merc.classid][i][j]; + } + } + } else if (merc.getSkill(this.variants[merc.classid][i], 1)) { + return this.variants[merc.classid][i]; + } + } + + throw new Error("Mercenary.getVariant: Couldn't determine merc variant."); + }, + + hasMerc: function (mercType, variant) { + if (!Config.UseMerc) { + return false; // We don't want a merc + } + + if (mercType === undefined) { + if (typeof Config.UseMerc === "object") { + mercType = Config.UseMerc.mercType; + } else { + mercType = Config.UseMerc; + } + } + + if (variant === undefined) { + if (typeof Config.UseMerc === "object") { + variant = Config.UseMerc.variant; + } else { + variant = -1; + } + } + + if (mercType < 1 || mercType > 5 || mercType === 4) { + throw new Error("Mercenary.hasMerc: Incorrect value for mercType"); + } + + var merc = this.getMerc(); + + if (this.isDead()) { + return true; // Merc is dead so we can't determine the variant or type + } else if ((!merc && (me.mercrevivecost === 0 || me.gametype === 0)) + || (this.classIds[mercType] !== merc.classid || (variant !== -1 && this.getVariant() !== variant))) { + return false; // We never had a merc, or we want a different type or possibly variant of merc + } + + return true; + }, + + isDead: function () { + return me.mercrevivecost > 0; + }, + + needMerc: function () { + if (me.gold < me.mercrevivecost) { + return false; + } + + return Config.UseMerc && (this.isDead() || !this.hasMerc()); + }, + + canHire: function (mercType) { + if (mercType === undefined) { + if (typeof Config.UseMerc === "object") { + mercType = Config.UseMerc.mercType; + } else { + mercType = Config.UseMerc; + } + } + + switch (mercType) { + case 1: + + if ((Misc.checkQuest(2, 1) || Misc.checkQuest(2, 0)) || me.charlvl >= 8) { + return true; + } + + break; + + case 2: + if (Misc.checkQuest(6, 0) || Misc.checkQuest(6, 1)) { + return true; + } + + break; + + case 3: + if (Misc.checkQuest(14, 0) || Misc.checkQuest(14, 1) || Misc.checkQuest(14, 3) || Misc.checkQuest(14, 4)) { + return true; + } + + break; + + case 5: + if (Misc.checkQuest(36, 0)) { + return true; + } + + break; + + default: + throw new Error("Mercenary.canHire: Incorrect value for mercType: " + mercType); + } + + return false; + + }, + + hire: function (args) { + if (args === undefined) { + return false; + } + + if (args.mercType === undefined) { + throw new Error("Mercenary.hire: args.mercType is required"); + } + + if (args.mercType < 1 || args.mercType === 4 || args.mercType > 5) { + throw new Error("Mercenary.hire: Invalid mercType"); + } + + if (args.replace === undefined) { + args.replace = false; + } // useful if the merc level is far behind our level and we want a new one to catch up + + if (args.variant === undefined) { + args.variant = -1; + } + + if (!args.replace && this.hasMerc(args.mercType, args.variant)) { + return true; + } + + if (!this.canHire(args.mercType)) { + print("Mercenary.hire: Can't hire merc of type " + args.mercType); + + return false; + } + + print("Mercenary.hire: Hiring new Merc of type " + args.mercType + " and variant " + args.variant); + + if (!Town.goToTown(args.mercType)) { + return false; + } + + if (this.isDead()) { + this.revive(); + } // Revive so we can pull the equipment off, just in case + + if (!this.unloadEquipment()) { + return false; + } // Bail out if we can't unload the merc's equipment, just in case + + Town.move(Town.tasks[args.mercType - 1].Merc); // Move to Merc NPC for the mercType act + Pather.moveTo(me.x + rand(-3, 3), me.y + rand(-3, 3)); + Town.move(Town.tasks[args.mercType - 1].Merc); + + delay(1000); + + addEventListener("gamepacket", this.mercPacket); + var hire = getUnit(1, Town.tasks[args.mercType - 1].Merc); + + if (!hire || !hire.openMenu()) { + sendPacket(1, 0x4b, 4, me.type, 4, me.gid); + delay(1000 + me.ping); + Town.move(Town.tasks[args.mercType - 1].Merc); + sendPacket(1, 0x4b, 4, me.type, 4, me.gid); + hire = getUnit(1, Town.tasks[args.mercType - 1].Merc); + hire.openMenu(); + + if (!hire || !hire.openMenu()) { + throw new Error("Mercenary.hireMerc: failed to open npc menu"); + } + } + + delay(1000 + me.ping * 2); + + removeEventListener("gamepacket", this._gamePacket); + + // Hire mercs until we get the right one. + if (Mercenary.availableMercs.length) { + do { + print("Trying to hire a merc"); + //print(Mercenary.availableMercs.toSource()); + Misc.useMenu(0x0D45); + sendPacket(1, 0x36, 4, hire.gid, 4, Mercenary.availableMercs.pop()); + + delay(2000 + me.ping * 2); + + var v = this.getVariant(); + print("my new merc variant is: " + v + " - desired variant: " + args.variant); + + if (args.variant === v) { + break; + } + + } while (!this.hasMerc(args.mercType, args.variant) && Mercenary.availableMercs.length > 0); + + } else { + print("No mercs available"); + + me.cancel(); + + return false; + } + + me.cancel(); + + return true; + }, + + revive: function () { + if (!this.needMerc()) { + print("I don't need to revive my merc"); + + return true; + } + + if (!this.isDead()) { + return true; + } + + // Fuck Aheara + if (me.act === 3) { + Town.goToTown(2); + } + + var i, tick, dialog, lines, + preArea = me.area, + npc = Town.initNPC("Merc", "reviveMerc"); + + if (!npc) { + return false; + } + + MainLoop: + for (i = 0; i < 3; i += 1) { + dialog = getDialogLines(); + + for (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(3362), "gi")) { + return false; + } + } + + while (getTickCount() - tick < 2000) { + if (this.getMerc()) { + delay(Math.max(750, me.ping * 2)); + + break MainLoop; + } + + delay(200); + } + } + + Attack.checkInfinity(); + + if (this.getMerc()) { + if (Config.MercWatch && !me.inTown) { // Cast BO on merc so he doesn't just die again + print("MercWatch precast"); + Pather.useWaypoint("random"); + Precast.doPrecast(true); + Pather.useWaypoint(preArea); + } + + return true; + } + + return false; + }, + + unloadEquipment: function () { + print("Unloading merc equipment"); + var cursorItem; + + // ok this is a bit stupid + clickItem(4, 4); + delay(me.ping + 500); + + if (me.itemoncursor) { + delay(me.ping + 500); + cursorItem = getUnit(100); + + if (cursorItem) { + if (!Storage.Inventory.MoveTo(cursorItem)) { + return false; + } + + delay(me.ping + 1000); + + if (me.itemoncursor) { + Misc.click(0, 0, me); + delay(me.ping + 500); + } + } + } + + clickItem(4, 3); + delay(me.ping + 500); + + if (me.itemoncursor) { + delay(me.ping + 500); + cursorItem = getUnit(100); + + if (cursorItem) { + if (!Storage.Inventory.MoveTo(cursorItem)) { + return false; + } + + delay(me.ping + 1000); + + if (me.itemoncursor) { + Misc.click(0, 0, me); + delay(me.ping + 500); + } + } + } + + clickItem(4, 1); + delay(me.ping + 500); + + if (me.itemoncursor) { + delay(me.ping + 500); + cursorItem = getUnit(100); + + if (cursorItem) { + if (!Storage.Inventory.MoveTo(cursorItem)) { + return false; + } + + delay(me.ping + 500); + + if (me.itemoncursor) { + Misc.click(0, 0, me); + delay(me.ping + 500); + } + } + } + + return true; + + }, + + mercPacket: function (bytes) { + if (bytes[0] !== 0x4e) { + return; + } + + var id = (bytes[2] << 8) + bytes[1]; + + if (Mercenary.availableMercs.indexOf(id) !== -1) { + Mercenary.availableMercs.length = 0; + } + + Mercenary.availableMercs.push(id); + + return; + } +}; diff --git a/d2bs/kolbot/libs/common/Misc.js b/d2bs/kolbot/libs/common/Misc.js index b1a0906d1..f960124c6 100644 --- a/d2bs/kolbot/libs/common/Misc.js +++ b/d2bs/kolbot/libs/common/Misc.js @@ -2038,6 +2038,13 @@ MainLoop: } return flags.length ? flags : null; + }, + + checkQuest: function (id, state) { + sendPacket(1, 0x40); + delay(500); + + return me.getQuest(id, state); } }; diff --git a/d2bs/kolbot/libs/common/Town.js b/d2bs/kolbot/libs/common/Town.js index dccb489c4..a2bdd4224 100644 --- a/d2bs/kolbot/libs/common/Town.js +++ b/d2bs/kolbot/libs/common/Town.js @@ -77,6 +77,8 @@ var Town = { Attack.weaponSwitch(Attack.getPrimarySlot()); this.heal(); + Mercenary.revive(); + Mercenary.hire(Config.UseMerc); this.identify(); this.shopItems(); this.fillTome(518); @@ -91,7 +93,6 @@ var Town = { this.buyKeys(); this.repair(repair); this.gamble(); - this.reviveMerc(); Cubing.doCubing(); Runewords.makeRunewords(); this.stash(true); @@ -1420,92 +1421,6 @@ CursorLoop: return itemList; }, - reviveMerc: function () { - if (!this.needMerc()) { - return true; - } - - // Fuck Aheara - if (me.act === 3) { - this.goToTown(2); - } - - var i, tick, dialog, lines, - preArea = me.area, - npc = this.initNPC("Merc", "reviveMerc"); - - if (!npc) { - return false; - } - -MainLoop: - for (i = 0; i < 3; i += 1) { - dialog = getDialogLines(); - - for (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(3362), "gi")) { - return false; - } - } - - while (getTickCount() - tick < 2000) { - if (!!me.getMerc()) { - delay(Math.max(750, me.ping * 2)); - - break MainLoop; - } - - delay(200); - } - } - - Attack.checkInfinity(); - - if (!!me.getMerc()) { - if (Config.MercWatch) { // Cast BO on merc so he doesn't just die again - print("MercWatch precast"); - Pather.useWaypoint("random"); - Precast.doPrecast(true); - Pather.useWaypoint(preArea); - } - - return true; - } - - return false; - }, - - needMerc: function () { - var i, merc; - - if (me.gametype === 0 || !Config.UseMerc || me.gold < me.mercrevivecost) { // gametype 0 = classic - return false; - } - - // me.getMerc() might return null if called right after taking a portal, that's why there's retry attempts - for (i = 0; i < 3; i += 1) { - merc = me.getMerc(); - - if (merc && merc.mode !== 0 && merc.mode !== 12) { - return false; - } - - delay(100); - } - - if (!me.mercrevivecost) { // In case we never had a merc and Config.UseMerc is still set to true for some odd reason - return false; - } - - return true; - }, - canStash: function (item) { var ignoredClassids = [91, 174]; // Some quest items that have to be in inventory or equipped diff --git a/d2bs/kolbot/tools/ToolsThread.js b/d2bs/kolbot/tools/ToolsThread.js index a1a91b91b..82db8638b 100644 --- a/d2bs/kolbot/tools/ToolsThread.js +++ b/d2bs/kolbot/tools/ToolsThread.js @@ -26,6 +26,7 @@ include("common/Precast.js"); include("common/Prototypes.js"); include("common/Runewords.js"); include("common/Storage.js"); +include("common/Mercenary.js"); include("common/Town.js"); function main() { @@ -600,7 +601,7 @@ function main() { if (Config.UseMerc) { mercHP = getMercHP(); - merc = me.getMerc(); + merc = Mercenary.getMerc(); if (mercHP > 0 && merc && merc.mode !== 12) { if (mercHP < Config.MercChicken) { diff --git a/d2bs/kolbot/tools/TownChicken.js b/d2bs/kolbot/tools/TownChicken.js index a3f139174..8fad22f0c 100644 --- a/d2bs/kolbot/tools/TownChicken.js +++ b/d2bs/kolbot/tools/TownChicken.js @@ -23,6 +23,7 @@ include("common/Precast.js"); include("common/Prototypes.js"); include("common/Runewords.js"); include("common/Storage.js"); +include("common/Mercenary.js"); include("common/Town.js"); function main() {