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).
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