Calling/Chaining scripts

Additional botting resources. Addons may be either for the game itself or for the RoM bot.
Forum rules
Only post additional bot resources here. Please do not ask unrelated questions.
Post Reply
Message
Author
dx876234
Posts: 188
Joined: Sat Jul 24, 2010 6:13 am

Calling/Chaining scripts

#1 Post by dx876234 » Mon Jul 09, 2012 4:17 am

As my script collection have grown, both in downloaded scripts and home-brewed, I seem to spend quite some time chaining and calling waypoint files, particularely where I reuse some transport waypoints in multiple setups.

So, I've been looking into how to chain/call waypoints in a generic method, leaning heavily on Rock's method but not quite satisfied with that one either as it hardcodes the chain into the waypoint itself making it difficult to re-use in multiple waypoint chains.

My suggestion (still thinking) is to implement a userfunction with call/chain/return functionality.
For this to work we need to catch termination of waypoints, the most used way is to call "error(msg, lvl)".

So, my implementation hooks the error function in the userfunction which also implements the call/chain/return fucntionality.

Main goal was to be compatible with existing scripts, i.e. I want a "main" waypoint to be able to call/chain sets of waypoints preferably without modifications.

The error hook only triggers on lvl = 0 errores, checks to see if my userfunction is installed, if not it calls the original error function directly.

The userfunction implements WPCall, WPChain and WPFinished.

WPCall(waypointfile) will load another waypoint file and when this executes a error(msg, 0) it will load the caller again.

WPChain(waypointfiles...) will load the waypoints specified in sequence, note that there is no implied "return to caller", to have that you must add __WPL.FileName as last waypoint in list.

WPFinished(When_Finished, charlist) basically implements (copies) Rock's chain setup, what he typically puts in the relog function. But, if the waypoint currently executing was started from another script with call/chain it will skipp the relog functionality and return or go to next waypoint in list.

The error(msg,0) basically executes a WPFinished() when called.

This results in the possibility of chaining together pieces of waypoints with hardcoding only in the toplevel script while any hardcoding in the called script will be executed as normal when its not called by another. (Hmm slightly unclear...)

UPDATES
  • Moved the error hook into the userfunction file

Any views on method?

Regards
DX
Attachments
userfunction_call.lua
v0.2
(2.83 KiB) Downloaded 171 times
userfunction_call.lua
v0.1
(2.51 KiB) Downloaded 153 times
Last edited by dx876234 on Mon Jul 09, 2012 6:30 am, edited 1 time in total.

dx876234
Posts: 188
Joined: Sat Jul 24, 2010 6:13 am

Re: Calling/Chaining scripts

#2 Post by dx876234 » Mon Jul 09, 2012 4:19 am

A small example from my dailies setup:

Code: Select all

elseif do_step.GOBLINS and do_goblin[player.Name] then
    WPChain("Dailies/02-snoop", "Dailies/90-goblin", __WPL.FileName)
Where 1st waypoint moves me to snoop, 2nd tp to silverfall and moves to npc (and calls a goblic script from forums) while last calls back to main script.

User avatar
lisa
Posts: 8332
Joined: Tue Nov 09, 2010 11:46 pm
Location: Australia

Re: Calling/Chaining scripts

#3 Post by lisa » Mon Jul 09, 2012 4:33 am

I am trying to work out the difference between adding the error hook and just calling the function WPFinished() in the actual WP.

With the hook you need to use an error message to make it call the function, so end result is it still calls the function with no added arguments.

Don't get me wrong I like what you've done, just trying to work out why the added step before you call the function.

Are you trying to make it continue on with the next WP when it gets any error?
Remember no matter you do in life to always have a little fun while you are at it ;)

wiki here http://www.solarstrike.net/wiki/index.php?title=Manual

User avatar
rock5
Posts: 12173
Joined: Tue Jan 05, 2010 3:30 am
Location: Australia

Re: Calling/Chaining scripts

#4 Post by rock5 » Mon Jul 09, 2012 5:01 am

That's what I was thinking. You can just have the waypoint call a function that makes the decision of which file to load next. The function could check __WPL.FileName to see which waypoint file you are upto and load the next in the sequence for that character.
  • Please consider making a small donation to me to support my continued contributions to the bot and this forum. Thank you. Donate
  • I check all posts before reading PMs. So if you want a fast reply, don't PM me but post a topic instead. PM me for private or personal topics only.
  • How to: copy and paste in micromacro
    ________________________
    Quote:
    • “They say hard work never hurt anybody, but I figure, why take the chance.”
          • Ronald Reagan

dx876234
Posts: 188
Joined: Sat Jul 24, 2010 6:13 am

Re: Calling/Chaining scripts

#5 Post by dx876234 » Mon Jul 09, 2012 5:44 am

The error hook is so that I dont have to modify scripts other ppl wrote, ideally it shouldnt be nessesary but as of now its the only way I found to be able to call/chain 3rd party scripts originally written as stand-alone

-dx

dx876234
Posts: 188
Joined: Sat Jul 24, 2010 6:13 am

Re: Calling/Chaining scripts

#6 Post by dx876234 » Mon Jul 09, 2012 5:47 am

lisa wrote:I am trying to work out the difference between adding the error hook and just calling the function WPFinished() in the actual WP.

With the hook you need to use an error message to make it call the function, so end result is it still calls the function with no added arguments.

Don't get me wrong I like what you've done, just trying to work out why the added step before you call the function.

Are you trying to make it continue on with the next WP when it gets any error?
There is a lot of scripts, ex Rock's element_fuser, which isnt made for chaining. By catching the error message used to terminate the script I can continue the chaining without having to modify the original script

-dx

User avatar
lisa
Posts: 8332
Joined: Tue Nov 09, 2010 11:46 pm
Location: Australia

Re: Calling/Chaining scripts

#7 Post by lisa » Mon Jul 09, 2012 5:59 am

dx876234 wrote:So, my implementation is a small addition to top of bot.lua which hooks the error function and a userfunction which implements the call/chain/return fucntionality.
Editing the bot.lua each time the bot is updated would be a pain though, have you tested adding that code to your userfunction? it will probably do the same thing.

If it was me I would just edit the WP to call the function because then you only do it once (each WP) and then it's done. Not everyone ends their WP with an error, I know I don't lol
Remember no matter you do in life to always have a little fun while you are at it ;)

wiki here http://www.solarstrike.net/wiki/index.php?title=Manual

dx876234
Posts: 188
Joined: Sat Jul 24, 2010 6:13 am

Re: Calling/Chaining scripts

#8 Post by dx876234 » Mon Jul 09, 2012 6:12 am

Ill give it a go inserting the error code function in userfunction file, would be a lot cleaner.

Lisa, how do you terminate a waypoint file?

As far as Ive seen it will loop forever unless u terminate with loading another or using error to stop...I've prolly missed something vital here :)

-dx

User avatar
lisa
Posts: 8332
Joined: Tue Nov 09, 2010 11:46 pm
Location: Australia

Re: Calling/Chaining scripts

#9 Post by lisa » Mon Jul 09, 2012 6:46 am

If it's the last char on the last WP I just put it to sleep.

Code: Select all

player:sleep()
I of course never end the last WP in the middle of hostile mobs, so sleep is enough. You can also use sleep as a pause, if it gets agro it will continue the WP, if you hit Delete it will wake up and continue.
Remember no matter you do in life to always have a little fun while you are at it ;)

wiki here http://www.solarstrike.net/wiki/index.php?title=Manual

dx876234
Posts: 188
Joined: Sat Jul 24, 2010 6:13 am

Re: Calling/Chaining scripts

#10 Post by dx876234 » Mon Jul 09, 2012 8:20 am

Ah, then u have most in one waypointfile I guess.

I typically have one or more generic for transport to known locations, called from multiple main scripts. Therefore I cant hardcode chaining and I cant terminate the transport with a sleep - not many other ways left than using error()

So one of my waypoint chains can be like (btw, controlled from an integrated mysql base):

<TP somewhere and run to start point> - <Do dailies, script depends on level of character> - <Do mails> - <Do events> - <log next character to work on>

Each of these might spawn subchains as ex. the event one which moves to right place, start Rocks script, moves to next location, do quests, then do transmution. I.E it gets kinda hard to keep integrating these together, particularely with scripts written by others.

So, if there was a standarized way of chaining/calling/terminating waypoint files it would simplify the process a lot.

-dx

BillDoorNZ
Posts: 446
Joined: Wed Aug 03, 2011 7:37 pm

Re: Calling/Chaining scripts

#11 Post by BillDoorNZ » Mon Jul 09, 2012 2:44 pm

I've been doing something similar to this for ages now...its a right pain :)

I ended up creating a new main lua file for the bot and have a global setting which tells it if it should loop waypoints or not. That way, when it gets to the last waypoint in the waypoint list, it either stops and goes into Wander mode, or it loops back to the beginning.

This works, but is a pain in itself.

I thought of a more generic solution the other week but haven't had time to implement it. Basically it requires some changes to the loadPaths function and as few other places. I was looking at completely replacing the WaypointList class (or subclassing it) so that it is only ever created once and then it decides what to do instead. So the new WPL would be told to load a waypoint (be it movement, a minigame or a grinding path etc) and then it will never tell the bot that the current waypoint index is the last one. If the waypoint it loads is supposed to loop, it would loop. If not, it wouldn't and would seamlessly load the next wpl internally and carry on telling the bot where to go.

I had implemented something similar a few months back that acted as a singleton controller and had a Think method which would determine what the bot should do next. The think method would get called with the current waypoint finished (looping was controlled by the global I talked about earlier), but required that I make a few mods to the waypoint list (added an onUnload and onFinished event to allow tidyup etc - the onFinished event was always attached to the Think method so the bot could work out what to do next and know the wpl had finished). I also ended up having to add a new method to the WPL class to allow it to load itself from a in-memory xml document (I generate the paths dynamically).

hmm...code below might clarify things a little more. This is from the controller class (there is a lot of hardcoding in the configuration side of things atm tho).

Excerpt from Controller:

Code: Select all

function CController:Think()
	self:print("==============\nTHINK\n==============");
	self:print(tostring(self.think.lastCallTime));
	local time = os.time();
	local elapsed = time - self.think.lastCallTime;
	self.think.lastCallTime = time;
	
	if (self.currentAction) then
		self:print("currentAction: "..tostring(self.currentAction.name));
	else
		self:print("currentAction: None");
	end;
	self:print("actionList.Empty: "..tostring(self.actionList:isEmpty())); -- actionList is a Queue BTW :)
	if (self.currentAction) then
		--check for errored state (stuck etc)
		self:print("  Perfoming an action - do nothing new");
		return;
	end;
	
	--we aint doing nuffink!
	--if we currently have a plan of action, then do that
	if (not self.actionList:isEmpty()) then
		self:print("  Moving on to next action");
		--are we still performing the current action? or are we ready to move on to the next action;
		self:SetAction(self.actionList:pop());
		return;
	end;
	
	-- =================== Decide what to do ====================
	
	self:print("Checking minigames");
	-- ============ minigames
	if (self.config.minigames.enabled) then
		for k, v in pairs(self.config.minigames) do
			if (k ~= "enabled") then
				if (not self:hasDoneMinigame(v)) then
					if (self:doMinigame(v)) then
						--self:SetAction(self.actionList:pop());
						return;
					end;
				end;
			end;
		end;
	end;
	
	self:print("Checking daily's");
	-- ============ daily's
	if (self.config.daily.enabled) then
		if (not self:HasCompletedDailies()) then
			self:print("  Aint done dailies");
			local wpl = self:buildPathToNPC(self.config.daily.StarterZone, self.config.daily.StarterId);
			local dailyWPL = CWaypointList();
			local paths = self:getQuestFile(self.config.daily.Id);
			if (#paths > 0) then
				local questFile = getExecutionPath().."/waypoints/quests/"..tostring(self.config.daily.Id)..".xml";
				dailyWPL:load(questFile, true);
				dailyWPL.notifyOfEvent = self.events.onComplete;
				
				--load the daily script
				if (wpl and dailyWPL) then 
					self.actionList:push(self:buildAction("Run to Dailies", wpl));
					self.actionList:push(self:buildAction("Do Dailies", dailyWPL));
					self:print("ActionList.Count: "..self.actionList:count());
				end;
				return;
			else
				self:print("Could not find waypoint file for daily quest "..tostring(self.config.daily.Id));
			end;
		end;
	end;

	-- ============ Housemaids
	self:print("Checking Housemaids");
	if (self.config.housemaids.enabled) then
		--do we need to do housemaids?
		--have we done them at all yet? or has it been 2 hours?
		--need to also check for potions in backpack
		if ((not self.config.housemaids.lastDone) or ((os.time() - self.config.housemaids.lastDone) > (60*60*2))) then
			local isInHouse = (getZoneId() == ZONE_HOUSE);
			local canRun = true;
			
			if (not isInHouse) then
				self:print("  Going to housemaids");
				local wpl = self:buildPathToHouse();
				self:print("Have path to house");
				if (wpl) then
					self:print("Have path to house xml AND execute xml");
					self.actionList:push(self:buildAction("Goto house", wpl));
				else
					canRun = false;
				end;
			else
				self:print("  Already in house");
			end;
			
			if (canRun) then 
				self.actionList:push(self:buildAction("Talk to housekeepers", "ControllerInstance():runHousemaids()", "code"));
				self.actionList:push(self:buildAction("Exit house", "player:target_NPC(\"Housekeeper\");\nChoiceOptionByName(\"leave\");\nwaitForLoadingScreen(30);\n", "code"));
				self:print("ActionList.Count: "..self.actionList:count());
				self:SetAction(self.actionList:pop());
			end;
			return;
		end;
	end;
	
	--what next!!!!?? questing / resting / KS farming???
	self:print("  Finished");
	
end;

function CController:SetAction(_action)
	self:print("New action specified: "..tostring(_action.name));
	self.currentAction = _action;
	if (_action) then
		while (_action and (_action.type ~= "wpl")) do
			local fn = loadstring(_action.WPL);
			fn();
			
			_action = nil;
			
			if (not self.actionList:isEmpty()) then
				self:print("  Was a code action, moving on to next action");
				_action = self.actionList:pop();
				if (_action) then
					self:print("    new action specified: "..tostring(_action.name));
				end;
			end;
			self.currentAction = _action;
		end;

		if (_action and (_action.type == "wpl")) then
			__WPL = self.currentAction.WPL;
			self:print("  performing Load");
			__WPL:performLoad();
			self:print("  Load done");
		else
			__WPL:abort();
			loadPaths("wander", rp_to_load);
			__WPL:setRadius(settings.profile.options.WANDER_RADIUS);
			__WPL:setMode("wander");
			releaseKeys();
			SetCommsState("on");
			sendChatMessage("NavPoint", "end"); --send a message to an external app
			self:Think();
		end;
	else
		__WPL:abort();
		loadPaths("wander", rp_to_load);
		__WPL:setRadius(settings.profile.options.WANDER_RADIUS);
		__WPL:setMode("wander");
		releaseKeys();
		SetCommsState("on");
		sendChatMessage("NavPoint", "end");
		self:Think();
	end;
	
	return;
end;

function CController:runHousemaids()
	--check we are in our house first
	local z=getZoneId();
	if (z ~= ZONE_HOUSE) then
		self:print("Cannot use housemaids as I am not in a house");
		return false;
	end;
	
	--find all housemaids
	self:print("Finding housemaids");
	local housemaids = {};
	local objectList = CObjectList();
	objectList:update();
	for i = 0,objectList:size() do
		local obj = objectList:getObject(i);
		if (obj ~= nil) and (obj.Id > 113700) and (obj.Id < 114000) then
			if (obj.Name ~= "Housekeeper") then
				local maid = {id=obj.Id, name=obj.Name};
				table.insert(housemaids, maid);
			end;
		end;
	end;

	--run housemaids every 5 hours, as they recover 5 fatigue on the turn of every hour
	self.config.housemaids.maids = housemaids;
	if ((self.config.housemaids.enabled) and ((os.time()-self.config.housemaids.lastDone) > (60*60*5))) then
		for k,v in pairs(housemaids) do
			self:runHousemaid(v);
		end;
		
		self.config.housemaids.lastDone = os.time();
	else
		return;
	end;
	
	local nameOptions = {}
	nameOptions.width = 20;
	nameOptions.alignment = "left";
	nameOptions.inside = false;
	nameOptions.capColor = "";
	nameOptions.spacerColor = "";
	nameOptions.textColor = "";
	
	local options = {}
	options.width = 20;
	options.alignment = "center";
	options.inside = false;
	options.capColor = "";
	options.spacerColor = "";
	options.textColor = "";
	
	local result = "\n";
	result = result.."====================================================================================================\n";
	result = result..align("Housemaids", {width=100, capColor="", spacerColor="", textColor=""});
	result = result.."\n====================================================================================================\n";
	result = result..string.format("%-20s%10s%10s%10s%10s%10s%10s%10s%10s\n",align("name", nameOptions),align("Affinity", options),align("Charisma", options),align("Fatigue", options),align("Magic", options),align("Battle", options),align("Defense", options),align("Cooking", options),align("Inventive", options));
	for k,v in pairs(housemaids) do
		result = result..string.format("%-20s%10s%10s%10s%10s%10s%10s%10s%10s\n", align(v.name, nameOptions), align(v.affinity.."/"..v.affinityMax, options), align(v.charisma.."/"..v.charismaMax, options), align(v.fatigue.."/"..v.fatigueMax, options), align(v.magic.."/"..v.magicMax, options), align(v.battle.."/"..v.battleMax, options), align(v.defense.."/"..v.defenseMax, options), align(v.cooking.."/"..v.cookingMax, options), align(v.inventive.."/"..v.inventiveMax, options));
	end;
	result = result.."====================================================================================================\n";
	
	self:print(result);
	
end;

function CController:getHousemaidIndex(_housemaid)
	local count = RoMScript("Houses_GetServantCount()");
	for i=1,count,1 do
		local DBID, name, sex, character, month, day, horoscope, race, 
		Affinity, AffinityMax, Charisma, CharismaMax, 
		Fatigue, FatigueMax, Magic, MagicMax, Battle, BattleMax, Defense, DefenseMax, Cooking, CookingMax, Inventive, InventiveMax = RoMScript("Houses_GetServantInfo("..tostring(i)..")");		
		self:print("Comparing "..tostring(_housemaid.name).." to dbid: "..tostring(DBID).." name: "..tostring(name).." char: "..tostring(character));
		if (name == _housemaid.name) then return i; end;
	end;
	
	return -1;
end;

function CController:runHousemaid(_housemaid)
	if (_housemaid) then
		self:print("running housemaid "..tostring(_housemaid.id));
		local housemaidIndex = self:getHousemaidIndex(_housemaid);
		self:print("index = "..tostring(housemaidIndex));
		
		if (housemaidIndex > -1) then
			self:print("getting servant info");
		
			local DBID, name, sex, character, month, day, horoscope, race, 
			Affinity, AffinityMax, Charisma, CharismaMax, 
			Fatigue, FatigueMax, Magic, MagicMax, Battle, BattleMax, Defense, DefenseMax, Cooking, CookingMax, Inventive, InventiveMax;

			local function UpdateMaid()
					DBID, name, sex, character, month, day, horoscope, race, 
								Affinity, AffinityMax, Charisma, CharismaMax, 
								Fatigue, FatigueMax, Magic, MagicMax, Battle, BattleMax, Defense, DefenseMax, Cooking, CookingMax, Inventive, InventiveMax = RoMScript("Houses_GetServantInfo("..tostring(housemaidIndex)..")");
					_housemaid.name = name;
					_housemaid.sex = sex;
					_housemaid.character = character;
					_housemaid.month = month;
					_housemaid.day = day;
					_housemaid.horoscope = horoscope;
					_housemaid.race = race;
					_housemaid.affinity = Affinity;
					_housemaid.affinityMax = AffinityMax;
					_housemaid.charisma = Charisma;
					_housemaid.charismaMax = CharismaMax;
					_housemaid.fatigue = Fatigue;
					_housemaid.fatigueMax = FatigueMax;
					_housemaid.magic = Magic;
					_housemaid.magicMax = MagicMax;
					_housemaid.battle = Battle;
					_housemaid.battleMax = BattleMax;
					_housemaid.defense = Defense;
					_housemaid.defenseMax = DefenseMax;
					_housemaid.cooking = Cooking;
					_housemaid.cookingMax = CookingMax;
					_housemaid.inventive = Inventive;
					_housemaid.inventiveMax = InventiveMax;										
			end;
			
			UpdateMaid();
			
			local function LevelOne(skill)
			  player:target_NPC(_housemaid.id);
			  self:print("Leveling skill "..skill)
			  ChoiceOptionByName(skill);
			  yrest(100);
			  UpdateMaid();
		   end			
		
			
			local done = false;
			repeat
				--chat adds 15 fatigue
				if (_housemaid.fatigue > 85) then done = true; end;
				if ((not done) and (_housemaid.fatigue > 75)) then
					if (Affinity < AffinityMax) then
						self:print("Fatigue > 75, can only level affinity");
						LevelOne("chat");
						done = true;
					else
						self:print("Fatigue > 75, and Affinity is at max. Done.");
						done = true;
					end;
				end;
				
				--determine which skills should be maxxed
				--level affinity to 40 first
				--always level inventive to 40 (for luck pots)
				--then check to see which skill(s) are at 100max
				if (not done) then
					if (Affinity < 50) then
						LevelOne("Chat"); 
					elseif ((Inventive < Affinity) and (Inventive < 50)) then
						LevelOne("Crafting")
					else
						local highestMax = InventiveMax;
						local highestName = "crafting"; --default to crafting
						
						if (CookingMax > highestMax) then
							highestMax = CookingMax;
							highestName = "cooking";
						end;
						if (MagicMax > highestMax) then
							highestMax = MagicMax;
							highestName = "magic";
						end;
						if (BattleMax > highestMax) then
							highestMax = BattleMax;
							highestName = "battle";
						end;
						if (DefenseMax > highestMax) then
							highestMax = DefenseMax;
							highestName = "defense";
						end;
						
						if (highestMax < Affinity) then
							LevelOne(highestName);
						else
							LevelOne("chat");
						end;
					end;
				end;
			 
			until done;
		end;
	end;
end


dx876234
Posts: 188
Joined: Sat Jul 24, 2010 6:13 am

Re: Calling/Chaining scripts

#12 Post by dx876234 » Wed Jul 11, 2012 3:49 am

Agree, its a pain.

So, question is; is it possible to make a standarized way of handling chaining/calling without hardcoding in scripts?

-dx

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest