Cow Portal for Act 1 shop bot

Simple mod to use cow portal for act 1 shopping.
You need to open it manually at the vendor you want to shop at, works same as nihla portal.

function ShopBot() {
	var i, tickCount,
		cycles = 0,
		cyclesText = new Text("Cycles in last minute:", 50, 260, 2, 1),
		title = new Text("kolbot shopbot", 50, 245, 2, 1),
		frequency = new Text("Valid item frequency:", 50, 275, 2, 1),
		totalCyclesText = new Text("Total cycles:", 50, 290, 2, 1),
		validItems = 0,
		totalCycles = 0;

	Pather.teleport = false;
	this.pickEntries = [];
	this.npcs = {};

	this.buildPickList = function () {
		var i, nipfile, line, lines, info,
			filepath = "pickit/shopbot.nip",
			filename = filepath.substring(filepath.lastIndexOf("/") + 1, filepath.length);

		if (!FileTools.exists(filepath)) {
			Misc.errorReport("ÿc1NIP file doesn't exist: ÿc0" + filepath);
			return false;
		}

		try {
			nipfile = File.open(filepath, 0);
		} catch (fileError) {
			Misc.errorReport("ÿc1Failed to load NIP: ÿc0" + filename);
		}

		if (!nipfile) {
			return false;
		}

		lines = nipfile.readAllLines();
		nipfile.close();

		for (i = 0; i < lines.length; i += 1) {
			info = {
				line: i + 1,
				file: filename,
				string: lines[i]
			};

			line = NTIP.ParseLineInt(lines[i], info);

			if (line) {
				this.pickEntries.push(line);
			}
		}

		return true;
	};

	this.openMenu = function (npc) {
		if (npc.type !== 1) {
			throw new Error("Unit.openMenu: Must be used on NPCs.");
		}

		var i, tick,
			interactedNPC = getInteractedNPC();

		if (interactedNPC && interactedNPC.name !== npc.name) {
			sendPacket(1, 0x30, 4, interactedNPC.type, 4, interactedNPC.gid);
			me.cancel();
		}

		if (getUIFlag(0x08)) {
			return true;
		}

		for (i = 0; i < 10; i += 1) {
			if (getDistance(me.x, me.y, npc.x, npc.y) > 5) {
				Pather.walkTo(npc.x, npc.y);
			}

			if (!getUIFlag(0x08)) {
				sendPacket(1, 0x13, 4, 1, 4, npc.gid);
				sendPacket(1, 0x2f, 4, 1, 4, npc.gid);
			}

			tick = getTickCount();

			while (getTickCount() - tick < Math.max(Math.round((i + 1) * 250 / (i / 3 + 1)), me.ping + 1)) {
				if (getUIFlag(0x08)) {
					return true;
				}

				delay(10);
			}
		}

		me.cancel();

		return false;
	};

	this.shopItems = function (npc, menuId) {
		var i, item, items, bought;

		if (!Storage.Inventory.CanFit({sizex: 2, sizey: 4}) && AutoMule.getMuleItems().length > 0) {
			D2Bot.printToConsole("Mule triggered");
			scriptBroadcast("mule");
			scriptBroadcast("quit");
			return true;
		}

		if (!npc) {
			return false;
		}

		for (i = 0; i < 10; i += 1) {
			delay(150);

			if (i % 2 === 0) {
				sendPacket(1, 0x38, 4, 1, 4, npc.gid, 4, 0);
			}

			if (npc.itemcount > 0) {
				//delay(200);
				break;
			}
		}

		item = npc.getItem();

		if (!item) {
			return false;
		}

		items = [];

		do {
			if (Config.ShopBot.ScanIDs.indexOf(item.classid) > -1 || Config.ShopBot.ScanIDs.length === 0) {
				items.push(copyUnit(item));
			}
		} while (item.getNext());

		//me.overhead(npc.itemcount + " items, " + items.length + " valid");

		validItems += items.length;
		frequency.text = "Valid base items / cycle: " + ((validItems / totalCycles).toFixed(2).toString());

		for (i = 0; i < items.length; i += 1) {
			if (Storage.Inventory.CanFit(items[i]) && Pickit.canPick(items[i]) &&
					me.gold >= items[i].getItemCost(0) &&
					NTIP.CheckItem(items[i], this.pickEntries)
					) {
				beep();
				D2Bot.printToConsole("Match found!", 7);
				delay(1000);

				if (npc.startTrade(menuId)) {
					Misc.logItem("Shopped", items[i]);
					items[i].buy();
					bought = true;
				}

				if (Config.ShopBot.QuitOnMatch) {
					scriptBroadcast("quit");
				}
			}
		}

		if (bought) {
			me.cancel();
			Town.stash();
		}

		return true;
	};

	this.shopAtNPC = function (name) {
		var i, npc, wp,
			menuId = "Shop";

		switch (name) {
		case NPC.Charsi:
			menuId = "Repair";
		case NPC.Akara:
		case NPC.Gheed:
			wp = 1;

			break;
		case NPC.Fara:
			menuId = "Repair";
		case NPC.Elzix:
		case NPC.Drognan:
			wp = 40;

			break;
		case NPC.Hratli:
			menuId = "Repair";
		case NPC.Asheara:
		case NPC.Ormus:
			wp = 75;

			break;
		case NPC.Halbu:
			menuId = "Repair";
		case NPC.Jamella:
			wp = 103;

			break;
		case NPC.Larzuk:
			menuId = "Repair";
		case NPC.Malah:
		case NPC.Anya:
			wp = 109;

			break;
		default:
			throw new Error("Invalid NPC");
		}

		if (!Pather.useWaypoint(wp)) {
			return false;
		}

		npc = this.npcs[name] || getUnit(1, name);

		if (!npc || getDistance(me, npc) > 5) {
			Town.move(name);
			npc = getUnit(1, name);
		}

		if (!npc) {
			return false;
		}

		if (!this.npcs[name]) {
			this.npcs[name] = copyUnit(npc);
		}

		if (Config.ShopBot.CycleDelay) {
			delay(Config.ShopBot.CycleDelay);
		}

		if (this.openMenu(npc)) {
			this.shopItems(npc, menuId);
		}

		return true;
	};

	// START
	for (i = 0; i < Config.ShopBot.ScanIDs.length; i += 1) {
		if (isNaN(Config.ShopBot.ScanIDs[i])) {
			if (NTIPAliasClassID.hasOwnProperty(Config.ShopBot.ScanIDs[i].replace(/\s+/g, "").toLowerCase())) {
				Config.ShopBot.ScanIDs[i] = NTIPAliasClassID[Config.ShopBot.ScanIDs[i].replace(/\s+/g, "").toLowerCase()];
			} else {
				Misc.errorReport("ÿc1Invalid ShopBot entry:ÿc0 " + Config.ShopBot.ScanIDs[i]);
				Config.ShopBot.ScanIDs.splice(i, 1);
				i -= 1;
			}
		}
	}

	if (typeof Config.ShopBot.ShopNPC === "string") {
		Config.ShopBot.ShopNPC = [Config.ShopBot.ShopNPC];
	}

	for (i = 0; i < Config.ShopBot.ShopNPC.length; i += 1) {
		Config.ShopBot.ShopNPC[i] = Config.ShopBot.ShopNPC[i].toLowerCase();
	}

	if (Config.ShopBot.MinGold && me.getStat(14) + me.getStat(15) < Config.ShopBot.MinGold) {
		return true;
	}

	this.buildPickList();
	print("Shopbot: Pickit entries: " + this.pickEntries.length);
	Town.doChores();

	tickCount = getTickCount();

	while (!Config.ShopBot.Cycles || totalCycles < Config.ShopBot.Cycles) {
		if (getTickCount() - tickCount >= 60 * 1000) {
			cyclesText.text = "Cycles in last minute: " + cycles.toString();
			totalCyclesText.text = "Total cycles: " + totalCycles.toString();
			cycles = 0;
			tickCount = getTickCount();
		}

		for (i = 0; i < Config.ShopBot.ShopNPC.length; i += 1) {
			this.shopAtNPC(Config.ShopBot.ShopNPC[i]);
		}

		if (me.inTown) {
			var area = getArea(),
				wp = getPresetUnit(me.area, 2, [119, 156, 237, 398, 429][me.act - 1]),
				wpX = wp.roomx * 5 + wp.x,
				wpY = wp.roomy * 5 + wp.y,
				exit = area.exits[0];

			for (i = 1; i < area.exits.length; i++) {
				if (getDistance(me, exit) > getDistance(me, area.exits[i])) {
					exit = area.exits[i];
				}
			}
			if (me.area === 1 && getDistance(me, Pather.usePortal(39)) < 20 && Pather.usePortal(39)){
				Attack.clear(15);
				delay(3000);
				Attack.clear(15);
				Pather.usePortal(1);
	
				delay(2000);
				
			}else
			if (me.area === 109 && getDistance(me, 5117, 5119) < 20 && me.getQuest(37, 0) === 1 && me.getQuest(38, 0) !== 1 && Pather.usePortal(121)) {
				delay(3000);
				Pather.usePortal(109);

				if (totalCycles === 0) {
					delay(5000);
				}

				delay(1500);
			} else if (getDistance(me, exit) < (getDistance(me, wpX, wpY) + 6)) {
				Pather.moveToExit(me.area + 1, true);
				Pather.moveToExit(me.area - 1, true);
			} else {
				Pather.useWaypoint([35, 48, 101, 107, 113][me.act - 1]);
			} 
			
		}

		cycles += 1;
		totalCycles += 1;
	}

	return true;
}

With Portal the Code works, but without it uses all delays that come after even the one from Nihlathak as it seems. Since I have no clue, I could need some help to fix that.

maybe you should add a condition to use this kind of shopbot in act 1 to the existing cow portal, and maybe also to me.getQuest(4, 10)) // already killed the cow king, and completing Cain quest = the availability of the Tristram portal if the shopbot is supposed to open the cow portal.

Was thinking about that 1st, but depending on what NPC people want to use it would need several different conditions to place the portal and then there is always the risk to have portal next to king. So I think it is better to open manually and check 1st before starting to shop.
Only reason to use this is increasing the interval per minute, so portal need to be as close to Vendor as possible. Been getting 8-9 per minute and with standard WP or exit it is only 4 per minute. Thought about trying this for Lazurk and Malah with Uber portal, but that be to risk and costly :laughing:

Does it have to be a red portal?

Yes, only work with cow portal, but you could change that to work with standard TP.
But I doubt it would be a wise choice since you would always need to buy new tp scrolls and the distance is nearly same as wp or exit.
Although it could be good idea for shopping at malah in act 5 :grin:

Thats what i was thinking. Was for malah if it doesn’t need to be a red portal then a blue portal would put you close. Could also do ormus in a3 but would probably be better to just use wp at that point. As for distance you could make the shop bot character a sorceress with telekinesis, I’ve update kolbot to use tk on units where its allowed so it really wouldn’t be that far

I tried a bit and it ignored the TP, might have wrong code or it needs a var for portal.
To late for me to think straight :laughing:

This is weird!
I made exact same changes as I did with act 1 cow portal for act 5 Tristram and bot ignores the red portal.

			if (me.area === 119 && getDistance(me,Pather.usePortal(136)) < 40 && Pather.usePortal(136)) {
				Attack.clear(5);
				delay(3000);
				Attack.clear(5);
				Pather.usePortal(109);
				delay(2000);
				
				if (totalCycles === 0) {
					delay(5000);
				}

				
			}

Likely because your code is wrong, usePortal doesn’t return a portal object it returns a boolean. I think you meant to use getPortal

That is how Nihla portal is used, not my code at all, I just copy pasted what was already there.
And it works with Cow that way, so must be something else that is missing or creating wrong call. My guess is the “if else” because the way I put act 1 in front of act 5, is only way I got that working. Will try to remove that and then change Nihla to Trist portal with specific position for distance check.

No it isn’t

 
 ​                ​if​ ​(​me​.​inTown​)​ ​{ 
 ​                        ​let​ ​area​ ​=​ ​getArea​(​)​, 
 ​                                ​wp​ ​=​ ​getPresetUnit​(​me​.​area​,​ ​2​,​ ​[​119​,​ ​156​,​ ​237​,​ ​398​,​ ​429​]​[​me​.​act​ ​-​ ​1​]​)​, 
 ​                                ​wpX​ ​=​ ​wp​.​roomx​ ​*​ ​5​ ​+​ ​wp​.​x​, 
 ​                                ​wpY​ ​=​ ​wp​.​roomy​ ​*​ ​5​ ​+​ ​wp​.​y​, 
 ​                                ​exit​ ​=​ ​area​.​exits​[​0​]​; 
  
 ​                        ​for​ ​(​let​ ​i​ ​=​ ​1​;​ ​i​ ​<​ ​area​.​exits​.​length​;​ ​i​++​)​ ​{ 
 ​                                ​if​ ​(​getDistance​(​me​,​ ​exit​)​ ​>​ ​getDistance​(​me​,​ ​area​.​exits​[​i​]​)​)​ ​{ 
 ​                                        ​exit​ ​=​ ​area​.​exits​[​i​]​; 
 ​                                ​} 
 ​                        ​} 
  
 ​                        ​if​ ​(​me​.​area​ ​===​ ​109​ ​&&​ ​getDistance​(​me​,​ ​5117​,​ ​5119​)​ ​<​ ​20​ ​&&​ ​me​.​getQuest​(​37​,​ ​0​)​ ​===​ ​1​ ​&&​ ​me​.​getQuest​(​38​,​ ​0​)​ ​!==​ ​1​ ​&&​ ​Pather​.​usePortal​(​121​)​)​ ​{ 
 ​                                ​delay​(​3000​)​; 
 ​                                ​Pather​.​usePortal​(​109​)​; 
  
 ​                                ​if​ ​(​totalCycles​ ​===​ ​0​)​ ​{ 
 ​                                        ​delay​(​10000​)​; 
 ​                                ​} 
  
 ​                                ​delay​(​1500​)​; 
 ​                        ​}​ ​else​ ​if​ ​(​getDistance​(​me​,​ ​exit​)​ ​<​ ​(​getDistance​(​me​,​ ​wpX​,​ ​wpY​)​ ​+​ ​6​)​)​ ​{ 
 ​                                ​Pather​.​moveToExit​(​me​.​area​ ​+​ ​1​,​ ​true​)​; 
 ​                                ​Pather​.​moveToExit​(​me​.​area​ ​-​ ​1​,​ ​true​)​; 
 ​                        ​}​ ​else​ ​{ 
 ​                                ​Pather​.​useWaypoint​(​[​35​,​ ​48​,​ ​101​,​ ​107​,​ ​113​]​[​me​.​act​ ​-​ ​1​]​)​; 
 ​                        ​} 
 ​                ​}

See the difference, you can’t pass a boolean into a distance check and expect a valid output.

Weird, your code is different! And I didn’t change the parts that are.

var area = getArea(),

for (i = 1; i < area.exits.length; i++) {
				if (getDistance(me, exit) > getDistance(me, area.exits[i])) {
					exit = area.exits[i];
				}

this was original and yours says “let” not “var” and “i”.
But

Pather​.​usePortal​(​121​)​

is same in both and not getPortal as you suggested.

Exit is the actual areas exit. It passes an x and y value. Thats why it works. The usePortal at the end is outside of that. So again if you want to check your distance from you to the portal. Do getDistance(me, Pather.getPortal(portalID))

Rather than keep going back and forth with you, here this should work including for uber portals if you want to shop at lazruk and position one near you

You ever test this out?

Where to start :sweat_smile:
Well, I tried! But since I didn’t update since January, it gave errors without end.
Because all the changes you made, that are so huge and awesome, I was forced to download newest update and am still working on configs and bug fixing.
For example, if I set Javazon to teleswitch, she attacks with CtA to clear :sweat_smile:
That bug was there before, when move retry happen during teleporting and since no one seem to use I guess no one noticed.
Even saw here got to eldritch, teleport with attack slot then switch to cta to attack and I have right settings in char config, only if turn on teleswitch this bug happen.
But I have to say! These changes you made are really great and whole bot seems to run smother on my 1st glance tonight. :star_struck:
One other thing that I miss is faster mod, during config creation there are no settings added in char config and I had to add myself to use em.
You have done so much, I think it’s normal to miss some things.

Oh i didnt know about thay bug ill look into fixing it. As for fast mod being removed that wasnt an oversight. Its because it only works offline. Otherwise it causes desync because the client shows its working but in reality it isnt. Im glad to hear that other than that everything is going smoother ive been putting a lot of work into fixing kolbot and making it far more stable

It is working, the animtion bug has always been there, it just won’t work for manual playing since you won’t send packets. I have talked to someone using different bot in 2012 he explained it to me, other players will never see the hammers for example as they would with a true 125fcr. But the bot will be using the mod with packet casting.
I been running hammer with hoto and arach and achieve full speed!

FastMod and PacketCasting are two different things. PacketCasting is still there in the configs and yes it works. FastMod is patched, thats where you used to be able to set your fcr to a value and it would use that instead of your true fcr.