WoWInterface

WoWInterface (https://www.wowinterface.com/forums/index.php)
-   General Authoring Discussion (https://www.wowinterface.com/forums/forumdisplay.php?f=20)
-   -   question about key bindings XML LUA (https://www.wowinterface.com/forums/showthread.php?t=45954)

shalom69 03-06-13 02:34 PM

question about key bindings XML LUA
 
Hey guys I have a question about setting key bindings.

I have a frame in my addon set some key bindings OnLoad with the following lines

in my XML file

Code:

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ .. \FrameXML\UI.xsd">
        <Frame name="BootUpFrame" hidden="true">
                <Scripts>
                        <OnLoad        function="Bindings_OnLoad"/>
                </Scripts>
        </Frame>
</Ui>

then the function at the top of my addon

Code:

function Bindings_OnLoad(self)
        SetBindingMacro("k", "Assist Leader")
end

from what I understand this should set my key "k" to macro Assist Leader which does not work in game

I tried this code in my .lua

Code:

function Bindings_OnLoad(self)
        SetBindingMacro("k", "Assist Leader")
        action = GetBindingByKey("k")
        print(action)
end

which outputs

"MACRO Assist Leader"

which indicates at least at the beginning it's bound

also tried

Code:

function Bindings_OnLoad(self)
        success=SetBindingMacro("k", "Assist Leader")
        print(success)
end

which output "1" which means it did the keybind according to API guides

For reference this is basically my multibox addon and it changes a bunch of bindings if I have it activated. If I'm doing arena or whatever I use different keybinds from when I multibox and run PVE.

l must be something simple I'm missing. anyone care to explain?

Phanx 03-06-13 04:12 PM

1. Your "action" and "success" variables should be local, not global. You should get in the habit of making everything local, and only making things global when it's actually required, which is almost never. Leaked globals cause unnecessary taint, break other addons, or even break the whole UI.

2. I believe key bindings need to be specified in uppercase, so "K" not "k".

3. Make sure you actually have a macro named "Assist Leader" with that exact spelling, capitalization, and spacing.

4. You probably need to wait until PLAYER_LOGIN or at least your addon's own ADDON_LOADED to change bindings, since at the OnLoad point, saved bindings might not be loaded yet, in which case you're setting your binding, and then saved bindings load and overwrite yours.

5. You should probably use override bindings instead of regular, permanent bindings. Override bindings exist only for the session, so they won't mess with the user's regular bindings. They also (as their name might imply) override regular bindings, so you don't need to unset any regular bindings that would otherwise conflict with your addon bindings.

6. Unless you really love XML, don't use it; it's unnecessary, verbose, ugly, annoying to work with directly, and makes it (basically) impossible to keep all of your related code together. Your existing XML and Lua code can be entirely replaced with this Lua code:

Code:

local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_LOGIN")
f:SetScript("OnEvent", function(self, event, ...)
    SetOverrideBinding(self, nil, "K", "MACRO Assist Leader")
end)


shalom69 03-06-13 05:11 PM

thanks for the reply dude.

I'm not at home atm so I can't check but at first glance looks like the lowercase "k" may be a culprit. I'll check and let you know when I am at my WOW client.

The syntax on the macro name is for sure good, that's the first thing I checked and I triple checked. The reason I'm using XML is because the dude in my stupid WOW addon book suggested it, although I thought it odd that most of the current addons I looked at omitted the addon.xml file completely.

The weird thing and what spurred me to post here to ask is the fact that

Code:

        action = GetBindingByKey("k")
        print(action)

spit out 2 different responses. When I included it in the OnLoad function the output for the variable action from the WOW client on my chat was "MACRO Assist Leader" as soon as I loaded

5 seconds later when I copy and pasted the exact same syntax manually with /run variable action printed nil.


In both instances it was a lowercase k since I copy and pasted to make sure the syntax was identical. I did find screwing around that the binding names are all in capitals when output from the client so there might be something to the capitalization. Maybe the game does think it successfully binds to the "k" but in fact needs a capital "K." Like I said I'll check and get back to you.

If I can do the entire thing in Lua then that works for me too. One thing that was good about using xml is that the book I am using showed how to bind the keys and get them to show up in the default WOW keybindings menu. Is there a way to do that with lua as well?

Your advice on using local for everything is noted. As I noted in the other thread, it's monkey see monkey do at this point as I'm learning. The examples I saw didn't specify "local action" and "local success" so monkey didn't type that. Semantically, "local" means only applying to that particular addon right? You're right that there's nothing in my addon that needs to be global in that case.

shalom69 03-06-13 07:08 PM

OK Phanx

Well the "k" wasn't the issue. Lowercase binds fine. We didn't manage to isolate the problem, it doesn't seem to me that the addon would be able to spit out a variable before it's loaded. I ended up using your workaround of registering the PLAYER_LOGIN and running the entire thing in lua

cheers and thanks for the input and your advice on local variables

Phanx 03-06-13 09:28 PM

Quote:

Originally Posted by shalom69 (Post 274092)
One thing that was good about using xml is that the book I am using showed how to bind the keys and get them to show up in the default WOW keybindings menu. Is there a way to do that with lua as well?

You still need a dedicated Bindings.xml file to add a binding to the default Key Bindings window, but that has to be separate from any other XML content you may have (like creating frames) so you may as well not have any other XML content.

If you use a Bindings.xml file, you do not need to (and should not) call SetBinding manually from your addon. Once the user binds a key in the Key Bindings window, WoW itself will handle assigning that binding at login.

Quote:

Originally Posted by shalom69 (Post 274092)
Your advice on using local for everything is noted. As I noted in the other thread, it's monkey see monkey do at this point as I'm learning. The examples I saw didn't specify "local action" and "local success" so monkey didn't type that. Semantically, "local" means only applying to that particular addon right? You're right that there's nothing in my addon that needs to be global in that case.

That's why I always yell at people for posting code with global variables. Even if they only mean it as a "quick example", other people may not realize it's not code that should be copied and pasted as-is.

Semantically, "local" means the variable only exists in that particular scope. If you write "local var = 2" at the top of your file, then you can access the variable "var" anywhere in that file to get the value 2. If you write the same line inside a function, then the variable only exists inside that function. If you define a variable as local inside a loop, it only exists inside the loop, etc.

A variable defined at the top of a function is available inside a loop that's also inside the same function. You can access variables that are local to higher scopes, but not to lower scopes. Imagine a set of Russian nesting dolls made out of one-way mirror glass. You can see out of them, but not into them. If you're in the 3rd doll from the oustide, you can see your own doll, and the two bigger dolls, but you can't see any dolls that might be inside yours.

Here are some examples:

Code:

local var = 2

local function PrintVar()
    local var = "two"
    print(var)
end

print(PrintVar())
print(var)

... will print "two" and then 2, not 2 and 2, and not "two" and "two".

Code:

local unit = "player"

for i = 1, GetNumSubgroupMembers() do
    print(unit)
    local unit = "party"..i
    print(unit)
end

print(name)

... will (in a full party) print "player", "party1", "party2", "party3", "party4", and then "player" again.

Now, if you remove the "local" declaration inside the loop:

Code:

local unit = "player"

for i = 1, GetNumSubgroupMembers() do
    print(unit)
    unit = "party"..i -- no longer local to this scope
    print(unit)
end

print(name)

... then you're using the "unit" variable from the higher-up scope, so you'll get "player", "party1", "party2", "party3", "party4" -- and then "party4" again instead of "player" like in the previous example.

shalom69 03-07-13 05:04 AM

Ok good explanation about local. I knew that declaring local variables at the start of the addon defined them for that addon but your explanation makes it clearer when I declare local variables inside a bunch of if-then statements.

I actually considered in passing that the examples for bindings may have something to do handling functions in different xml and lua files, so I thought to minimize errors the first time I was coding it by following the examples exactly. Obviously knowing the semantics precisely makes that unnecessary for me so I appreciate you typing that all out. To your point I think folks who post examples and do tutorials need to assume that some portion of the readers do not have any formal training and may not be aware of noob things you learn the first week of programming class

I took a look at the bindings.xml

in the xml file

Code:

<Binding header="MYADDON" name="ASSISTLEADERMACRO">?????????()</Binding>
and in the lua

Code:

BINDING_HEADER_MYADDON= "MyAddon"
BINDING_NAME_ASSISTLEADERMACRO= "Assist Leader"

creates the category "MyAddon" and associates a button to a string named "Assist Leader" in WOW default Key Bindings. Using just bindings.xml and .lua how do I assign the actual macro assist actions to the string?

myrroddin 03-07-13 05:31 AM

I am not going to comment or reply about your code because I never found XML useful enough to even bother learning.

What I am adding to the discussion is an example case in support of Phanx's suggestion of using override bindings. In my AddOn SmartRes2, the key binds are overrides. You will not see them in the keybindings window (Esc, Keybindings). Because SmartRes2 deals with all things raising players from the dead after combat, here's what a player can do:
  1. Use the Keybindings screen (Esc, Keybindings) to set a key or combination of keys to perform action X while in combat
  2. Bind the same key or combination of keys in SmartRes2, which will override the bindings when the player is out of combat
This means the same keybinds can be used both in and out of combat for completely different purposes.

shalom69 03-07-13 05:47 AM

I gotcha, you are saying that you register PLAYER_REGEN_DISABLED and then SetBindingMacro a bunch of stuff when you flag in and out of combat.

or I guess if SetBinding is protected, you check INCOMBAT FLAGGED when you press a key out of combat you switch it, press then rebind back to combat. something like that.

And then instead of using the default UI Key Binding frame, you make your own frame with the binds or w/e

That's pretty sweet. Your guys' suggestion about using conditional bindings is actually very useful, esp for a multibox with limited keyboard space. I'd still like to know how to work what Phanx was talking about in the bindings.xml and linking the string "Assist Leader" to the actual macro that does the assisting.

myrroddin 03-07-13 01:46 PM

Here's how I did it, using non-Ace3 speak:
Lua Code:
  1. local MyAddOn = ...
  2. local eventFrame = CreateFrame("Frame")
  3. eventFrame:RegisterEvent("PLAYER_LOGIN")
  4. eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
  5. eventFrame:RegiserEvent("PLAYER_REGEN_DISABLED")
  6.  
  7. function MyAddOn:SetBindings()
  8.     -- set your override bindings
  9. end
  10.  
  11. function MyAddOn:ClearBindings()
  12.     -- clear all overrides
  13. end
  14.  
  15. function MyAddOn:PLAYER_LOGIN()
  16.     self:SetBindings()
  17.     eventFrame:UnregisterEvent("PLAYER_LOGIN")
  18. end
  19.  
  20. function MyAddOn:PLAYER_REGEN_ENABLED()
  21.     self:SetBindings()
  22. end
  23.  
  24. function MyAddOn:PLAYER_REGEN_DISABLED()
  25.     self:ClearBindings()
  26. end

shalom69 03-07-13 02:13 PM

Cool man, I guess clearing bindings isn't affected by combat so you can simply dump them to get back to combat bindings

Thanks for the example myrroddin can you explain why you need that first line?

Aanson 03-07-13 05:15 PM

Quote:

Originally Posted by myrroddin (Post 274151)
function Bindings_OnLoad(self)
SetBindingMacro("k", "Assist Leader")
end

Hey. I could be wrong here, but it's my understanding that once you've set a binding in this way, you immediately need to save the binding before it can be used.

Saving the binding transfers the data from ram to disk (WTF\Account\ACCOUNTNAME\bindings-cache.wtf). I've always looked at it as being the equivalent of pressing confirm in the binding UI frame.

The function you'd be after is:

Lua Code:
  1. SaveBindings(index)

... where 'index' is either integer 1 or 2 (account wide or character wide respectively).



Aanson.

Phanx 03-07-13 05:16 PM

The first line in Myrroddin's example should be:

Code:

local MYADDON, MyAddOn = ...
Every addon gets two variables passed into every one of its Lua files. The first is a string containing the addon's folder/TOC name. The second is a table that's local to the addon, so it's accessible only by the addon's files (unless the addon explicitly adds it to the global namespace) and is handy for sharing stuff between your addon's files that don't need to be shared with the rest of the UI.

However, in Myrroddin's example, the first line isn't actually needed at all, and you can just replace all other instances of "MyAddOn" with "eventFrame" -- if you already have a frame (ie. to listen for events) you don't need to define your functions/methods on another table, you can just define them on the frame.

----------

For bindings, if you are using conditional bindings, you use SetOverrideBinding, not SetBinding. Using SetBinding will permanently overwrite the user's normal bindings; using SetOverrideBinding does nto affect them.

Code:

function MyAddOn:SetBindings()
    -- Make sure you're not in combat:
    if InCombatLockdown() then return end

    -- Temporarily bind "K" to the "Assist Leader" macro:
    SetOverrideBinding(self, nil, "K", "MACRO Assist Leader")
end

function MyAddOn:ClearBindings()
    -- Make sure you're not in combat:
    if InCombatLockdown() then return end

    -- (a) Remove the temporary binding for "K" set by this addon:
    SetOverrideBinding(self, nil, "K", nil)

    -- (b) or, remove *all* temporary bindings set by this addon:
    ClearOverrideBindings(self)

    -- Do either (a) or (b), not both!
end

-----

To bind a key to a macro using Bindings.xml, do this:

Code:

<Bindings>
    <Binding name="MACRO Assist Leader" header="MYADDON">
        -- Nothing goes here.
    </Binding>
</Bindings>

Then define your strings like so:

Code:

BINDING_HEADER_MYADDON = "My AddOn"
_G["BINDING_NAME_MACRO Assist Leader"] = "Assist Leader macro"

Global variables (such as the binding name key) can contain spaces; you just need to quote them as strings and explicitly look them up in the _G table instead of looking them up directly.

----------

Edit:

Aason also has a point. You do need to save permanent bindings. However, you don't need to save override bindings, as they don't actually get "saved" like permanent bindings.

When calling SaveBindings(), always use GetCurrentBindingSet() to get the right "index" value so you don't screw with the user's preference to use account-wide or per-character bindings:

Code:

SaveBindings(GetCurrentBindingSet())

myrroddin 03-08-13 12:39 AM

That's not my quote, Aason! LOL anyway...

Yes, once again, Phanx is correct about the first line in my example code. I left out the table part because it didn't seem required to illustrate. Here is an example of that first line as I use it in FeedTillers:
Lua Code:
  1. -- both of these are variables, and can be named anything
  2. -- I could have written local JoeBob, Clyde = ...
  3. -- but the names I chose are more meaningful
  4. -- ADDON is a string derived from FeedTillers.toc
  5. -- L is a table that I use for localization
  6. local ADDON, L = ...
  7.  
  8. local LOCALE = GetLocale()
  9. if LOCALE == "esES" then
  10. L["CLICK_SORT"] = "Clic para alternar entre ordenar por nombre de PNJ o nombre de comida."
  11. L["SHIFT_DOWN"] = "Mayús-clic para ocultar a los Labradores que ya ha alimentado hoy."
  12. L["ToC/Description"] = "Mostrar cuál comida le gusta cada miembro de los Labradores, y si ha completado su misión diaria \"Un plato para...\""
  13.  
  14. elseif LOCALE == "esMX" then
  15. L["CLICK_SORT"] = "Clic para alternar entre ordenar por nombre de PNJ o nombre de comida."
  16. L["SHIFT_DOWN"] = "Mayús-clic para ocultar a los Labradores que ya ha alimentado hoy."
  17. L["ToC/Description"] = "Mostrar cuál comida le gusta cada miembro de los Labradores, y si ha completado su misión diaria \"Un plato para...\""
  18.  
  19. elseif LOCALE == "itIT" then
  20. L["CLICK_SORT"] = "Click sul plugin per elencare i Coltivatori per il nome o per il loro oggetto"
  21. L["SHIFT_DOWN"] = "Tieni premuto il tasto <Shift> per nascondere i Contivatori giŕ sfamati"
  22. L["ToC/Description"] = "Indica quale Coltivatore, che cibo gli piace, e se lo hai giŕ sfamato oggi con la quest \"Un piatto per...\""
  23.  
  24. elseif LOCALE == "ptBR" then
  25. -- L["CLICK_SORT"] = ""
  26. -- L["SHIFT_DOWN"] = ""
  27. -- L["ToC/Description"] = ""
  28.  
  29. elseif LOCALE == "frFR" then
  30. -- L["CLICK_SORT"] = ""
  31. -- L["SHIFT_DOWN"] = ""
  32. -- L["ToC/Description"] = ""
  33.  
  34. elseif LOCALE == "deDE" then
  35. -- L["CLICK_SORT"] = ""
  36. -- L["SHIFT_DOWN"] = ""
  37. -- L["ToC/Description"] = ""
  38.  
  39. elseif LOCALE == "ruRU" then
  40. -- L["CLICK_SORT"] = ""
  41. -- L["SHIFT_DOWN"] = ""
  42. -- L["ToC/Description"] = ""
  43.  
  44. elseif LOCALE == "zhCN" then
  45. --@localization(locale="zhCN", format="lua_additive_table)@
  46. elseif LOCALE == "zhTW" then
  47. -- L["CLICK_SORT"] = ""
  48. -- L["SHIFT_DOWN"] = ""
  49. -- L["ToC/Description"] = ""
  50.  
  51. else
  52. L["CLICK_SORT"] = "Click the plugin to sort by Tiller name or item name"
  53. L["SHIFT_DOWN"] = "Hold the <Shift> key and click to hide already fed Tillers"
  54. end

Please note that line 45 in the above code refers to Curseforge's and WoWAce's Localization app keyword substitutions.

shalom69 03-08-13 07:14 AM

ok thanks for the replies. gj guys

if I have any more questions I'll jump back on this thread after trying out what's been suggested and learning some of the things you brought up

Phanx 03-10-13 05:47 PM

Quote:

Originally Posted by shalom69
looking at this code (code isn't important except the highlighted part)

Code:

if (msg == "INCOMBAT FLAGGED") then
    if sender == UnitName("player") then
        local clone, target
        for clone, target in pairs(cloneTargetTable) do
            if target == "NONE" or nil then
                RaidNotice_AddMessage(RaidWarningFrame, clone .." HAS NO TARGET", ChatTypeInfo["RAID_WARNING"])
            end
        end
    elseif not UnitAffectingCombat("player") then
        RaidNotice_AddMessage(RaidWarningFrame, sender .." HAS ENTERED COMBAT", ChatTypeInfo["RAID_WARNING"])                       
    end   
end

is there a way to declare the variables one further level in?

for example I know you can do

Code:

local _,target = strsplit(" ", msg, 2)
and declare it right in the operation you're doing. just that quick question,

thanks

You don't need to declare those variables as local at all, because they are automatically defined as local to the scope of the loop when used in a "for" statement:

Code:

for k, v in pairs(t) do
  -- k and v exist here
end
-- but not here

Code:

for i = 1, 10 do
  -- i exists here
end
-- but not here


shalom69 03-10-13 06:49 PM

gotcha. appreciate the information Phanx

I see

Code:

"for i= 1, 10 do local k, v = etc"
all the time. Are only the variables explicitly part of the for = statement declared locally

Phanx 03-11-13 02:40 AM

Yes. The "=" isn't really important; only the "for ... do" parts. Any variables defined between "for" and "do" are automatically local to the scope that starts with "for" and ends with the corresponding "end".

Variables used in other types of loops are not automatically local, eg:

Code:

local i = 1
while i < 10 do
    -- i exists here
    i = i + 1
end
-- and it still exists here

Code:

local i = 1
repeat
    -- i exists here
    i = i + 1
until i == 10
-- and still here

Also:

Code:

local i = 1
for i = 1, 3 do
    print(i)
end
print(i)

... will print 1 2 3 1, since the "i" inside the loop is the one declared as part of the "for" statement, while the "i" that exists after the for-loop's "end" statement is the one explicitly defined as "local i" before the loop.

Rilgamon 03-11-13 03:22 AM

I memorized it this way: "do ... end" is a new scope. for, while, etc expand on it.
If your code have variables or functions that run only once during runtime you can put them in a new scope so that they can be removed from memory during the "normal" runtime of your code.

Lua Code:
  1. local setting
  2. do
  3.     local result = false
  4.     local function check()
  5.         return true
  6.     end
  7.     result = check()
  8.     setting = result
  9. end

shalom69 03-11-13 03:24 PM

awesome. thanks for those posts guys


All times are GMT -6. The time now is 11:30 AM.

vBulletin © 2024, Jelsoft Enterprises Ltd
© 2004 - 2022 MMOUI