Fantastic document.... tempts me to brush off my old FORTRAN, COBOL and Perl skills and come out of retirement!!
Fantastic document.... tempts me to brush off my old FORTRAN, COBOL and Perl skills and come out of retirement!!
Bill Magill Mac Player Founder/Lifetimer
Old Timers Guild - Gladden
Sr. Editor LOTRO-Wiki.com
Val - Man Minstrel (108)
Valalin - Dwarf Minsrel (71)
Valamar - Dwarf Hunter (120)
Valdicta - Dwarf RK (107)
Valhad - Elf LM (66)
Valkeeper - Elf RK (87)
Valwood - Dwarf RK (81)
Valhunt - Dwarf Hunter (71)
Valanne - Beorning (105)
Ninth - Man Warden (66)
"Laid back, not so serious, no drama.
All about the fun!"
This installment deals with one of the mildly confusing UI elements for new developers, the ScollBar control. This control can either be used as a stand alone slider control or can be bound to any class that is derived from a Turbine class derived from the ScrollableControl. The reason the previous sentence seems redundant by specifying "derived from" twice is that user classes should not derive directly from the ScrollableControl class, since using the SetParent() method of an instance of this class directly will cause the client to crash, so instead either derive from or create instances of any of the controls listed in the API documentation as being derived from ScrollableControl, such as Label, Button, CheckBox, TextBox, etc.
There are three common ways in which I tend to use a scrollbar. First as a stand alone scrollbar to generate a scrollable value, such as an opacity setting in an options dialog. Second as an unbound scrolling control for a viewport. Third as a bound control for scrolling one of Turbines scrollable controls. We will explore examples of each.
When a scrollbar is unbound, the user has to set the limits, value and scroll event handlers for the control. When a control is bound, the limits, value and scroll event handlers are unavailable programatically and attempting to set them or read them will generate errors. The Members and Methods affected by this are:
GetMaximum(), GetMinimum(), GetValue(), SetMaximum(), SetMinimum(), SetValue() and ValueChanged.
Our first sample is an unbound slider that will control the Opacity of it's parent window. This example is very common for option panels as unbound scrollbars can be used to control or set just about any numeric value. There is one minor issue with the current Turbine implementation, it doesn't properly support negative bounds so if your lower limit represents a negative, use an offset for the bounds and adjust the value accordingly. For instance, to generate -10 to 10, you would actually set the bounds to 0 to 20 and then subtract 10 when reading the value and add 10 when setting the value.
Note, I set the minimum opactity to .1 so that you wouldn't acccidentally scroll your window to invisibility and then lose itCode:import "Turbine" import "Turbine.UI" import "Turbine.UI.Lotro" scrollWindow=Turbine.UI.Lotro.Window(); scrollWindow:SetSize(400,400); scrollWindow:SetPosition((Turbine.UI.Display:GetWidth()-scrollWindow:GetWidth())/2,(Turbine.UI.Display:GetHeight()-scrollWindow:GetHeight())/2); scrollWindow:SetText("Scrollbar Sample"); -- create a caption for our scrollable value opacityCaption=Turbine.UI.Label(); opacityCaption:SetParent(scrollWindow); opacityCaption:SetSize(120,20); opacityCaption:SetPosition(10,45); opacityCaption:SetText("Opacity"); -- create the actual scrollbar and initialize it scrollbar1=Turbine.UI.Lotro.ScrollBar(); scrollbar1:SetParent(scrollWindow); scrollbar1:SetOrientation(Turbine.UI.Orientation.Horizontal); scrollbar1:SetPosition(opacityCaption:GetLeft()+opacityCaption:GetWidth()+5,opacityCaption:GetTop()+4); scrollbar1:SetSize(scrollWindow:GetWidth()-10-scrollbar1:GetLeft(),12); -- set width to window-border-left, 12 pixel height is standard for "Lotro" style horizontal scrollbars scrollbar1:SetBackColor(Turbine.UI.Color(.1,.1,.2)); -- just to give it a little style scrollbar1:SetMinimum(10); scrollbar1:SetMaximum(100); -- we will divide the value by 100 to get our 0-1 scale with 2 decimal places local initValue=scrollWindow:GetOpacity()*100; -- retrieve the initial value and convert it to our scroll scale if initValue<10 then initValue=10 end; -- it's good practice to make sure you initialize your control to a valid value based on the min/max values you set scrollbar1:SetValue(initValue); -- set the ValueChanged event handler to take an action when our value changes, in this case, change the current window's opacity scrollbar1.ValueChanged=function() scrollWindow:SetOpacity(scrollbar1:GetValue()/100); -- we devide the 0-100 scrollbar scale to get our 0-1 opacity scale with 2 decimal places end scrollWindow:SetVisible(true);
That's about all there is to a simple scrollable value control. Our next sample is a bit more complex because it will provide a scrollable viewport - a fairly common need. For this example, we will create a grid of some of the compass map 200x200 images and allow the user to scroll around them with a 200x200 viewport. This will demonstrate how to use both a horizontal and a vertical scrollbar.
Now we will recreate that same viewport using a pair of bound controls and a listbox.Code:import "Turbine" import "Turbine.UI" import "Turbine.UI.Lotro" scrollWindow=Turbine.UI.Lotro.Window(); scrollWindow:SetSize(400,400); scrollWindow:SetPosition((Turbine.UI.Display:GetWidth()-scrollWindow:GetWidth())/2,(Turbine.UI.Display:GetHeight()-scrollWindow:GetHeight())/2); scrollWindow:SetText("Scrollbar Sample"); -- create a caption for our viewport viewportCaption=Turbine.UI.Label(); viewportCaption:SetParent(scrollWindow); viewportCaption:SetSize(120,20); viewportCaption:SetPosition(10,45); viewportCaption:SetText("Viewport:"); -- create the viewport control all it needs is size and position as it is simply used to create viewable bounds for our map viewport=Turbine.UI.Control(); viewport:SetParent(scrollWindow); viewport:SetSize(200,200); viewport:SetPosition(viewportCaption:GetLeft()+viewportCaption:GetWidth()+5,viewportCaption:GetTop()); -- create the map content for our viewport, again, it only needs size and position as it is just a container for the grid of images viewport.map=Turbine.UI.Control(); viewport.map:SetParent(viewport); -- set the map as a child of the viewport so that it will be bounded by it for drawing purposes viewport.map:SetSize(1000,800); -- we'll use a 5x4 grid but this obviously could be expanded, or even set up as a recycled array of controls viewport.map:SetPosition(0,0); -- we'll start off in the upper left -- create the grid of map tiles mapTiles={} local hIndex,vIndex; for hIndex=1,5 do mapTiles[hIndex]={}; for vIndex=1,4 do mapTiles[hIndex][vIndex]=Turbine.UI.Control() mapTiles[hIndex][vIndex]:SetParent(viewport.map); mapTiles[hIndex][vIndex]:SetPosition((hIndex-1)*200,(vIndex-1)*200) mapTiles[hIndex][vIndex]:SetSize(200,200); end end -- set the tile images to a group of images I happen to know from the misty mountains area... mapTiles[1][1]:SetBackground(0x4101db9d); mapTiles[1][2]:SetBackground(0x4101db9c); mapTiles[1][3]:SetBackground(0x4101db9b); mapTiles[1][4]:SetBackground(0x4101db9a); mapTiles[2][1]:SetBackground(0x4101dba2); mapTiles[2][2]:SetBackground(0x4101dba1); mapTiles[2][3]:SetBackground(0x4101dba0); mapTiles[2][4]:SetBackground(0x4101db9f); mapTiles[3][1]:SetBackground(0x4101dba7); mapTiles[3][2]:SetBackground(0x4101dba6); mapTiles[3][3]:SetBackground(0x4101dba5); mapTiles[3][4]:SetBackground(0x4101dba4); mapTiles[4][1]:SetBackground(0x4101dbab); mapTiles[4][2]:SetBackground(0x4101dbaa); mapTiles[4][3]:SetBackground(0x4101dba9); mapTiles[4][4]:SetBackground(0x4101dba8); mapTiles[5][1]:SetBackground(0x4101dbaf); mapTiles[5][2]:SetBackground(0x4101dbae); mapTiles[5][3]:SetBackground(0x4101dbad); mapTiles[5][4]:SetBackground(0x4101dbac); -- create the vertical scrollbar for our viewport vscroll=Turbine.UI.Lotro.ScrollBar(); vscroll:SetParent(scrollWindow); vscroll:SetOrientation(Turbine.UI.Orientation.Vertical); vscroll:SetPosition(viewport:GetLeft()+viewport:GetWidth(),viewport:GetTop()); vscroll:SetSize(12,viewport:GetHeight()); -- set width to 12 since it's a "Lotro" style scrollbar and the height is set to match the control that we will be scrolling vscroll:SetBackColor(Turbine.UI.Color(.1,.1,.2)); -- just to give it a little style vscroll:SetMinimum(0); vscroll:SetMaximum(viewport.map:GetHeight()-viewport:GetHeight()); -- we will allow scrolling the height of the map-the viewport height vscroll:SetValue(0); -- set the initial value -- set the ValueChanged event handler to take an action when our value changes, in this case, change the map position relative to the viewport vscroll.ValueChanged=function() viewport.map:SetTop(0-vscroll:GetValue()); end -- create the horizontal scrollbar for our viewport hscroll=Turbine.UI.Lotro.ScrollBar(); hscroll:SetParent(scrollWindow); hscroll:SetOrientation(Turbine.UI.Orientation.Horizontal); hscroll:SetPosition(viewport:GetLeft(),viewport:GetTop()+viewport:GetHeight()); hscroll:SetSize(viewport:GetWidth(),12); hscroll:SetBackColor(Turbine.UI.Color(.1,.1,.2)); -- just to give it a little style hscroll:SetMinimum(0); hscroll:SetMaximum(viewport.map:GetWidth()-viewport:GetWidth()); -- we will allow scrolling the width of the map-the viewport width hscroll:SetValue(0); -- set the initial value -- set the ValueChanged event handler to take an action when our value changes, in this case, change the map position relative to the viewport hscroll.ValueChanged=function() viewport.map:SetLeft(0-hscroll:GetValue()); end scrollWindow:SetVisible(true);
The only difference to the end user is that the scrollbars have a smaller scale since you are scrolling the entire control and not pixel by pixel. So, if the built in controls can create the same effect with less code, why would we ever want to create our own custom scrollable controls? Well, there are some limitations to the built in scrollable controls, for instance there are situations where it might be desirable to manipulate the scroll position of the control programatically, which you can not do with the built in controls. In other cases, you might want to be able to respond to the value changes but the ValueChanged event will not fire when bound and the GetValue method is not valid when bound (it generates an error when bound). So for simply displaying data to the user, the built-in scrollable controls and bound scrollbars are just fine, but for controls that need to interact with other UI elements, you will need to use unbound scrollbars and control the limits, values and actions yourself.Code:import "Turbine" import "Turbine.UI" import "Turbine.UI.Lotro" scrollWindow=Turbine.UI.Lotro.Window(); scrollWindow:SetSize(400,400); scrollWindow:SetPosition((Turbine.UI.Display:GetWidth()-scrollWindow:GetWidth())/2,(Turbine.UI.Display:GetHeight()-scrollWindow:GetHeight())/2); scrollWindow:SetText("Scrollbar Sample"); -- create a caption for our viewport viewportCaption=Turbine.UI.Label(); viewportCaption:SetParent(scrollWindow); viewportCaption:SetSize(120,20); viewportCaption:SetPosition(10,45); viewportCaption:SetText("Viewport:"); -- create the viewport control all it needs is size and position as it is simply used to create viewable bounds for our map viewport=Turbine.UI.ListBox(); viewport:SetParent(scrollWindow); viewport:SetSize(200,200); viewport:SetPosition(viewportCaption:GetLeft()+viewportCaption:GetWidth()+5,viewportCaption:GetTop()); -- this time we create the map content as rows in the listbox -- create the grid of map tiles mapTiles={}; -- now, if we only wanted to use this as a display, there's really no need to maintain the mapTiles outside of their rows, but it's easier to set/manipulate the background images if we do local hIndex,vIndex; for vIndex=1,4 do mapTiles[vIndex]={}; -- note, this time we have to use the vIndex as the first index since the row item gets created first and contains the column items local tmpRow=Turbine.UI.Control(); -- we will use a control as a container for each row tmpRow:SetParent(viewport); tmpRow:SetSize(1000,200); for hIndex=1,5 do mapTiles[vIndex][hIndex]=Turbine.UI.Control() mapTiles[vIndex][hIndex]:SetParent(tmpRow); mapTiles[vIndex][hIndex]:SetPosition((hIndex-1)*200,0) mapTiles[vIndex][hIndex]:SetSize(200,200); end viewport:AddItem(tmpRow); end -- set the tile images to a group of images I happen to know from the misty mountains area... mapTiles[1][1]:SetBackground(0x4101db9d); mapTiles[2][1]:SetBackground(0x4101db9c); mapTiles[3][1]:SetBackground(0x4101db9b); mapTiles[4][1]:SetBackground(0x4101db9a); mapTiles[1][2]:SetBackground(0x4101dba2); mapTiles[2][2]:SetBackground(0x4101dba1); mapTiles[3][2]:SetBackground(0x4101dba0); mapTiles[4][2]:SetBackground(0x4101db9f); mapTiles[1][3]:SetBackground(0x4101dba7); mapTiles[2][3]:SetBackground(0x4101dba6); mapTiles[3][3]:SetBackground(0x4101dba5); mapTiles[4][3]:SetBackground(0x4101dba4); mapTiles[1][4]:SetBackground(0x4101dbab); mapTiles[2][4]:SetBackground(0x4101dbaa); mapTiles[3][4]:SetBackground(0x4101dba9); mapTiles[4][4]:SetBackground(0x4101dba8); mapTiles[1][5]:SetBackground(0x4101dbaf); mapTiles[2][5]:SetBackground(0x4101dbae); mapTiles[3][5]:SetBackground(0x4101dbad); mapTiles[4][5]:SetBackground(0x4101dbac); -- create the vertical scrollbar for our viewport vscroll=Turbine.UI.Lotro.ScrollBar(); vscroll:SetParent(scrollWindow); vscroll:SetOrientation(Turbine.UI.Orientation.Vertical); vscroll:SetPosition(viewport:GetLeft()+viewport:GetWidth(),viewport:GetTop()); vscroll:SetSize(12,viewport:GetHeight()); -- set width to 12 since it's a "Lotro" style scrollbar and the height is set to match the control that we will be scrolling vscroll:SetBackColor(Turbine.UI.Color(.1,.1,.2)); -- just to give it a little style viewport:SetVerticalScrollBar(vscroll); --note the complete lack of setting minimum, maximum values, initializing the value or creating an action. -- create the horizontal scrollbar for our viewport hscroll=Turbine.UI.Lotro.ScrollBar(); hscroll:SetParent(scrollWindow); hscroll:SetOrientation(Turbine.UI.Orientation.Horizontal); hscroll:SetPosition(viewport:GetLeft(),viewport:GetTop()+viewport:GetHeight()); hscroll:SetSize(viewport:GetWidth(),12); hscroll:SetBackColor(Turbine.UI.Color(.1,.1,.2)); -- just to give it a little style viewport:SetHorizontalScrollBar(hscroll); --note the complete lack of setting minimum, maximum values, initializing the value or creating an action. hscroll.ValueChanged=function() Turbine.Shell.WriteLine("value:"..hscroll:GetValue()) end scrollWindow:SetVisible(true);
The code for this sample is available as a single window plugin on LoTROInterface:
http://www.lotrointerface.com/downlo...nfo.php?id=659
Last edited by Garan; Dec 06 2011 at 01:51 AM.
This is a great thread, and I keep coming back to it Thank you Garan for all the information! I want to dust off my programming skills from years ago and dive into creating my own plugins but have not found the time yet to actually do some coding - soon! Till then I keep reading and learning! Please continue providing information and examples, they are very appreciated.
[charsig=http://lotrosigs.level3.turbine.com/0520a00000007cd7e/01008/signature.png]Talaina[/charsig]
Ugh. I've been doing it wrong this whole time. I didn't know the Unload event is shared between plugins, and that directly setting the Unload event could clobber other plugins' event handlers and vice versa. Fortunately, switching to the method above fixed the problem for me. Thank you for providing this resource.
How did you find out about the Plugins[] element? I couldn't find it documented anywhere.
What I was doing before was something like:
Code:function Turbine.Plugin:Unload() -- save plugin state end
I don't think you were actually clobbering anyone else's unload handlers. After a bit of testing with two plugins in the same apartment, one using Plugins[].Unload to assign its event handler and the other using Turbine.Plugin.Unload to assign its event handler it appears that the Turbine.Plugin object refers only to your plugin instance so each plugin gets a distinct instance of the Turbine.Plugin object. That is, assigning Turbine.Plugins.Unload in plugin "A" is the same as assigning Plugins["A"].Unload and will not overwrite the Unload handler for Plugins["B"]. Of course, this may be something that was changed with the latest updates and may have functioned differently before.
I really don't remember where I learned about the Plugins[] table. It may have been from one of the beta forum threads before Lua was released or it may have been from one of the plugins that I dissected while learning Lua myself. Now that I looked for it, I agree that it doesn't seem to be documented anywhere.
Last edited by Garan; Jan 17 2012 at 10:42 AM.
Here's a test case that demonstrates event handler clobbering.
If you load plugin A, followed by plugin B, and then unload all plugins, only B.plugindata gets generated. If you only load and unload A, then its plugindata gets generated as you'd expect.
A
BCode:savedata = "plugin A save data"; Turbine.Plugin.Unload = function() Turbine.PluginData.Save(Turbine.DataScope.Account, "A", savedata); end
Code:savedata = "plugin B save data"; Turbine.Plugin.Unload = function() Turbine.PluginData.Save(Turbine.DataScope.Account, "B", savedata); end
Furthermore, you can even use this to communicate across plugins! I thought they were supposed to be sandboxed from each other?
A
BCode:Turbine.Plugin.Unload = "have a cooky";
Code:Turbine.Shell.WriteLine(Turbine.Plugin.Unload);
The first part is an interesting anomaly. If you use the code:
in plugin "A" you will see that when loading only plugin "A" or loading it first, the Plugins["A"].Unload overwrites the Turbine.Plugin.Unload handler (you never get a "C.plugindata" file, only the "A.plugindata" file). You can verify that the Turbine.Plugin.Unload handler would fire without the Plugins["A"].Unload handler by commenting out the SetWantsUpdates(true) line. This would imply that they reference the same object. However, if you then use a second Plugin as you did above, the Plugins["A"].Unload handler is not overwritten by the global Turbine.Plugin.Unload from Plugin "B" but the global handler is overwritten (you get a "A.plugindata" file and a "B.plugindata" file but not a "C.plugindata" file. But loading them in reverse order, Plugin "A" overrides the global event handler of Plugin "B" and fires BOTH the global Turbine.Plugin.Unload AND the Plugins["A"].Unload (you get a "C.plugindata" file and a "A.plugindata" file. This is very odd in that how Turbine.Plugin.Unload is affected depends on whether another plugin is previously loaded.Code:savedata = "plugin A save data"; import "Turbine.UI" test1=Turbine.UI.Window() test1.Update=function() if Plugins["A"]~=nil then Plugins["A"].Unload = function() Turbine.PluginData.Save(Turbine.DataScope.Account, "A", savedata); end test1:SetWantsUpdates(false) end end test1:SetWantsUpdates(true) Turbine.Plugin.Unload=function() Turbine.PluginData.Save(Turbine.DataScope.Account, "C", savedata); end
So, if other authors were using the Plugins[].Unload method you were not clobbering them, you were only clobbering plugins that also used the Turbine.Plugin.Unload method. Interesting.
EDIT: After a bit of thought, the below information on environments made me realize what is going on. The Turbine.Plugin.Unload is a global while the Plugins[].Unload is an instance and it resolves to the global when it is not set. heh. I should have seen that sooner
As to the sharing of data, the Turbine object exists in the global environment under the "_G" object. So the Turbine.Plugin object is accessible to all plugins in the same apartment (each apartment gets their own global environment). The variables you normally create are created in your plugin environment which exists in the "_G.authorname.pluginname" environment which protects them from other plugins, but you can share data with any other plugin in your environment by creating variables like "_G.test" and then access them as "test" as long as you didn't create a variable with the same name in your plugin. This can get a little confusing for instance try
You will see that the variable "test" will first try to resolve to a value in the plugin environment but if the plugin environment value is nil it will resolve to the global environment value (or nil if it is nil in both environments). In Lua you can also explicitly change the environment that a function executes in but that is beyond the scope of this thread (check out the debug window from Moormap or Cards if you want to see a sample of this) but there is a nice write up at http://www.lua.org/manual/5.1/manual.html#2.9Code:_G.test="first entry" Turbine.Shell.WriteLine("test= "..tostring(test)) Turbine.Shell.WriteLine("_G.test= "..tostring(_G.test)) test="second entry" Turbine.Shell.WriteLine("test= "..tostring(test)) Turbine.Shell.WriteLine("_G.test= "..tostring(_G.test)) test=nil Turbine.Shell.WriteLine("test= "..tostring(test)) Turbine.Shell.WriteLine("_G.test= "..tostring(_G.test))
Last edited by Garan; Jan 17 2012 at 01:48 PM.
Thank you for this, Garan. It is immensely useful.
I have a question about what should be done in the unload() handler, in a best practices, good citizen sense. You mention very briefly:
function UnloadMe()
-- release any event handlers, callbacks, commands
-- save any data that needs saving
end
What I have seen a lot of plugins do is save their data, but not release event handlers, callbacks or commands. This appears quite deliberate - going so far as not having RemoveCallback() in the code base - and in extremely well-written plugins such as Kragenbars. Nor is Kragenbars alone in this, it is wide-spread practice not to call RemoveCallback and not to remove the created command again.
My question is: Can attempting to release callbacks and commands during unload actually destabilize things, as it might step on LotRO's trash collection? Or is it simply unnecessary because of the trash collection?
There has to be a reason I don't see anything but save calls in unload(). I just took on maintenance of WardenIndicator, and added all this housekeeping to unload(). I want to make sure I'm not making things worse.
Releasing callbacks and commands during unload will not destabilize anything. On the contrary, depending on the environment to clean up these objects can lead to system instability. I don't recall all of the specifics and haven't tested it in quite a while, but there was at least one bug in the environment that would crash the client when chat commands were not properly released - hopefully that particular bug has been fixed, but it's prior existance shows the importance of cleaning up our own code to prevent running into bugs in the environment.
While some would argue that it is Turbine's responsibility to properly manage the plugin code, I personally find it more practical to clean up my objects and not subject users to avoidable client crashes while waiting for Turbine to respond to bug reports and fix the environment which can take months due to limited resources (but it's still important to file the bug reports).
all this is so new to me i am trying to learn it all
thanks for great assistance was huge help though.
Wouahhh !
Great job !
Gz and I hop we are going to see some wonderful new plugins soon
Or some of the out of date nice old ones wo can be fixed !
It's about time!
It's been quite a while since I published any new examples here and since at least one budding author had a question about using a timer in Lua I figured this was as good a time as any to add a new sample. This installment is a fairly simple but robust timer class, oringally published in response to the question, "how to write a timer?".
There are three time related methods in the Turbine engine, Turbine.Engine.GetDate(), Turbine.Engine.GetGameTime() and Turbine.Engine.GetLocalTime(). The first, GetDate() returns a table with values for "Day", "DayOfWeek", "DayOfYear", "Hour", "IsDST", "Minute", "Month", "Second" and "Year". This can be quite handy for creating plugins that implement real life event scheduling, such as an alert that flashes "Hurry up and log out, your wife is almost home!" The second, GetGameTime() returns the number of seconds since the servers went live which is very useful for ingame timing. The third, GetLocalTime() returns the number of seconds since Jan 1st, 1970 which is also handy for real life timers. Note that GetGameTime() will include a fractional component valid to at least 4 decimals (it may be valid to 5 places and is just thrown off in formatting, I haven't really needed anything beyond 10000ths of a second so I never checked that fifth decimal) whereas GetLocalTime() will only return an integer thus only allowing timing to the nearest second.
The sample I will provide here is a fairly simple use of the GetGameTime() method combined with an Update event handler to create a simple but useful Timer class.
To use the timer, just include the above code or copy it to a file and include that file, then create an instance of the timer, set an event and set the timer:Code:-- This is a basic, reusable timer class -- To set the timer, call Timer:SetTime(numberOfSeconds, repeat) -- -- where numberOfSeconds is the number of seconds before the timer event will fire and repeat is an optional argument that will set whether the timer will automatically repeat (any non-nil, non-false value is considered true) -- When the timer reaches the set time, it will fire the event, Timer.TimeReached which you implement the same as any other event handler -- This class handles multiple events assigned to the TimeReached event, supported by the AddCallback/RemoveCallback mechanism. Timer = class( Turbine.UI.Control ); -- base the class on a generic control so that we can use an Update handler function Timer:Constructor() Turbine.UI.Control.Constructor( self ); -- generic control constructor self.EndTime=Turbine.Engine.GetGameTime(); -- default the EndTime value self.Repeat=false; -- default the Repeat value -- this is the function which users will call on an instance of the class to set a timer self.SetTime=function(sender, numSeconds, setRepeat) numSeconds=tonumber(numSeconds); -- force the "type" of the numSeconds parameter if numSeconds==nil or numSeconds<=0 then numSeconds=0; -- force the numSeconds to a 0 or positive value (negative time is a baaadd thing, Marty...) end self.EndTime=Turbine.Engine.GetGameTime()+numSeconds; -- set the end time based on current time + the provided number of seconds -- note, numSeconds can contain a fractions of a second self.Repeat=false; -- default repeat self.NumSeconds=numSeconds; -- store the number of seconds internally for use with Repeat if setRepeat~=nil and setRepeat~=false then -- any non-false value will trigger a repeat self.Repeat=true; end self:SetWantsUpdates(true); -- we set updates to true AFTER we have established the end time and repeat settings (lua uses a single thread so this is kind of overkill, but again, a good practice) end -- this is the Update handler that will handle checking the time and firing the event(s) if needed self.Update=function() if self.EndTime~=nil and Turbine.Engine.GetGameTime()>=self.EndTime then -- we have a valid timer and it the end time has been reached self:SetWantsUpdates(false); -- turn off timer to avoid firing again while we are processing (not likely but it's a good practice) -- fire whatever event you are trying to trigger if self.TimeReached~=nil then -- we account for both a single "function" as well as a possible table of functions if type(self.TimeReached)=="function" then self.TimeReached(); elseif type(self.TimeReached)=="table" then for k,v in pairs(self.TimeReached) do if type(v)=="function" then v(); end end end end -- last but not least, if we are set to repeat then we need to calculate the next time to fire and reenable the Update handler if self.Repeat then self.EndTime=Turbine.Engine.GetGameTime()+self.NumSeconds; self:SetWantsUpdates(true); end end end end
Code:timer1=Timer(); -- then set the TimeReached event handler myEvent=function() Turbine.Shell.WriteLine("Timer Just Fired!"); end AddCallback(timer1,"TimeReached",myEvent); -- note, you have to include the definition for the AddCallback function which of course all good authors already do... ;) -- and finally, set the timer timer1:SetTime(60, true); -- cause the timer to fire every 60 seconds, auto repeating
Last edited by Garan; Jul 16 2012 at 07:29 PM. Reason: formatting
Hi Garan,
Wow, so much useful info! So much so that it's a bit overwhelming for a noob, but as you say, we'll have to take the time to digest it! :-). I'm sure it'll prove immensely helpful! Thanks!
One question: I noticed that at least one current plugin ("HotswapSlot") swaps (i.e. equips) items of the character. You mentioned in october 2011 that this wasn't yet possible with the currenct API. Do you know if this is a special case utilized by HotswapSlot, limited use or...? Or do you have any info about how to equip items as part of a newly released API update?
Thanks again, great work!
We still can not programatically swap items. The HotswapSlot plugin requires multiple user clicks to actually swap the items. It helps to speed up the swapping by changing the item in the quickslot between clicks but is not actually automated.
The biggest drawback to this kind of functionality is that it always tries to fill the first available slot of its type which means it will not work correctly to swap out anything with multiple slots such as earrings, bracelets and rings. What you would have to do is combine the functionality of Unequipper to remove the correct item and then equip the new item - note, you would have to unequip BOTH items and requip both items to quarantee correct functionality since the behaviour of clicking on an item is different depending on which slots are already populated - for instance, clicking on a ring will populate the first slot if the first slot is empty or both slots are filled, but will populate the second slot if only the first slot is filled. If we could swap items programmatically (if equipment slots supported the PerformItemDrop method that the backpack supports), this sort of issue would not arise. It would also eliminate the need for multiple clicks and the possibility of accidentally double clicking.
Last edited by Garan; Aug 23 2012 at 11:14 AM.
bump this up again
Bill Magill Mac Player Founder/Lifetimer
Old Timers Guild - Gladden
Sr. Editor LOTRO-Wiki.com
Val - Man Minstrel (108)
Valalin - Dwarf Minsrel (71)
Valamar - Dwarf Hunter (120)
Valdicta - Dwarf RK (107)
Valhad - Elf LM (66)
Valkeeper - Elf RK (87)
Valwood - Dwarf RK (81)
Valhunt - Dwarf Hunter (71)
Valanne - Beorning (105)
Ninth - Man Warden (66)
"Laid back, not so serious, no drama.
All about the fun!"
It's been quite a while since I've added to this thread and someone was recently asking me about responding to keys. So I decided it was a good time to delve a bit into the mysterious and sometimes befuddling control:KeyDown and control:KeyUp events and Actions in general.
To start with, you should be aware that the object.KeyDown() and object.KeyUp() events do NOT actually capture key presses, rather they are fired when an Action takes place. In some cases, these actions do not even have to be activated by a key, they can be activated by a mouse click. While it takes a bit of getting used to and has some significant limitations, it also has a great benefit. If the user changes their keymappings it won't affect your plugin since you are responding to the bound Action, not the key that caused it.
There are two events, KeyDown and KeyUp that are fired in response to Actions. Not all Actions fire both events. For instance, opening your inventory only fires the KeyDown event but pressing the Delete key while editing text only fires the KeyUp event. Some Actions such as the quickslot visibility and the push to talk actions will fire both events. You will likely have to try trapping Actions in both KeyDown and KeyUp handlers until you find which one fires for the circumstance you are looking for.
The event handlers for KeyDown and KeyUp are only fired for controls that are listening for KeyEvents. To enable key events you call control:SetWantsKeyEvents(true ) and control:SetWantsKeyEvents(fals e) to turn those events off. This is particularly useful when handling things like cursor events in a textbox - there's no point in the textbox processing every event that occurs in the game so you can use the control:FocusGained() and control:FocusLost() events to enable and disable the key event handlers to save processing time. This can significantly impact the performance of the client.
The KeyDown and KeyUp events will pass two arguments, the control firing the event and a table with the following members: Action, Alt, Control and Shift. You can use a simple handler to identify any particular action you are looking for:
where object is a Control.Code:object.KeyDown=function(sender,args) Turbine.Shell.WriteLine("Action:"..tostring(args.Action)); end
To make handling Actions easier, Turbine started defining an enumeration for the available Actions. Unfortunately, they gave up after only defining a few:
A couple of years ago, I started to fill in the gaps (or rather the enormous gaping holes) and posted a list of known Actions but that list is now woefully out of date. Below is a current list which you can copy to a file (I named mine "action.lua") and include it in your projects:Code:Turbine.UI.Lotro.Action.ToggleBags = 268435604; Turbine.UI.Lotro.Action.ToggleBag1 = 268435478; Turbine.UI.Lotro.Action.ToggleBag2 = 268435486; Turbine.UI.Lotro.Action.ToggleBag3 = 268435493; Turbine.UI.Lotro.Action.ToggleBag4 = 268435501; Turbine.UI.Lotro.Action.ToggleBag5 = 268435513; Turbine.UI.Lotro.Action.ToggleBag6 = 268436015; Turbine.UI.Lotro.Action.EscapeKey = 145; Turbine.UI.Lotro.Action.Undefined = 0;
Once imported, you can refer to the actions by the names rather than the codes to make maintenance a bit easier. You can also simply use the above definitions to look up the particular codes you wish to handle.Code:import "Turbine.UI.Lotro"; -- Predefined by Turbine -- Turbine.UI.Lotro.Action.ToggleBags = 268435604; -- Turbine.UI.Lotro.Action.ToggleBag1 = 268435478; -- Turbine.UI.Lotro.Action.ToggleBag2 = 268435486; -- Turbine.UI.Lotro.Action.ToggleBag3 = 268435493; -- Turbine.UI.Lotro.Action.ToggleBag4 = 268435501; -- Turbine.UI.Lotro.Action.ToggleBag5 = 268435513; -- Turbine.UI.Lotro.Action.ToggleBag6 = 268436015; -- Turbine.UI.Lotro.Action.EscapeKey = 145; -- Turbine.UI.Lotro.Action.Undefined = 0; if _G.Turbine.UI.Lotro.Action==nil then _G.Turbine.UI.Lotro.Action={} end --if _G.Turbine.UI.Lotro.Action.DismountRemount==nil then _G.Turbine.UI.Lotro.Action.DismountRemount = 268435916 end -- DismountRemount -- MOVEMENT -- none of the normal movement actions appear to be available at this time -- QUICKSLOTS if _G.Turbine.UI.Lotro.Action.QuickslotPageUp==nil then _G.Turbine.UI.Lotro.Action.QuickslotPageUp=268436022 end if _G.Turbine.UI.Lotro.Action.QuickslotPageDown==nil then _G.Turbine.UI.Lotro.Action.QuickslotPageDown=268436021 end if _G.Turbine.UI.Lotro.Action.Quickslot_1==nil then _G.Turbine.UI.Lotro.Action.Quickslot_1=268435498 end if _G.Turbine.UI.Lotro.Action.Quickslot_2==nil then _G.Turbine.UI.Lotro.Action.Quickslot_2=268435506 end if _G.Turbine.UI.Lotro.Action.Quickslot_3==nil then _G.Turbine.UI.Lotro.Action.Quickslot_3=268435518 end if _G.Turbine.UI.Lotro.Action.Quickslot_4==nil then _G.Turbine.UI.Lotro.Action.Quickslot_4=268435527 end if _G.Turbine.UI.Lotro.Action.Quickslot_5==nil then _G.Turbine.UI.Lotro.Action.Quickslot_5=268435536 end if _G.Turbine.UI.Lotro.Action.Quickslot_6==nil then _G.Turbine.UI.Lotro.Action.Quickslot_6=268435543 end if _G.Turbine.UI.Lotro.Action.Quickslot_7==nil then _G.Turbine.UI.Lotro.Action.Quickslot_7=268435551 end if _G.Turbine.UI.Lotro.Action.Quickslot_8==nil then _G.Turbine.UI.Lotro.Action.Quickslot_8=268435559 end if _G.Turbine.UI.Lotro.Action.Quickslot_9==nil then _G.Turbine.UI.Lotro.Action.Quickslot_9=268435569 end if _G.Turbine.UI.Lotro.Action.Quickslot_10==nil then _G.Turbine.UI.Lotro.Action.Quickslot_10=268435535 end if _G.Turbine.UI.Lotro.Action.Quickslot_11==nil then _G.Turbine.UI.Lotro.Action.Quickslot_11=268435542 end if _G.Turbine.UI.Lotro.Action.Quickslot_12==nil then _G.Turbine.UI.Lotro.Action.Quickslot_12=268435550 end -- QUICKSLOT BAR 1 if _G.Turbine.UI.Lotro.Action.Quickbar1Visibility==nil then _G.Turbine.UI.Lotro.Action.Quickbar1Visibility=268435575 end if _G.Turbine.UI.Lotro.Action.Quickslot_13==nil then _G.Turbine.UI.Lotro.Action.Quickslot_13=268435558 end if _G.Turbine.UI.Lotro.Action.Quickslot_14==nil then _G.Turbine.UI.Lotro.Action.Quickslot_14=268435568 end if _G.Turbine.UI.Lotro.Action.Quickslot_15==nil then _G.Turbine.UI.Lotro.Action.Quickslot_15=268435576 end if _G.Turbine.UI.Lotro.Action.Quickslot_16==nil then _G.Turbine.UI.Lotro.Action.Quickslot_16=268435586 end if _G.Turbine.UI.Lotro.Action.Quickslot_17==nil then _G.Turbine.UI.Lotro.Action.Quickslot_17=268435598 end if _G.Turbine.UI.Lotro.Action.Quickslot_18==nil then _G.Turbine.UI.Lotro.Action.Quickslot_18=268435606 end if _G.Turbine.UI.Lotro.Action.Quickslot_19==nil then _G.Turbine.UI.Lotro.Action.Quickslot_19=268435614 end if _G.Turbine.UI.Lotro.Action.Quickslot_20==nil then _G.Turbine.UI.Lotro.Action.Quickslot_20=268435473 end if _G.Turbine.UI.Lotro.Action.Quickslot_21==nil then _G.Turbine.UI.Lotro.Action.Quickslot_21=268435481 end if _G.Turbine.UI.Lotro.Action.Quickslot_22==nil then _G.Turbine.UI.Lotro.Action.Quickslot_22=268435490 end if _G.Turbine.UI.Lotro.Action.Quickslot_23==nil then _G.Turbine.UI.Lotro.Action.Quickslot_23=268435497 end if _G.Turbine.UI.Lotro.Action.Quickslot_24==nil then _G.Turbine.UI.Lotro.Action.Quickslot_24=268435505 end -- QUICKSLOT BAR 2 if _G.Turbine.UI.Lotro.Action.Quickbar2Visibility==nil then _G.Turbine.UI.Lotro.Action.Quickbar2Visibility=268435556 end if _G.Turbine.UI.Lotro.Action.Quickslot_25==nil then _G.Turbine.UI.Lotro.Action.Quickslot_25=268435517 end if _G.Turbine.UI.Lotro.Action.Quickslot_26==nil then _G.Turbine.UI.Lotro.Action.Quickslot_26=268435526 end if _G.Turbine.UI.Lotro.Action.Quickslot_27==nil then _G.Turbine.UI.Lotro.Action.Quickslot_27=268435534 end if _G.Turbine.UI.Lotro.Action.Quickslot_28==nil then _G.Turbine.UI.Lotro.Action.Quickslot_28=268435541 end if _G.Turbine.UI.Lotro.Action.Quickslot_29==nil then _G.Turbine.UI.Lotro.Action.Quickslot_29=268435549 end if _G.Turbine.UI.Lotro.Action.Quickslot_30==nil then _G.Turbine.UI.Lotro.Action.Quickslot_30=268435461 end if _G.Turbine.UI.Lotro.Action.Quickslot_31==nil then _G.Turbine.UI.Lotro.Action.Quickslot_31=268435467 end if _G.Turbine.UI.Lotro.Action.Quickslot_32==nil then _G.Turbine.UI.Lotro.Action.Quickslot_32=268435472 end if _G.Turbine.UI.Lotro.Action.Quickslot_33==nil then _G.Turbine.UI.Lotro.Action.Quickslot_33=268435480 end if _G.Turbine.UI.Lotro.Action.Quickslot_34==nil then _G.Turbine.UI.Lotro.Action.Quickslot_34=268435489 end if _G.Turbine.UI.Lotro.Action.Quickslot_35==nil then _G.Turbine.UI.Lotro.Action.Quickslot_35=268435496 end if _G.Turbine.UI.Lotro.Action.Quickslot_36==nil then _G.Turbine.UI.Lotro.Action.Quickslot_36=268435504 end -- QUICKSLOT BAR 3 if _G.Turbine.UI.Lotro.Action.Quickbar3Visibility==nil then _G.Turbine.UI.Lotro.Action.Quickbar3Visibility=268435458 end if _G.Turbine.UI.Lotro.Action.Quickslot_37==nil then _G.Turbine.UI.Lotro.Action.Quickslot_37=268435516 end if _G.Turbine.UI.Lotro.Action.Quickslot_38==nil then _G.Turbine.UI.Lotro.Action.Quickslot_38=268435525 end if _G.Turbine.UI.Lotro.Action.Quickslot_39==nil then _G.Turbine.UI.Lotro.Action.Quickslot_39=268435533 end if _G.Turbine.UI.Lotro.Action.Quickslot_40==nil then _G.Turbine.UI.Lotro.Action.Quickslot_40=268435597 end if _G.Turbine.UI.Lotro.Action.Quickslot_41==nil then _G.Turbine.UI.Lotro.Action.Quickslot_41=268435605 end if _G.Turbine.UI.Lotro.Action.Quickslot_42==nil then _G.Turbine.UI.Lotro.Action.Quickslot_42=268435613 end if _G.Turbine.UI.Lotro.Action.Quickslot_43==nil then _G.Turbine.UI.Lotro.Action.Quickslot_43=268435619 end if _G.Turbine.UI.Lotro.Action.Quickslot_44==nil then _G.Turbine.UI.Lotro.Action.Quickslot_44=268435629 end if _G.Turbine.UI.Lotro.Action.Quickslot_45==nil then _G.Turbine.UI.Lotro.Action.Quickslot_45=268435632 end if _G.Turbine.UI.Lotro.Action.Quickslot_46==nil then _G.Turbine.UI.Lotro.Action.Quickslot_46=268435641 end if _G.Turbine.UI.Lotro.Action.Quickslot_47==nil then _G.Turbine.UI.Lotro.Action.Quickslot_47=268435460 end if _G.Turbine.UI.Lotro.Action.Quickslot_48==nil then _G.Turbine.UI.Lotro.Action.Quickslot_48=268435466 end -- QUICKSLOT BAR 4 if _G.Turbine.UI.Lotro.Action.Quickbar4Visibility==nil then _G.Turbine.UI.Lotro.Action.Quickbar4Visibility=268435485 end if _G.Turbine.UI.Lotro.Action.Quickslot_49==nil then _G.Turbine.UI.Lotro.Action.Quickslot_49=268435471 end if _G.Turbine.UI.Lotro.Action.Quickslot_50==nil then _G.Turbine.UI.Lotro.Action.Quickslot_50=268435488 end if _G.Turbine.UI.Lotro.Action.Quickslot_51==nil then _G.Turbine.UI.Lotro.Action.Quickslot_51=268435495 end if _G.Turbine.UI.Lotro.Action.Quickslot_52==nil then _G.Turbine.UI.Lotro.Action.Quickslot_52=268435503 end if _G.Turbine.UI.Lotro.Action.Quickslot_53==nil then _G.Turbine.UI.Lotro.Action.Quickslot_53=268435515 end if _G.Turbine.UI.Lotro.Action.Quickslot_54==nil then _G.Turbine.UI.Lotro.Action.Quickslot_54=268435524 end if _G.Turbine.UI.Lotro.Action.Quickslot_55==nil then _G.Turbine.UI.Lotro.Action.Quickslot_55=268435532 end if _G.Turbine.UI.Lotro.Action.Quickslot_56==nil then _G.Turbine.UI.Lotro.Action.Quickslot_56=268435540 end if _G.Turbine.UI.Lotro.Action.Quickslot_57==nil then _G.Turbine.UI.Lotro.Action.Quickslot_57=268435548 end if _G.Turbine.UI.Lotro.Action.Quickslot_58==nil then _G.Turbine.UI.Lotro.Action.Quickslot_58=268435557 end if _G.Turbine.UI.Lotro.Action.Quickslot_59==nil then _G.Turbine.UI.Lotro.Action.Quickslot_59=268435567 end if _G.Turbine.UI.Lotro.Action.Quickslot_60==nil then _G.Turbine.UI.Lotro.Action.Quickslot_60=268435628 end -- QUICKSLOT BAR 5 if _G.Turbine.UI.Lotro.Action.Quickbar5Visibility==nil then _G.Turbine.UI.Lotro.Action.Quickbar5Visibility=268435539 end if _G.Turbine.UI.Lotro.Action.Quickslot_61==nil then _G.Turbine.UI.Lotro.Action.Quickslot_61=268435631 end if _G.Turbine.UI.Lotro.Action.Quickslot_62==nil then _G.Turbine.UI.Lotro.Action.Quickslot_62=268435640 end if _G.Turbine.UI.Lotro.Action.Quickslot_63==nil then _G.Turbine.UI.Lotro.Action.Quickslot_63=268435459 end if _G.Turbine.UI.Lotro.Action.Quickslot_64==nil then _G.Turbine.UI.Lotro.Action.Quickslot_64=268435465 end if _G.Turbine.UI.Lotro.Action.Quickslot_65==nil then _G.Turbine.UI.Lotro.Action.Quickslot_65=268435470 end if _G.Turbine.UI.Lotro.Action.Quickslot_66==nil then _G.Turbine.UI.Lotro.Action.Quickslot_66=268435479 end if _G.Turbine.UI.Lotro.Action.Quickslot_67==nil then _G.Turbine.UI.Lotro.Action.Quickslot_67=268435487 end if _G.Turbine.UI.Lotro.Action.Quickslot_68==nil then _G.Turbine.UI.Lotro.Action.Quickslot_68=268435494 end if _G.Turbine.UI.Lotro.Action.Quickslot_69==nil then _G.Turbine.UI.Lotro.Action.Quickslot_69=268435502 end if _G.Turbine.UI.Lotro.Action.Quickslot_70==nil then _G.Turbine.UI.Lotro.Action.Quickslot_70=268435612 end if _G.Turbine.UI.Lotro.Action.Quickslot_71==nil then _G.Turbine.UI.Lotro.Action.Quickslot_71=268435618 end if _G.Turbine.UI.Lotro.Action.Quickslot_72==nil then _G.Turbine.UI.Lotro.Action.Quickslot_72=268435627 end -- SELECTION if _G.Turbine.UI.Lotro.Action.SelectionSelf==nil then _G.Turbine.UI.Lotro.Action.SelectionSelf=268435508 end -- SELECTION_SELF if _G.Turbine.UI.Lotro.Action.SelectionNearestFoe==nil then _G.Turbine.UI.Lotro.Action.SelectionNearestFoe=268435607 end if _G.Turbine.UI.Lotro.Action.SelectionNextFoe==nil then _G.Turbine.UI.Lotro.Action.SelectionNextFoe=268435622 end --SELECTION_NEXT_FOE if _G.Turbine.UI.Lotro.Action.SelectionPreviousFoe==nil then _G.Turbine.UI.Lotro.Action.SelectionPreviousFoe=268435491 end --SELECTION_PREVIOUS_FOE if _G.Turbine.UI.Lotro.Action.SelectionNextTracked==nil then _G.Turbine.UI.Lotro.Action.SelectionNextTracked=268435684 end if _G.Turbine.UI.Lotro.Action.SelectionPreviousTracked==nil then _G.Turbine.UI.Lotro.Action.SelectionPreviousTracked=268435685 end if _G.Turbine.UI.Lotro.Action.SelectFellowOne==nil then _G.Turbine.UI.Lotro.Action.SelectFellowOne=268435500 end -- SelectFellowOne if _G.Turbine.UI.Lotro.Action.SelectFellowTwo==nil then _G.Turbine.UI.Lotro.Action.SelectFellowTwo=268435596 end -- SelectFellowTwo if _G.Turbine.UI.Lotro.Action.SelectFellowThree==nil then _G.Turbine.UI.Lotro.Action.SelectFellowThree=268435538 end -- SelectFellowThree if _G.Turbine.UI.Lotro.Action.SelectFellowFour==nil then _G.Turbine.UI.Lotro.Action.SelectFellowFour=268435595 end -- SelectFellowFour if _G.Turbine.UI.Lotro.Action.SelectFellowFive==nil then _G.Turbine.UI.Lotro.Action.SelectFellowFive=268435464 end -- SelectFellowFive if _G.Turbine.UI.Lotro.Action.SelectFellowSix==nil then _G.Turbine.UI.Lotro.Action.SelectFellowSix=268435523 end -- SelectFellowSix if _G.Turbine.UI.Lotro.Action.AssistFellowTwo==nil then _G.Turbine.UI.Lotro.Action.AssistFellowTwo=268435689 end -- AssistFellowTwo if _G.Turbine.UI.Lotro.Action.AssistFellowThree==nil then _G.Turbine.UI.Lotro.Action.AssistFellowThree=268435688 end -- AssistFellowThree if _G.Turbine.UI.Lotro.Action.AssistFellowFour==nil then _G.Turbine.UI.Lotro.Action.AssistFellowFour=268435692 end -- AssistFellowFour if _G.Turbine.UI.Lotro.Action.AssistFellowFive==nil then _G.Turbine.UI.Lotro.Action.AssistFellowFive=268435691 end -- AssistFellowFive if _G.Turbine.UI.Lotro.Action.AssistFellowSix==nil then _G.Turbine.UI.Lotro.Action.AssistFellowSix=268435690 end -- AssistFellowSix if _G.Turbine.UI.Lotro.Action.SelectionNearestFellow==nil then _G.Turbine.UI.Lotro.Action.SelectionNearestFellow=268435544 end -- SELECTION_NEAREST_FELLOW if _G.Turbine.UI.Lotro.Action.SelectionNearestPlayer==nil then _G.Turbine.UI.Lotro.Action.SelectionNearestPlayer=268435469 end -- SELECTION_NEAREST_PC if _G.Turbine.UI.Lotro.Action.SelectionNextPlayer==nil then _G.Turbine.UI.Lotro.Action.SelectionNextPlayer=268435475 end -- SELECTION_NEXT_PC if _G.Turbine.UI.Lotro.Action.SelectionPreviousPlayer==nil then _G.Turbine.UI.Lotro.Action.SelectionPreviousPlayer=268435608 end -- SELECTION_PREVIOUS_PC if _G.Turbine.UI.Lotro.Action.SelectionNearestCreature==nil then _G.Turbine.UI.Lotro.Action.SelectionNearestCreature=268435577 end -- SELECTION_NEAREST_CREATURE if _G.Turbine.UI.Lotro.Action.SelectionNextCreature==nil then _G.Turbine.UI.Lotro.Action.SelectionNextCreature=268435588 end -- SELECTION_NEXT_CREATURE if _G.Turbine.UI.Lotro.Action.SelectionPreviousCreature==nil then _G.Turbine.UI.Lotro.Action.SelectionPreviousCreature=268435507 end -- SELECTION_PREVIOUS_CREATURE if _G.Turbine.UI.Lotro.Action.SelectionNearestItem==nil then _G.Turbine.UI.Lotro.Action.SelectionNearestItem=268435633 end -- SELECTION_NEAREST_ITEM if _G.Turbine.UI.Lotro.Action.SelectionNextItem==nil then _G.Turbine.UI.Lotro.Action.SelectionNextItem=268435634 end if _G.Turbine.UI.Lotro.Action.SelectionPreviousItem==nil then _G.Turbine.UI.Lotro.Action.SelectionPreviousItem=268435519 end if _G.Turbine.UI.Lotro.Action.PreviousSelection==nil then _G.Turbine.UI.Lotro.Action.PreviousSelection=268435599 end if _G.Turbine.UI.Lotro.Action.PreviousAttacker==nil then _G.Turbine.UI.Lotro.Action.PreviousAttacker=268435474 end if _G.Turbine.UI.Lotro.Action.SelectionAssist==nil then _G.Turbine.UI.Lotro.Action.SelectionAssist=268435468 end -- SELECTION_ASSIST -- PANELS if _G.Turbine.UI.Lotro.Action.ToggleSkillPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleSkillPanel=268435483 end -- ToggleSkillPanel if _G.Turbine.UI.Lotro.Action.ToggleTraitPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleTraitPanel=268435510 end -- ToggleTraitPanel if _G.Turbine.UI.Lotro.Action.HousingPanel==nil then _G.Turbine.UI.Lotro.Action.HousingPanel=268435707 end if _G.Turbine.UI.Lotro.Action.ToggleCraftingPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleCraftingPanel=268435520 end -- ToggleCraftingPanel if _G.Turbine.UI.Lotro.Action.MapPanel==nil then _G.Turbine.UI.Lotro.Action.MapPanel=268435521 end -- ToggleMapPanel if _G.Turbine.UI.Lotro.Action.ToggleJournalPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleJournalPanel=268435529 end -- ToggleJournalPanel if _G.Turbine.UI.Lotro.Action.TitlesPanel==nil then _G.Turbine.UI.Lotro.Action.TitlesPanel=268435528 end if _G.Turbine.UI.Lotro.Action.ToggleSocialPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleSocialPanel=268435509 end -- ToggleSocialPanel if _G.Turbine.UI.Lotro.Action.TogglePendingLoot==nil then _G.Turbine.UI.Lotro.Action.TogglePendingLoot=268436023 end -- Dressing Room not available -- Link Item to Chat not available if _G.Turbine.UI.Lotro.Action.ToggleOptionsPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleOptionsPanel=268435512 end -- ToggleOptionsPanel if _G.Turbine.UI.Lotro.Action.ToggleAssistancePanel==nil then _G.Turbine.UI.Lotro.Action.ToggleAssistancePanel=268435637 end -- ToggleAssistancePanel (Help Panel) if _G.Turbine.UI.Lotro.Action.ToggleRadar==nil then _G.Turbine.UI.Lotro.Action.ToggleRadar=268435476 end if _G.Turbine.UI.Lotro.Action.ToggleQuestPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleQuestPanel=268435530 end -- ToggleQuestPanel if _G.Turbine.UI.Lotro.Action.ToggleAccomplishmentPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleAccomplishmentPanel=268435562 end -- ToggleAccomplishmentPanel (Deed Panel) if _G.Turbine.UI.Lotro.Action.ToggleItemAdvancementPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleItemAdvancementPanel=268435754 end -- ToggleItemAdvancementPanel if _G.Turbine.UI.Lotro.Action.ToggleMountsPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleMountsPanel=268435901 end if _G.Turbine.UI.Lotro.Action.ToggleWorldJoin==nil then _G.Turbine.UI.Lotro.Action.ToggleWorldJoin=268435888 end -- ToggleWorldJoin if _G.Turbine.UI.Lotro.Action.ToggleSkirmishPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleSkirmishPanel=268435854 end -- ToggleSkirmishPanel if _G.Turbine.UI.Lotro.Action.ToggleInstanceFinderPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleInstanceFinderPanel=268435924 end if _G.Turbine.UI.Lotro.Action.ToggleInstanceFinderSimplePanel==nil then _G.Turbine.UI.Lotro.Action.ToggleInstanceFinderSimplePanel=268436014 end if _G.Turbine.UI.Lotro.Action.ToggleInstanceFinderAdvancedPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleInstanceFinderAdvancedPanel=268436013 end if _G.Turbine.UI.Lotro.Action.ToggleMountedCombatUI==nil then _G.Turbine.UI.Lotro.Action.ToggleMountedCombatUI=268436016 end -- ToggleMountedCombatUI if _G.Turbine.UI.Lotro.Action.MyLOTROPanel==nil then _G.Turbine.UI.Lotro.Action.MyLOTROPanel=268435499 end if _G.Turbine.UI.Lotro.Action.ToggleWebStore==nil then _G.Turbine.UI.Lotro.Action.ToggleWebStore=268435889 end -- ToggleWebStore if _G.Turbine.UI.Lotro.Action.ReputationPanel==nil then _G.Turbine.UI.Lotro.Action.ReputationPanel=268435696 end if _G.Turbine.UI.Lotro.Action.HobbyPanel==nil then _G.Turbine.UI.Lotro.Action.HobbyPanel=268435910 end if _G.Turbine.UI.Lotro.Action.DestinyPointPerksPanel==nil then _G.Turbine.UI.Lotro.Action.DestinyPointPerksPanel=268435913 end if _G.Turbine.UI.Lotro.Action.ToggleFellowshipMakerUI==nil then _G.Turbine.UI.Lotro.Action.ToggleFellowshipMakerUI=268435907 end -- ToggleFellowshipMakerUI if _G.Turbine.UI.Lotro.Action.FriendsPanel==nil then _G.Turbine.UI.Lotro.Action.FriendsPanel=268435909 end if _G.Turbine.UI.Lotro.Action.KinshipPanel==nil then _G.Turbine.UI.Lotro.Action.KinshipPanel=268435905 end if _G.Turbine.UI.Lotro.Action.RaidPanel==nil then _G.Turbine.UI.Lotro.Action.RaidPanel=268435908 end if _G.Turbine.UI.Lotro.Action.GroupStagePanel==nil then _G.Turbine.UI.Lotro.Action.GroupStagePanel=268435906 end if _G.Turbine.UI.Lotro.Action.TogglePaperItemPanel==nil then _G.Turbine.UI.Lotro.Action.TogglePaperItemPanel=268435917 end -- TogglePaperItemPanel (Wallet) -- CHAT if _G.Turbine.UI.Lotro.Action.ChatModeReply==nil then _G.Turbine.UI.Lotro.Action.ChatModeReply=268435546 end-- ChatModeReply if _G.Turbine.UI.Lotro.Action.Start_Command==nil then _G.Turbine.UI.Lotro.Action.Start_Command=268435578 end -- START_COMMAND -- MISCELLANEOUS if _G.Turbine.UI.Lotro.Action.QuickSlot_SkillMode==nil then _G.Turbine.UI.Lotro.Action.QuickSlot_SkillMode=268435639 end -- QUICKSLOT_SKILLMODE (auto attack) if _G.Turbine.UI.Lotro.Action.Use==nil then _G.Turbine.UI.Lotro.Action.Use=268435589 end -- USE if _G.Turbine.UI.Lotro.Action.FollowSelection==nil then _G.Turbine.UI.Lotro.Action.FollowSelection=268436029 end if _G.Turbine.UI.Lotro.Action.FindItems==nil then _G.Turbine.UI.Lotro.Action.FindItems=268436030 end if _G.Turbine.UI.Lotro.Action.Show_Names==nil then _G.Turbine.UI.Lotro.Action.Show_Names=268435642 end -- SHOW_NAMES if _G.Turbine.UI.Lotro.Action.ShowDamage==nil then _G.Turbine.UI.Lotro.Action.ShowDamage=268435561 end if _G.Turbine.UI.Lotro.Action.CaptureScreenshot==nil then _G.Turbine.UI.Lotro.Action.CaptureScreenshot=116 end -- CaptureScreenshot if _G.Turbine.UI.Lotro.Action.Tooltip_Detach==nil then _G.Turbine.UI.Lotro.Action.Tooltip_Detach=268435482 end -- TOOLTIP_DETACH if _G.Turbine.UI.Lotro.Action.ToggleHiddenDragBoxes==nil then _G.Turbine.UI.Lotro.Action.ToggleHiddenDragBoxes=268435579 end -- ToggleHiddenDragBoxes if _G.Turbine.UI.Lotro.Action.ToggleQuickslotLock==nil then _G.Turbine.UI.Lotro.Action.ToggleQuickslotLock=268435462 end if _G.Turbine.UI.Lotro.Action.UI_Toggle==nil then _G.Turbine.UI.Lotro.Action.UI_Toggle=268435635 end -- UI_TOGGLE if _G.Turbine.UI.Lotro.Action.Logout==nil then _G.Turbine.UI.Lotro.Action.Logout=268435552 end -- LOGOUT if _G.Turbine.UI.Lotro.Action.VoiceChat_Talk==nil then _G.Turbine.UI.Lotro.Action.VoiceChat_Talk=268435555 end -- VOICECHAT_TALK if _G.Turbine.UI.Lotro.Action.ToggleItemSellLock==nil then _G.Turbine.UI.Lotro.Action.ToggleItemSellLock=268435590 end -- ToggleItemSellLock -- auto loot all doesn't seem to be available, may have to test while looting if _G.Turbine.UI.Lotro.Action.DismountRemount==nil then _G.Turbine.UI.Lotro.Action.DismountRemount=268435916 end if _G.Turbine.UI.Lotro.Action.ShowRemoteQuestActions==nil then _G.Turbine.UI.Lotro.Action.ShowRemoteQuestActions=268436019 end if _G.Turbine.UI.Lotro.Action.TrackNearbyQuests==nil then _G.Turbine.UI.Lotro.Action.TrackNearbyQuests=268435929 end if _G.Turbine.UI.Lotro.Action.ClearAllFilters==nil then _G.Turbine.UI.Lotro.Action.ClearAllFilters=268435918 end -- MUSIC (many of these actions can only occur during Music Mode) -- Note, the naming using flats comes from Turbine in the Keymap file so I retained it for the Action names if _G.Turbine.UI.Lotro.Action.ToggleMusicMode==nil then _G.Turbine.UI.Lotro.Action.ToggleMusicMode=268435683 end if _G.Turbine.UI.Lotro.Action.MusicEndSong==nil then _G.Turbine.UI.Lotro.Action.MusicEndSong=268435695 end -- MusicEndSong if _G.Turbine.UI.Lotro.Action.Music_A2==nil then _G.Turbine.UI.Lotro.Action.Music_A2=268435659 end if _G.Turbine.UI.Lotro.Action.Music_Bb2==nil then _G.Turbine.UI.Lotro.Action.Music_Bb2=268435659 end if _G.Turbine.UI.Lotro.Action.Music_B2==nil then _G.Turbine.UI.Lotro.Action.Music_B2=268435649 end -- MUSIC_B2 if _G.Turbine.UI.Lotro.Action.Music_C2==nil then _G.Turbine.UI.Lotro.Action.Music_C2=268435676 end if _G.Turbine.UI.Lotro.Action.Music_Db2==nil then _G.Turbine.UI.Lotro.Action.Music_Db2=268435656 end -- if _G.Turbine.UI.Lotro.Action.Music_D2==nil then _G.Turbine.UI.Lotro.Action.Music_D2=268435666 end if _G.Turbine.UI.Lotro.Action.Music_Eb2==nil then _G.Turbine.UI.Lotro.Action.Music_Eb2=268435662 end if _G.Turbine.UI.Lotro.Action.Music_E2==nil then _G.Turbine.UI.Lotro.Action.Music_E2=268435674 end if _G.Turbine.UI.Lotro.Action.Music_F2==nil then _G.Turbine.UI.Lotro.Action.Music_F2=268435661 end if _G.Turbine.UI.Lotro.Action.Music_Gb2==nil then _G.Turbine.UI.Lotro.Action.Music_Gb2=268435671 end if _G.Turbine.UI.Lotro.Action.Music_G2==nil then _G.Turbine.UI.Lotro.Action.Music_G2=268435652 end if _G.Turbine.UI.Lotro.Action.Music_Ab3==nil then _G.Turbine.UI.Lotro.Action.Music_Ab3=268435680 end if _G.Turbine.UI.Lotro.Action.Music_A3==nil then _G.Turbine.UI.Lotro.Action.Music_A3=268435660 end -- MUSIC_A3 if _G.Turbine.UI.Lotro.Action.Music_Bb3==nil then _G.Turbine.UI.Lotro.Action.Music_Bb3=268435648 end -- MUSIC_Bb3 if _G.Turbine.UI.Lotro.Action.Music_B3==nil then _G.Turbine.UI.Lotro.Action.Music_B3=268435651 end -- MUSIC_B3 if _G.Turbine.UI.Lotro.Action.Music_C3==nil then _G.Turbine.UI.Lotro.Action.Music_C3=268435678 end -- MUSIC_C3 if _G.Turbine.UI.Lotro.Action.Music_Db3==nil then _G.Turbine.UI.Lotro.Action.Music_Db3=268435657 end -- MUSIC_Db3 if _G.Turbine.UI.Lotro.Action.Music_D3==nil then _G.Turbine.UI.Lotro.Action.Music_D3=268435669 end -- MUSIC_D3 if _G.Turbine.UI.Lotro.Action.Music_Eb3==nil then _G.Turbine.UI.Lotro.Action.Music_Eb3=268435665 end -- MUSIC_Eb3 if _G.Turbine.UI.Lotro.Action.Music_E3==nil then _G.Turbine.UI.Lotro.Action.Music_E3=268435675 end -- MUSIC_E3 if _G.Turbine.UI.Lotro.Action.Music_F3==nil then _G.Turbine.UI.Lotro.Action.Music_F3=268435664 end -- MUSIC_F3 if _G.Turbine.UI.Lotro.Action.Music_Gb3==nil then _G.Turbine.UI.Lotro.Action.Music_Gb3=268435672 end -- MUSIC_Gb3 if _G.Turbine.UI.Lotro.Action.Music_G3==nil then _G.Turbine.UI.Lotro.Action.Music_G3=268435654 end -- MUSIC_G3 if _G.Turbine.UI.Lotro.Action.Music_Ab4==nil then _G.Turbine.UI.Lotro.Action.Music_Ab4=268435682 end --MUSIC_Ab4 if _G.Turbine.UI.Lotro.Action.Music_A4==nil then _G.Turbine.UI.Lotro.Action.Music_A4=268435663 end -- MUSIC_A4 if _G.Turbine.UI.Lotro.Action.Music_Bb4==nil then _G.Turbine.UI.Lotro.Action.Music_Bb4=268435650 end if _G.Turbine.UI.Lotro.Action.Music_B4==nil then _G.Turbine.UI.Lotro.Action.Music_B4=268435653 end -- MUSIC_B4 if _G.Turbine.UI.Lotro.Action.Music_C4==nil then _G.Turbine.UI.Lotro.Action.Music_C4=268435679 end -- MUSIC_C4 if _G.Turbine.UI.Lotro.Action.Music_Db4==nil then _G.Turbine.UI.Lotro.Action.Music_Db4=268435658 end if _G.Turbine.UI.Lotro.Action.Music_D4==nil then _G.Turbine.UI.Lotro.Action.Music_D4=268435670 end -- MUSIC_D4 if _G.Turbine.UI.Lotro.Action.Music_Eb4==nil then _G.Turbine.UI.Lotro.Action.Music_Eb4=268435668 end if _G.Turbine.UI.Lotro.Action.Music_E4==nil then _G.Turbine.UI.Lotro.Action.Music_E4=268435677 end -- MUSIC_E4 if _G.Turbine.UI.Lotro.Action.Music_F4==nil then _G.Turbine.UI.Lotro.Action.Music_F4=268435667 end -- MUSIC_F4 if _G.Turbine.UI.Lotro.Action.Music_Gb4==nil then _G.Turbine.UI.Lotro.Action.Music_Gb4=268435673 end if _G.Turbine.UI.Lotro.Action.Music_G4==nil then _G.Turbine.UI.Lotro.Action.Music_G4=268435655 end -- MUSIC_G4 if _G.Turbine.UI.Lotro.Action.Music_Ab5==nil then _G.Turbine.UI.Lotro.Action.Music_Ab5=268435646 end if _G.Turbine.UI.Lotro.Action.Music_C5==nil then _G.Turbine.UI.Lotro.Action.Music_C5=268435681 end -- MUSIC_C5 -- FELLOWSHIP MANOEUVRES if _G.Turbine.UI.Lotro.Action.FellowshipSkillAssist==nil then _G.Turbine.UI.Lotro.Action.FellowshipSkillAssist=268435686 end if _G.Turbine.UI.Lotro.Action.TopFellowshipManoeuvre==nil then _G.Turbine.UI.Lotro.Action.TopFellowshipManoeuvre=268435609 end if _G.Turbine.UI.Lotro.Action.BottomFellowshipManoeuvre==nil then _G.Turbine.UI.Lotro.Action.BottomFellowshipManoeuvre=268435615 end if _G.Turbine.UI.Lotro.Action.LeftFellowshipManoeuvre==nil then _G.Turbine.UI.Lotro.Action.LeftFellowshipManoeuvre=268435624 end if _G.Turbine.UI.Lotro.Action.RightFellowshipManoeuvre==nil then _G.Turbine.UI.Lotro.Action.RightFellowshipManoeuvre=268435630 end -- FELLOWSHIP TARGET MARKING if _G.Turbine.UI.Lotro.Action.ShieldMark==nil then _G.Turbine.UI.Lotro.Action.ShieldMark=268435706 end if _G.Turbine.UI.Lotro.Action.SpearMark==nil then _G.Turbine.UI.Lotro.Action.SpearMark=268435697 end if _G.Turbine.UI.Lotro.Action.ArrowMark==nil then _G.Turbine.UI.Lotro.Action.ArrowMark=268435698 end if _G.Turbine.UI.Lotro.Action.SunMark==nil then _G.Turbine.UI.Lotro.Action.SunMark=268435699 end if _G.Turbine.UI.Lotro.Action.SwordsMark==nil then _G.Turbine.UI.Lotro.Action.SwordsMark=268435700 end if _G.Turbine.UI.Lotro.Action.MoonMark==nil then _G.Turbine.UI.Lotro.Action.MoonMark=268435701 end if _G.Turbine.UI.Lotro.Action.StarMark==nil then _G.Turbine.UI.Lotro.Action.StarMark=268435702 end if _G.Turbine.UI.Lotro.Action.ClawMark==nil then _G.Turbine.UI.Lotro.Action.ClawMark=268435703 end if _G.Turbine.UI.Lotro.Action.SkullMark==nil then _G.Turbine.UI.Lotro.Action.SkullMark=268435704 end if _G.Turbine.UI.Lotro.Action.LeafMark==nil then _G.Turbine.UI.Lotro.Action.LeafMark=268435705 end -- COSMETIC OUTFIT SELECTION if _G.Turbine.UI.Lotro.Action.PresentMainInventory==nil then _G.Turbine.UI.Lotro.Action.PresentMainInventory=268435710 end if _G.Turbine.UI.Lotro.Action.PresentOutfit1==nil then _G.Turbine.UI.Lotro.Action.PresentOutfit1=268435708 end if _G.Turbine.UI.Lotro.Action.PresentOutfit2==nil then _G.Turbine.UI.Lotro.Action.PresentOutfit2=268435709 end if _G.Turbine.UI.Lotro.Action.PresentOutfit3==nil then _G.Turbine.UI.Lotro.Action.PresentOutfit3=268435921 end if _G.Turbine.UI.Lotro.Action.PresentOutfit4==nil then _G.Turbine.UI.Lotro.Action.PresentOutfit4=268435922 end if _G.Turbine.UI.Lotro.Action.PresentOutfit5==nil then _G.Turbine.UI.Lotro.Action.PresentOutfit5=268435923 end if _G.Turbine.UI.Lotro.Action.PresentOutfit6==nil then _G.Turbine.UI.Lotro.Action.PresentOutfit6=268435925 end if _G.Turbine.UI.Lotro.Action.PresentOutfit7==nil then _G.Turbine.UI.Lotro.Action.PresentOutfit7=268435926 end -- CAMERA if _G.Turbine.UI.Lotro.Action.RotateCamera==nil then _G.Turbine.UI.Lotro.Action.RotateCamera=92 end -- no other Camera actions seem to be available at this time -- MOUSE if _G.Turbine.UI.Lotro.Action.RightMouseDown==nil then _G.Turbine.UI.Lotro.Action.RightMouseDown=19 end if _G.Turbine.UI.Lotro.Action.CutText==nil then _G.Turbine.UI.Lotro.Action.CutText=8 end -- Ctrl+X if _G.Turbine.UI.Lotro.Action.CopyText==nil then _G.Turbine.UI.Lotro.Action.CopyText=170 end -- Ctrl+C if _G.Turbine.UI.Lotro.Action.PasteText==nil then _G.Turbine.UI.Lotro.Action.PasteText=100 end -- Ctrl+V if _G.Turbine.UI.Lotro.Action.ToggleDebugHUD==nil then _G.Turbine.UI.Lotro.Action.ToggleDebugHUD=42 end -- ToggleDebugHUD (oddly named since it is the FPS display) if _G.Turbine.UI.Lotro.Action.SystemMenu==nil then _G.Turbine.UI.Lotro.Action.SystemMenu=268435900 end if _G.Turbine.UI.Lotro.Action.MainMenu==nil then _G.Turbine.UI.Lotro.Action.MainMenu=268435899 end if _G.Turbine.UI.Lotro.Action.ExitGame==nil then _G.Turbine.UI.Lotro.Action.ExitGame=268435570 end -- not sure this one is really worth knowing but you never know if _G.Turbine.UI.Lotro.Action.Admin_Light==nil then _G.Turbine.UI.Lotro.Action.Admin_Light=268435623 end -- ADMIN_LIGHT if _G.Turbine.UI.Lotro.Action.Accept_Input==nil then _G.Turbine.UI.Lotro.Action.Accept_Input=162 end -- ACCEPT_INPUT if _G.Turbine.UI.Lotro.Action.CursorPreviousPage==nil then _G.Turbine.UI.Lotro.Action.CursorPreviousPage=146 end if _G.Turbine.UI.Lotro.Action.CursorNextPage==nil then _G.Turbine.UI.Lotro.Action.CursorNextPage=49 end if _G.Turbine.UI.Lotro.Action.CursorStartOfLine==nil then _G.Turbine.UI.Lotro.Action.CursorStartOfLine=58 end if _G.Turbine.UI.Lotro.Action.CursorEndOfLine==nil then _G.Turbine.UI.Lotro.Action.CursorEndOfLine=57 end if _G.Turbine.UI.Lotro.Action.CursorCharLeft==nil then _G.Turbine.UI.Lotro.Action.CursorCharLeft=127 end if _G.Turbine.UI.Lotro.Action.CursorCharRight==nil then _G.Turbine.UI.Lotro.Action.CursorCharRight=108 end if _G.Turbine.UI.Lotro.Action.CursorPreviousLine==nil then _G.Turbine.UI.Lotro.Action.CursorPreviousLine=29 end if _G.Turbine.UI.Lotro.Action.CursorNextLine==nil then _G.Turbine.UI.Lotro.Action.CursorNextLine=113 end if _G.Turbine.UI.Lotro.Action.CursorWordLeft==nil then _G.Turbine.UI.Lotro.Action.CursorWordLeft=163 end if _G.Turbine.UI.Lotro.Action.CursorWordRight==nil then _G.Turbine.UI.Lotro.Action.CursorWordRight=37 end if _G.Turbine.UI.Lotro.Action.BackspaceKey==nil then _G.Turbine.UI.Lotro.Action.BackspaceKey=99 end if _G.Turbine.UI.Lotro.Action.DeleteKey==nil then _G.Turbine.UI.Lotro.Action.DeleteKey=75 end -- Vendor quantity selection if _G.Turbine.UI.Lotro.Action.ToggleStackDisplay==nil then _G.Turbine.UI.Lotro.Action.ToggleStackDisplay=268435836 end -- ToggleStackDisplay if _G.Turbine.UI.Lotro.Action.VendorFullStack==nil then _G.Turbine.UI.Lotro.Action.VendorFullStack=268435463 end if _G.Turbine.UI.Lotro.Action.VendorQuantity==nil then _G.Turbine.UI.Lotro.Action.VendorQuantity=268435835 end -- The following are only valid for Turbine's internal version of the client and can theoretically be used to attach actions to keystrokes since they do nothing in the player version of the client -- the default key binding is in parenthesis if _G.Turbine.UI.Lotro.Action.ToggleDebugConsole==nil then _G.Turbine.UI.Lotro.Action.ToggleDebugConsole=43 end -- ToggleDebugConsole (Ctrl+`) if _G.Turbine.UI.Lotro.Action.ToggleStringTokenDebugger==nil then _G.Turbine.UI.Lotro.Action.ToggleStringTokenDebugger=17 end -- ToggleStringTokenDebugger (Alt+`) if _G.Turbine.UI.Lotro.Action.ToggleMemoryGraph==nil then _G.Turbine.UI.Lotro.Action.ToggleMemoryGraph=184 end -- ToggleMemoryGraph (Shift+Alt+Ctrl+F8) if _G.Turbine.UI.Lotro.Action.ToggleBlockUI==nil then _G.Turbine.UI.Lotro.Action.ToggleBlockUI=173 end -- ToggleBlockUI (Shift+Alt+Ctrl+F9) if _G.Turbine.UI.Lotro.Action.TogglePerfGraph==nil then _G.Turbine.UI.Lotro.Action.TogglePerfGraph=139 end -- TogglePerfGraph (Shift+Alt+Ctrl+F10) if _G.Turbine.UI.Lotro.Action.ToggleProfiler==nil then _G.Turbine.UI.Lotro.Action.ToggleProfiler=140 end -- ToggleProfiler (Shift+Alt+Ctrl+F11) if _G.Turbine.UI.Lotro.Action.ToggleEntityNodeLabels==nil then _G.Turbine.UI.Lotro.Action.ToggleEntityNodeLabels=174 end -- ToggleEntityNodeLabels (Shift+Alt+Ctrl+F12) -- The following do not appear to be tied to in-game actions at this time but may be at some future time if _G.Turbine.UI.Lotro.Action.ToggleTraitTreeUI==nil then _G.Turbine.UI.Lotro.Action.ToggleTraitTreeUI=268436027 end -- ToggleTraitTreeUI (Ctrl+S) if _G.Turbine.UI.Lotro.Action.ToggleAdminPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleAdminPanel=268435571 end -- ToggleAdminPanel (Ctrl+A) if _G.Turbine.UI.Lotro.Action.ToggleThreatTrackerPanel==nil then _G.Turbine.UI.Lotro.Action.ToggleThreatTrackerPanel=268435853 end -- ToggleThreatTrackerPanel (Ctrl+H)
At the bottom of these definitions are a series of particularly interesting codes. Most keystrokes that are bound to an Action will activate some in-game action. The entries at the bottom of the definitions will NOT actually cause any in-game action in the live client and are thus noteworthy. Lua applications can respond to these Actions without any other in-game action occuring. There are two big downsides to this, first, there is no way to know if another plugin is responding to the Action and second, Turbine could theoretically remove the Actions from the game at any time.
So, what is needed to actually make use of an Action? Let's suppose for some reason that we want to create a window that will display whenever the user turns their in-game light on or off. I know this isn't terribly useful, but it will suffice for a good example. First, we will need a basic window with some incredibly important message, like "It is pitch black. You are likely to be eaten by a grue.". For this sample, we will assume that the light is off by default and will only display the window when the light is off (so the message is visible as soon as we load the plugin).
Now, whenever you press Alt+F10 (or whatever key your light has been bound to if you changed it) the message will toggle off and on in response to the Admin_Light Action. As it happens, the light actually has three states, two on and one off so the message gets out of sync with the actual light and you are destined to be eaten but you get the general idea.Code:import "Turbine" import "Turbine.UI" import "Turbine.UI.Lotro" ZorkWindow=Turbine.UI.Lotro.Window(); ZorkWindow:SetSize(320,140); ZorkWindow:SetPosition(Turbine.UI.Display:GetWidth()/2-100,Turbine.UI.Display:GetHeight()/2-100); ZorkWindow:SetText("Turn on your light!"); ZorkWindow.Message=Turbine.UI.Label(); ZorkWindow.Message:SetParent(ZorkWindow); ZorkWindow.Message:SetSize(300,60); ZorkWindow.Message:SetPosition(10,40); ZorkWindow.Message:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter); ZorkWindow.Message:SetMultiline(true); ZorkWindow.Message:SetFont(Turbine.UI.Lotro.Font.Verdana26); ZorkWindow.Message:SetText("It is pitch black. You are likely to be eaten by a grue."); ZorkWindow.KeyDown=function(sender,args) if args.Action==268435623 then -- if we had imported the above code we could have used Turbine.UI.Lotro.Action.Admin_Light instead of the constant, 268435623 ZorkWindow:SetVisible(not ZorkWindow:IsVisible()); end end ZorkWindow:SetVisible(true); ZorkWindow:SetWantsKeyEvents(true);
Last edited by Garan; Jun 27 2022 at 07:36 PM.
If only I had seen this article about a week ago. I just submitted my first plugin and it was quite a learning curve. I am a programer in RL, but this is a complete different style that what I'm used to working with.
I'm planning on coming back and reading all the examples in detail. Thanks for all your effort!
What I don't understand is how do I find out what values are in the "args" being passed over? From what I noticed, it seems that the Turbine documentation doesn't list all of the available methods(either that or I don't quite know how to navigate the documentation). I saw some methods in other author's plugins that weren't documented. Is there some secret Lua Lotro society I need to get invited to?
P.S.- I did find out that there's a completely free editor that has a plugin in to support Lua scripting. It was a godsend. If you're the type that prefers a controlled environment look up Lua Eclipse
Last edited by Davinna; Feb 10 2013 at 12:24 AM.
Turbine's documentation has not been updated in over a year and even then many significant methods were undocumented. Some of these are explored in the posts in this thread.
You can use Lua to explore the methods of objects fairly easily once you get used to it. You can add Turbine's table.lua to your project and then use Table.Dump(args) to see what the args contains. For a graphical representation, just download any of my plugins that include my debugger (the latest ones have some internal improvements so ItemTracker or AltInventory would be best) and create an instance of the DebugWindow from debugwindow.lua. This dialog allows for interactively running lua commands, watching variable/expression values and a tree for displaying objects.
Anyone managed to fidn a workaround for GetTarget():GetClass ? I closed my plugin project because of this 2 years ago, tried now - same. This function doesn't exist. The question is - why?
I decided to answer Artouiros's question with a new installment which attemts to cover some of the oddities of GetTarget. The sample code can be executed in my plugin's debug window or copied to a separate file and included in your own test plugins to see how GetTarget works with the various objects in game.
The GetTarget() method tends to cause considerable confusion. This is mostly because the method can return several different types of objects depending on what is currently targeted. The most basic objects returned are instances of Turbine.Gameplay.Entity and represent generic items like ore nodes, doors, etc. The next class of objects that may be returned are instances of Turbine.Gameplay.Actor and represent NPCs, pets and player characters that are not grouped with the local player. The next class of objects returned are instances of Turbine.Gameplay.Player and represent player characters that ARE GROUPED with the local player. The last class of object is Turbine.Gameplay.LocalPlayer which of course means you are targeting yourself. These classes of objects all form a hierarchy where each class is derived from (and thus inherits the methods of) the class above it in the following order: Entity > Actor > Player > LocalPlayer. So, every Actor supports the GetName method that is defined for Entity objects but will not support the GetClass method that is defined for Player objects. Note that classes like Pets and PartyMembers inherit from one of these four classes (you can find the inheritance hierarchy by selecting a class in the Turbine API docs noted below). Now, the reason this causes confusion is when you use GetTarget() to get an instance of the object you are currently targeting, the resulting object may or may not support many of the methods you are trying to access. In the case of Artouiros, the author was looking for the GetClass method when targeting players but may not have been aware that that method is only defined when the targeted object is a player that is currently grouped with the local player.
So, how do you know what type of object has been returned? The easiest way is to test the existence of a function that is either inherited or defined for the class of object you are expecting to handle. For an object, curTarget, this can be as simple as:
Note the period notation and lack of parenthesis when testing the existence of the function (we want to see if the function is defined, not test the results of calling the function)Code:if curTarget.GetClass~=nil then -- we have an object that supports GetClass -- add code here to display/handle the class information for this target end
All of the possible returned classes are defined in the Turbine API documentation under Turbine.Gameplay The docs are available at:
http://www.lotrointerface.com/downlo...mentation.html
The following code sample defines a basic window that will display your current target's type, Name, Alignment code (if available) and Power (again, if available)
Code:import "Turbine"; import "Turbine.Gameplay"; import "Turbine.UI"; import "Turbine.UI.Lotro"; curTarget=nil localPlayer=Turbine.Gameplay.LocalPlayer.GetInstance(); -- This is the callback mechanism provided by Pengoros, slightly modified to guarantee uniqueness function AddCallback(object, event, callback) if (object[event] == nil) then object[event] = callback; else if (type(object[event]) == "table") then local exists=false; local k,v; for k,v in ipairs(object[event]) do if v==callback then exists=true; break; end end if not exists then table.insert(object[event], callback); end else if object[event]~=callback then object[event] = {object[event], callback}; end end end return callback; end -- safely remove a callback without clobbering any extras function RemoveCallback(object, event, callback) if (object[event] == callback) then object[event] = nil; else if (type(object[event]) == "table") then local size = table.getn(object[event]); for i = 1, size do if (object[event][i] == callback) then table.remove(object[event], i); break; end end end end end targetWindow=Turbine.UI.Lotro.Window() targetWindow:SetSize(400,400) targetWindow:SetText("Target Window") targetWindow.TargetTypeCap=Turbine.UI.Label() targetWindow.TargetTypeCap:SetParent(targetWindow) targetWindow.TargetTypeCap:SetSize(190,20) targetWindow.TargetTypeCap:SetPosition(10,40) targetWindow.TargetTypeCap:SetText("Target Type:") targetWindow.TargetType=Turbine.UI.Label() targetWindow.TargetType:SetParent(targetWindow) targetWindow.TargetType:SetSize(190,20) targetWindow.TargetType:SetPosition(200,40) targetWindow.TargetNameCap=Turbine.UI.Label() targetWindow.TargetNameCap:SetParent(targetWindow) targetWindow.TargetNameCap:SetSize(190,20) targetWindow.TargetNameCap:SetPosition(10,60) targetWindow.TargetNameCap:SetText("Target Name:") targetWindow.TargetName=Turbine.UI.Label() targetWindow.TargetName:SetParent(targetWindow) targetWindow.TargetName:SetSize(190,20) targetWindow.TargetName:SetPosition(200,60) targetWindow.TargetAlignCap=Turbine.UI.Label() targetWindow.TargetAlignCap:SetParent(targetWindow) targetWindow.TargetAlignCap:SetSize(190,20) targetWindow.TargetAlignCap:SetPosition(10,80) targetWindow.TargetAlignCap:SetText("Target Align:") targetWindow.TargetAlign=Turbine.UI.Label() targetWindow.TargetAlign:SetParent(targetWindow) targetWindow.TargetAlign:SetSize(190,20) targetWindow.TargetAlign:SetPosition(200,80) targetWindow.TargetPowerCap=Turbine.UI.Label() targetWindow.TargetPowerCap:SetParent(targetWindow) targetWindow.TargetPowerCap:SetSize(190,20) targetWindow.TargetPowerCap:SetPosition(10,100) targetWindow.TargetPowerCap:SetText("Target Power:") targetWindow.TargetPower=Turbine.UI.Label() targetWindow.TargetPower:SetParent(targetWindow) targetWindow.TargetPower:SetSize(190,20) targetWindow.TargetPower:SetPosition(200,100) targetChanged=function() curTarget=localPlayer:GetTarget() if curTarget==nil then targetWindow.TargetType:SetText("nil") targetWindow.TargetName:SetText("") targetWindow.TargetPower:SetText("") targetWindow.TargetAlign:SetText("") else -- to determine the class we need to see where in the hierarch this object exists. We do this by simply testing the existance of methods to determine which class this is targetWindow.TargetType:SetText("Entity") -- default to most basic class if curTarget.GetPower~=nil then -- now test for a function that is defined in the class derived from Entity targetWindow.TargetType:SetText("Actor") targetWindow.TargetPower:SetText(tostring(curTarget:GetPower())) else targetWindow.TargetPower:SetText("") end if curTarget.GetAlignment~=nil then -- then test for a function defined in the class derived from Actor, etc. -- GetAlignment is defined in the Turbine.Gameplay.Player class targetWindow.TargetType:SetText("Player") targetWindow.TargetAlign:SetText(tostring(curTarget:GetAlignment())) else targetWindow.TargetAlign:SetText("") end -- now for any other functions you care about, just test each remaining function to be sure it is defined, otherwise set the value to a default if curTarget.GetName==nil then targetWindow.TargetName:SetText("") else targetWindow.TargetName:SetText(curTarget:GetName()) end end end AddCallback(localPlayer,"TargetChanged",targetChanged) targetWindow:SetVisible(true) -- remember to add RemoveCallback(localPlayer,"TargetChanged",targetChanged) to the plugin's unload event handler
Last edited by Garan; May 29 2015 at 08:41 AM.
Hey Garan,
I was trying to use GetTarget() the other day and came across an apparent problem that I would like to get your advice on.
In particular: If you're targeting a player who is in your party, and you use GetTarget() to get the Player object, the GetEffects() method always returns an empty list. If the player isn't in your party, GetEffects() seems to work fine. If you want to use GetEffects() on party members, you have to get the Player object from GetMember() -- not from GetTarget().
So I tried to implement a workaround for this as follows:
However, I found that IsMember() always returned false, even when the target was a member of my party. So I ended up having to compare the target:GetName() value to the GetName() values of all party members.Code:target = player:GetTarget(); party = player:GetParty(); if (party:IsMember(target)) then -- get the player object using party:GetMember() end
Have you observed these two bugs, or am I doing something wrong?
Apparent bug #1: If you're targeting a player who is in your party, and you use GetTarget() to get the Player object, the GetEffects() method always returns an empty list.
Apparent bug #2: Party.IsMember() doesn't seem to work for Player objects returned by GetTarget().
Thanks,
Thurallor
I would say that both appear to be bugs and should be reported. The first issue seems to be that the GetEffects method is getting overridden in the Player object since Player is derived from Actor it should behave correctly but doesn't seem to. The second seems to be a bug in Party:IsMember since both the Player object returned by GetTarget and the PartyMember object returned by GetMember both refer to the same underlying game object.
One correction to my post above:
The above statement is incorrect. Not sure what I was doing before that made me think I could see effects for non-party players.If the player isn't in your party, GetEffects() seems to work fine.
That is at least more consistent, but the fact that GetEffects is defined for the Actor class means that it really ought to work for any Actor, including characters that are not in your party or the method should simply not be defined at that level. I'm not sure if the devs that were working on Lua are even still with Turbine since we haven't had any updates or communication since last year so it's probable that this bug will persist indefinitely.
Member
I've investigated a little more. Further observations about the EffectList for targets:
- You can see the effects, but only after a certain delay. There is a delay (which seems to be several Update() cycles, but in fact is probably dependent on network latency) after the LocalPlayer.TargetChanged event, before the target's effect list gets populated. If you look closely at the target's effect list in the standard GUI, you can see this delay after selecting a new target, before the effect icons appear. When the effect icons appear in the standard GUI, you will receive EffectAdded events corresponding to them. This seems to be intrinsic to the design of the game, and I wouldn't consider it a bug; just something that needs to be documented.
- EffectRemoved events are never generated. This is clearly a bug.
- Sometimes multiple EffectAdded events are generated for the same effect. This is clearly a bug.
- The contents of the effect list is not consistent with the effect list shown in the standard GUI. So even if you ignore the events and simply poll the effect lists at every Update() cycle, you will not get an accurate list of the effects. This is clearly a bug.
I entered a bug report on Bullroarer, including a nice demo plugin to demonstrate the bugs. Fingers crossed.
Last edited by Thurallor; Jul 06 2015 at 04:37 PM.