Thread Tools Display Modes
07-23-17, 04:56 PM   #1
aallkkaa
A Warpwood Thunder Caller
 
aallkkaa's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2017
Posts: 98
Any way to change ActionBar Page in combat? [SOLVED]

Hi all,

I know you can't (in principle, or maybe at all?) call ChangeActionBarPage(number) in combat but I was wondering if there is some context (e.g. in Restricted Environment) or maybe some alternative method to switching Blizzard's main action bar page, while in combat.

As for the first alternative, the "secure context", I must admit it's likely way way way over my head. but I did try declaring my events-handling frame as inheriting from SecureHandlerBaseTemplate. Like so:
Lua Code:
  1. CreateFrame("Frame", "AlkalineTweaks_EventsFrame", UiParent, "SecureHandlerBaseTemplate");
Checking the result of issecure() immediately afterwards I get that the execution path is insecure, as it was already before declaring the frame. And that's enough to leave me completely lost.
In any case, I think I understand the intention of implementing a Restricted Environment for your addon is so that the frames you create can do things they normaly couldn't, but to themselves only. I'm guessing it doesn't help at all when it comes to using such an environment to control Blizzard frames. Am I right?

As for the second alternative, I tried the following:
Lua Code:
  1. ActionBarDownButton:Click();
And it works just as well as ChangeActionBarPage(number), i.e. fine out of combat, not at all in combat.

For further clarification, I was trying to do these calls in response to ACTIONBAR_UPDATE_STATE and after checking GetActionBarPage() ~= 1, to bring it back to 1.
I have macros to switch between page 1 and 2 through 4, which work fine in combat. Furthermore, the macro bellow also works fine in combat:
Code:
/cast Some Spell
/changeactionbar 1
I'm just trying to find a way to do just that without having to write a macro for everything I place in a button on those three pages.

Any thoughts would be greatly appreciated!

Last edited by aallkkaa : 07-30-17 at 05:15 AM. Reason: Sollution given.
  Reply With Quote
07-25-17, 09:54 AM   #2
Rainrider
A Firelord
AddOn Author - Click to view addons
Join Date: Nov 2008
Posts: 454
SecureStateDriver
  Reply With Quote
07-25-17, 12:35 PM   #3
aallkkaa
A Warpwood Thunder Caller
 
aallkkaa's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2017
Posts: 98
Thanks a lot for that reference, Rainrider!

It's all still very confusing for me at the moment, but that might actually lead me somewhere. Incidentally... do you know this can be done or were you just hinting at it because you know maybe it can?...

Anyway, in the little time I've spent reading that page, it got me to another one ( SecureActionButtonTemplate ), where it is apparently said (or maybe I'm misreading) that there is actually an "actionbar" type of attribute exactly for the purpose of changing the actionbar page (not just checking its state).

Anyway, I feel like I'll still have to learn a big bit before I get this right, but it does feel like I'm moving in the right direction now. Thanks!

And ... more thoughts still welcome!
  Reply With Quote
07-26-17, 02:53 AM   #4
Rainrider
A Firelord
AddOn Author - Click to view addons
Join Date: Nov 2008
Posts: 454
If you want to change bar pages based on user input, then use the SecureActionButtonTemplate. Else you use the SecureStateDriver. You could take a look at Dominos code for reference.
  Reply With Quote
07-27-17, 01:34 AM   #5
aallkkaa
A Warpwood Thunder Caller
 
aallkkaa's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2017
Posts: 98
Yes, I want ro change bar pages based on user input, so yeah, I'm trying to look at SecureActionButtonTemplate now.

Dominos does change action bar pages in reaction to my macros, including during combat lockdown. Or at least so it seems. Maybe it only catches the call to change the page (I can easily do that myself, through the ACTIONBAR_PAGE_CHANGED event).

Assuming Dominos changes the actual page on the Blizzard ActionBar (and then reflects that change in its own bars), it's still not very obvious to me how it does it.
Looking for "Template" in the Dominos folder, I got four different templates being used:
- ActionBarButtonTemplate;
- SecureHandlerStateTemplate;
- SecureActionButtonTemplate;
- SecureHandlerShowHideTemplate.
And that's just for the files in the bars and core subfolders. There's more in libs and in plugins ... SecureActionButtonTemplate but I'm unsure that's "the one" and "only" one needed for the task. SecureHandlerStateTemplate appears two or three times.

RegisterStateDriver also makes appearances in Dominos. Three "states" (if I'm using the correct term) that I think may be of interest to me would be:
- 'page';
- state - yes, it's a variable - I'll have to go chasing it around the code to understand its workings, I guess;
- 'combat'.

Seems like a lot of time trying to figure out how Dominos does something that might or not be what I want to do.
Dominos is of course a much much much more ambitious project than my own (and coded by someone much much much more knowledgeable too) and I don't really have the time to read and understand all the relevant parts at the moment. Maybe I will when I get some more spare time.

In the meanwhile, I found another ActionBars addon, called Paging. It serves a purpose similar to what I want to perform but it uses the modifier keys (ctrl, shift, alt) to keep on an alternate page only for as long as yu keep that modifier key pressed.
That's not what I want. Currently, I press one of the keys E, R or T and the page swaps; I then have to press said key again to swap back to 1; sometimes, when reaction times are critical, I get lost about which page I'm on). The abilities one pages 2 through 4 are mostly situational ones and though very useful in those situations, it's most often than not a case of use once and not again for a long time... Making them available only while certain modifier key is down is something that I acomplished before with keybindings (to the bars) for a long time, and I wasn't happy.
Press one key (no need to be doing hand-contortionism to hold a modifier all along) and I have a new set of buttons at my disposal, press the one I want and (the part currently missing) forget about it. This is what I'm trying to do.
TL;DR: Paging is an addon which seems to be much simpler in scope than Dominos and I might get enough information from it. It is BTW ... outdated, from 6.0.3 if I remember correctly. Will have to test whether it still works.

Once again, thanks for the thoughts, RainRider! Keep them coming!
  Reply With Quote
07-27-17, 01:44 PM   #6
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
I can show you how to do this using a combination of SecureHandlerBaseTemplate and SecureActionButtonTemplate. Since the default action bar doesn't use these templates (they are only for addons), I'm not sure you can get around the fact that you would need a custom action bar for this. You would at least need to create a hidden action bar that simply hijacks the regular action bar bindings to make this work for the default UI... to my knowledge.
__________________

Last edited by MunkDev : 07-28-17 at 09:11 PM.
  Reply With Quote
07-27-17, 04:20 PM   #7
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
I tinkered a bit more with this and you can actually do the same thing with just one button that uses a state driver, a wrapped PreClick and a custom identifier passed as the 'button' argument to the click script.

This solution only uses one secure button, does what you want, and the only drawback is that it won't trigger the first time if you're on a swap page when changing key bindings. This method also works when changing key bindings in combat.

Lua Code:
  1. local header = CreateFrame('Button', 'AllInOneActionButton', nil, 'SecureHandlerBaseTemplate, SecureHandlerStateTemplate, SecureActionButtonTemplate')
  2. header:RegisterForClicks('AnyUp', 'AnyDown') -- process both press and release of an arbitrary key
  3. header:SetAttribute('action', 1) -- this attribute will be used in conjunction with 'actionbar' to swap the bar back
  4.  
  5. do  -- generate a state driver condition with generic values to ensure any bar change pushes an update.
  6.     -- the actual bar ID check is done in the _onstate-actionpage function body instead.
  7.     -- this method seems to work regardless of API changes that cause
  8.     -- some of these macro conditions to shift around on certain specs.
  9.     local conditionBase, driver = '[%s] %d; ', ''
  10.     local conditions = {
  11.         ----------------------------------
  12.         'vehicleui', 'possessbar', 'overridebar', 'shapeshift',
  13.         'bar:2', 'bar:3', 'bar:4', 'bar:5', 'bar:6',
  14.         'bonusbar:1', 'bonusbar:2', 'bonusbar:3', 'bonusbar:4'
  15.         ----------------------------------
  16.     }
  17.     -- concatenate conditions into a long macro condition that can be
  18.     -- evaluated by the state driver and send updates to the header.
  19.     -- e.g. "[vehicleui] 1; [possessbar] 2; [overridebar] 3;" etc.
  20.     for i, macroCondition in ipairs(conditions) do
  21.         driver = driver .. conditionBase:format(macroCondition, i)
  22.     end
  23.     ----------------------------------
  24.     -- append the list for the default bar (1) when none of the conditions apply.
  25.     driver = driver .. (#conditions + 1)
  26.  
  27.     -- register the state driver to handle page updates
  28.     RegisterStateDriver(header, 'actionpage', driver)
  29. end
  30.  
  31. -- set up the _onstate-actionpage function body that sets the new actionpage ID.
  32. -- this is a rewrite of the response in ActionBarController_UpdateAll.
  33. -- state drivers use the _onstate-<name> attribute when looking for the function body to run.
  34. -- 'newstate' is the argument passed to all state drivers expressing the outcome
  35. -- of the registered condition, but in this case it's overwritten by the page calculation.
  36. -- note that all code in a restricted environment is written as strings and compiled in
  37. -- real time using loadstring(), to ensure you don't have access to unrestricted API.
  38. header:SetAttribute('_onstate-actionpage', [[
  39.     -- update actionpage
  40.     if HasVehicleActionBar() then
  41.         newstate = GetVehicleBarIndex()
  42.     elseif HasOverrideActionBar() then
  43.         newstate = GetOverrideBarIndex()
  44.     elseif HasTempShapeshiftActionBar() then
  45.         newstate = GetTempShapeshiftBarIndex()
  46.     elseif GetBonusBarOffset() > 0 then
  47.         newstate = GetBonusBarOffset() + 6
  48.     else
  49.         newstate = GetActionBarPage()
  50.     end
  51.     self:SetAttribute('actionpage', newstate)
  52.  
  53.     -- update bindings
  54.     self:ClearBindings()
  55.     for i=1, 12 do
  56.         local key = GetBindingKey('ACTIONBUTTON' .. i)
  57.         if key then
  58.             self:SetBindingClick(false, key, self, tostring(i))
  59.         end
  60.     end
  61. ]])
  62.  
  63. -- wrap the preclick script on the header
  64. -- use the inherent 'button' argument to tell the header
  65. -- which action button you want to simulate.
  66. -- since PreClick runs before OnClick, you're able to change
  67. -- what the button should do while you're pressing it.
  68. header:WrapScript(header, 'PreClick', [[
  69.     if down then
  70.         -- set up the downpress attributes to perform the action first
  71.         self:SetID(tonumber(button)) -- see ActionButton_CalculateAction
  72.         self:SetAttribute('type', 'action')
  73.     else
  74.         -- set the type to actionbar on release, which swaps the bar back
  75.         self:SetAttribute('type', 'actionbar')
  76.     end
  77.     self:CallMethod('OnButtonPressed', down, button)
  78. ]])
  79.  
  80. -- finally, execute the actionpage response on load,
  81. -- to set the correct action page and your override bindings.
  82. -- 'Execute' is included in the SecureHandlerBaseTemplate,
  83. -- and allows you to execute code in a restricted environment.
  84. header:Execute(header:GetAttribute('_onstate-actionpage'))
  85.  
  86. -- this function is called from the preclick script,
  87. -- which visually indicates a button press on your bar.
  88. -- this part isn't functionally necessary,
  89. -- it just makes it look like you're pressing regular buttons,
  90. -- when you're actually pressing your hidden button.
  91. function header:OnButtonPressed(down, id)
  92.     local button = GetActionButtonForID(id)
  93.     if button then
  94.         if down and button:GetButtonState() == 'NORMAL' then
  95.             button:SetButtonState('PUSHED')
  96.         elseif not down and button:GetButtonState() == 'PUSHED' then
  97.             button:SetButtonState('NORMAL')
  98.         end
  99.     end
  100. end
__________________

Last edited by MunkDev : 07-28-17 at 09:43 PM.
  Reply With Quote
07-30-17, 12:44 AM   #8
aallkkaa
A Warpwood Thunder Caller
 
aallkkaa's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2017
Posts: 98
Wow, thank you very very very much, MunkDev! This is working perfectly!
And I reckon it would have took me about a couple years to get all that down on my own! That was really awesome and kind of you! Again, thank you!
I'm enjoying the fruits of it already. And still looking marvelled at little parts of your code to try and understand it better - I will probably break it every so often while testing stuff with it...
I'd read about this stuff, wrapping-scripts in restriced environments, using long strings instead of functions in restricted environments, several other cool "tricks" in there, but it all seemed so blurry to me (still does for a big part). But seeing it all come together for one (seemingly) simple task is really cool!

Again, thank you, you rock!


Also, for the time being, I'll only be using this in a personal addon, which I don't intend to publish in the near future (or possibly ever). But if I do publish it sometime, will it be alright to include your snippet in it?
  Reply With Quote
07-31-17, 11:32 AM   #9
MunkDev
A Scalebane Royal Guard
 
MunkDev's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2015
Posts: 431
Feel free to release this as your own, because I certainly won't. I tried to put comments on everything that might be unclear, because I also had these struggles when I first started with addon development. There are only a few cases for beginners to look at and they are often not that clear and concise.

In Iriel’s Field Guide to Secure Handlers the documentation on restricted environments and how it all works is pretty comprehensive, but under "Examples", all that's said is this:
There are now a number of AddOns in the wild which use the new SecureHandlers
mechanisms.
For someone who has no experience in coding these things, it can definitely be daunting to just open, for example, LibActionButton and figure out the details of how it all works.
__________________

Last edited by MunkDev : 07-31-17 at 11:35 AM.
  Reply With Quote
08-01-17, 05:12 AM   #10
aallkkaa
A Warpwood Thunder Caller
 
aallkkaa's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2017
Posts: 98
Thank you!

Yes, I did do a quick read of Iriel's Field Guide to Secure Handlers and though I found it very well written and easy to follow, I still had many doubts in the end, regarding how to bring it all together to achieve a task. And yes, the Examples section was rather erm shorter than I expected...

Your comments on the code are very very very helpful. Matter of fact, they are so helpful I got a little urge to mess about them, just for the sake of experimenting...
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Any way to change ActionBar Page in combat?

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off