-------------------------------------------------------------------------------- -- AutoSelectDialog ver.2.1 addon by hal.dll -- ver.1.0 was originally written by Zeon aka Jed © 2013-07-22 -- ver.2.0 addon has been completelly reworked by hal.dll © 2014-05-27 -- Added support of autostarting dialogs with NPCs, autoselecting and -- autofinishing quests, fixed some bugs. -- v2.1: fixed mods.txt bug + lua 5.1 support, by hal.dll © 2014-07-16 -------------------------------------------------------------------------------- -- Created: 2013-07-22 -- Updated: 2014-07-17 -- https://alloder.pro/files/file/165-autoselectdialog/ -------------------------------------------------------------------------------- -- API stubs local GetContextActionShortInfo = avatar.GetContextActionShortInfo or avatar.GetContextActionInfo local IsNavigateToPoint = avatar.IsNavigateToPoint or function() return false end local ExtractWStringFromValuedText = function(str) return common.IsWString(str) and str or common.ExtractWStringFromValuedText(str) end -------------------------------------------------------------------------------- -- Helper functions -------------------------------------------------------------------------------- local acceptedOnce local cached -- After returning a quest other already finished quests could not disappear -- from the list, so we can cache the list between calls. But other quests -- might come to finished state after returning already finished, so after -- returning a quest we have to continue searching for is there any other -- quests to finish or otherwise destroy the cache to build a new next time. function FinishQuest(interactorId) local flag = false local actions = autoSelectQuests[userMods.FromWString(object.GetName(interactorId))] if actions then if not cached then cached = {} local quests = object.GetInteractorQuests(interactorId) local imax = quests and GetTableSize(quests.readyToGive) or 0 for i = 0, imax do if quests.readyToAccept[i] then local reward = avatar.GetQuestReward(quests.readyToAccept[i]) local cantReturn = reward and reward.alternativeItems and GetTableSize(reward.alternativeItems) > 1 if not cantReturn then local info = avatar.GetQuestInfo(quests.readyToAccept[i]) local name = userMods.FromWString(ExtractWStringFromValuedText(info.name)) cached[name] = quests.readyToAccept[i] end end end end for j = 1, GetTableSize(actions) do local name = actions[j] if cached[name] then if flag then return flag end acceptedOnce[name] = true avatar.ReturnQuest(cached[name], nil) cached[name] = nil flag = true end end end cached = nil return flag end -- After accepting a quest any other available quest could disappear from -- the list and/or new quests could appear, so we can not cache the list -- and we have to request the list of available quests every time. function AcceptQuest(interactorId) local actions = autoSelectQuests[userMods.FromWString(object.GetName(interactorId))] if actions then local quests = object.GetInteractorQuests(interactorId) local imax = quests and GetTableSize(quests.readyToGive) or 0 if imax > 0 then local name = {} for j = 1, GetTableSize(actions) do if not acceptedOnce[ actions[j] ] then for i = 0, imax do if quests.readyToGive[i] then if not name[i] then local info = avatar.GetQuestInfo(quests.readyToGive[i]) name[i] = userMods.FromWString(ExtractWStringFromValuedText(info.name)) end if name[i] == actions[j] then acceptedOnce[ name[i] ] = true avatar.AcceptQuest(quests.readyToGive[i]) return true end end end end end end end end function RunDialog(interactorId) local actions = autoSelectDialog[userMods.FromWString(object.GetName(interactorId))] if actions then for j = 1, GetTableSize(actions) do if type(actions[j]) == "function" then actions[j]() else --if type(actions[j]) == "string" then local choices = avatar.GetInteractorNextCues() for i = 0, GetTableSize(choices) do if choices[i] and userMods.FromWString(choices[i].name) == actions[j] then avatar.SelectInteractorCue(i) break end end end end end end -------------------------------------------------------------------------------- -- Interaction with NPC events handlers -------------------------------------------------------------------------------- -- Addon may receive EVENT_INTERACTION_STARTED many times while talking -- with the same NPC, e.g. when user is clicking "Return" button. -- Also addons are allowed to accept or finish one quest per the event -- occurrence, and have to wait for the event before next attempts. local inProgress = false local retries local maxRetries = 20 function OnTalkStarted() inProgress = true acceptedOnce = {} retries = nil end function OnInteractionStarted() if not inProgress then return end local info = avatar.GetInteractorInfo() if info and info.hasInteraction then -- always true? inProgress = AcceptQuest(info.interactorId) or FinishQuest(info.interactorId) or RunDialog(info.interactorId) or false else inProgress = false end if not inProgress then acceptedOnce = nil end end -- AO 5.0.02+ game client automatically selects mob in target after avatar.StartInteract(). -- Make user happy with not unselecting it manually. -- Non-nil activeNPC means this addon started interaction with NPC specified in activeNPC. -- For the interactions started by user, activeNPC == nil and addon should not unselect them. local activeNPC function OnTalkStopped() if activeNPC then local target = avatar.GetTarget() -- or avatar.GetSecondaryTarget() ? local name = target and object.IsExist(target) and userMods.FromWString(object.GetName(target)) if not inProgress then OnTalkStarted() OnInteractionStarted() return end if name and activeNPC == name then avatar.UnselectTarget() end end activeNPC = nil inProgress = false acceptedOnce = nil end -- Only checking new context actions local knownNPCs = {} -- Sometimes a message "Target is too far" appears when addon tries to start -- interaction. That is most likely due to delay in avatar coordinates -- handling, so context action appears earlier than avatar coordinates get -- updated. So retrying to start interaction if we get such error. function OnActionFailed( params ) if activeNPC and params.sysId == "ENUM_ActionFailCause_TooFar" and not params.isInNotPredicate then if not retries then retries = maxRetries end if retries > 0 then retries = retries - 1 -- Try again knownNPCs = {} activeNPC = nil OnContextActionsChanged() else -- It's enough retries = nil activeNPC = nil end end end function OnContextActionsChanged() local current = {} local flag = false local actions = avatar.GetContextActions() for _,action in pairs(actions) do local info = GetContextActionShortInfo(action) if info and info.objectId and info.enabled and info.sysType == "ENUM_CONTEXT_ACTION_TYPE_NPC_TALK" then local name = userMods.FromWString(object.GetName(info.objectId)) if not knownNPCs[name] then flag = true end current[name] = info.objectId end end -- Not starting interaction if: -- 1. already talking with another NPC -- 2. automoving to NPC (not to point --- TODO) if avatar.IsTalking() or IsNavigateToPoint() then flag = false end if flag then for j = 1, GetTableSize(autoStartDialog) do local name = autoStartDialog[j] if current[name] and not knownNPCs[name] then avatar.StartInteract(current[name]) activeNPC = name break end end end knownNPCs = current end -------------------------------------------------------------------------------- -- INITIALIZATION -------------------------------------------------------------------------------- function FixConfig() for name,value in pairs(autoSelectQuests) do if type(value) == "string" then autoSelectQuests[name] = { value } elseif type(value) == "table" then -- TODO check all items else autoSelectQuests[name] = nil end end for name,value in pairs(autoSelectDialog) do if type(value) == "string" then autoSelectDialog[name] = { value } elseif type(value) == "function" then autoSelectDialog[name] = { value } elseif type(value) == "table" then -- TODO check all items else autoSelectDialog[name] = nil end end end function Init() FixConfig() common.RegisterEventHandler(OnTalkStarted, "EVENT_TALK_STARTED") common.RegisterEventHandler(OnTalkStopped, "EVENT_TALK_STOPPED") common.RegisterEventHandler(OnActionFailed, "EVENT_ACTION_FAILED_OTHER") common.RegisterEventHandler(OnInteractionStarted, "EVENT_INTERACTION_STARTED") -- Let users disable this feature by removing everything from autoStartDialog if GetTableSize(autoStartDialog) > 0 then common.RegisterEventHandler(OnContextActionsChanged, "EVENT_CONTEXT_ACTIONS_CHANGED") else OnContextActionsChanged = nil end FixConfig = nil Init = nil end -------------------------------------------------------------------------------- Init() --------------------------------------------------------------------------------