We have detected that cookies are not enabled on your browser. Please enable cookies to ensure the proper experience.
Page 2 of 2 FirstFirst 1 2
Results 26 to 50 of 50
  1. #26
    Join Date
    Feb 2007
    Location
    Philadelphia, PA
    Posts
    2,586

    Re: Writing LoTRO Lua Plugins for Noobs

    Fantastic document.... tempts me to brush off my old FORTRAN, COBOL and Perl skills and come out of retirement!!
    Bill Magill - Mac Player - Old Timers Guild- Gladden:

    Partial cast
    Valamar: Dwarf Hunter - Level 100
    Valdicta: Dwarf Rune-keeper - Level 100
    Valanne: Beorning - Level 80

  2. #27
    Join Date
    Mar 2007
    Posts
    1,158

    Slip Sliding Away

    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.
    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 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);
    Note, I set the minimum opactity to .1 so that you wouldn't acccidentally scroll your window to invisibility and then lose it

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

    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 02:51 AM.

  3. #28

    Re: Writing LoTRO Lua Plugins for Noobs

    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]

  4. Re: The Unload Event Handler

    Quote Originally Posted by Garan View Post
    To handle the Unload event, you must assign an unload event handler. This is a little tricky since you can't assign an event handler for an object until the object exists and the Plugins[] element for your plugin will not exist until it completes the loading state. To handle this, most plugins set an Update event handler in their main window with a semaphore (a flag) used to determine whether the plugin should be considered "loading".
    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

  5. #30
    Join Date
    Mar 2007
    Posts
    1,158

    Re: The Unload Event Handler

    Quote Originally Posted by Astleigh View Post
    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 11:42 AM.

  6. Re: Writing LoTRO Lua Plugins for Noobs

    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
    Code:
    savedata = "plugin A save data";
    
    Turbine.Plugin.Unload = function()
        Turbine.PluginData.Save(Turbine.DataScope.Account, "A", savedata);
    end
    B
    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
    Code:
    Turbine.Plugin.Unload = "have a cooky";
    B
    Code:
    Turbine.Shell.WriteLine(Turbine.Plugin.Unload);

  7. #32
    Join Date
    Mar 2007
    Posts
    1,158

    Re: Writing LoTRO Lua Plugins for Noobs

    Quote Originally Posted by Astleigh View Post
    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
    Code:
    savedata = "plugin A save data";
    
    Turbine.Plugin.Unload = function()
        Turbine.PluginData.Save(Turbine.DataScope.Account, "A", savedata);
    end
    B
    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
    Code:
    Turbine.Plugin.Unload = "have a cooky";
    B
    Code:
    Turbine.Shell.WriteLine(Turbine.Plugin.Unload);
    The first part is an interesting anomaly. If you use the code:
    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
    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.

    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
    Code:
    _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))
    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.9
    Last edited by Garan; Jan 17 2012 at 02:48 PM.

  8. #33
    Join Date
    Jul 2010
    Posts
    4

    Re: Writing LoTRO Lua Plugins for Noobs

    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.

  9. #34
    Join Date
    Mar 2007
    Posts
    1,158

    Re: Writing LoTRO Lua Plugins for Noobs

    Quote Originally Posted by Dwamur View Post
    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).

  10. #35
    Join Date
    Jan 2007
    Location
    Parchment,Michigan
    Posts
    413

    Re: Writing LoTRO Lua Plugins for Noobs

    all this is so new to me i am trying to learn it all

    thanks for great assistance was huge help though.

  11. #36

    Re: Writing LoTRO Lua Plugins for Noobs

    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 !

  12. #37
    Join Date
    Mar 2007
    Posts
    1,158

    It 's about time!

    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.

    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
    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:
    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 08:29 PM. Reason: formatting

  13. #38
    Join Date
    Oct 2010
    Posts
    5

    equipping items

    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!

  14. #39
    Join Date
    Mar 2007
    Posts
    1,158
    Quote Originally Posted by gwathtauron View Post
    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 12:14 PM.

  15. #40
    Join Date
    Feb 2007
    Location
    Philadelphia, PA
    Posts
    2,586
    bump this up again
    Bill Magill - Mac Player - Old Timers Guild- Gladden:

    Partial cast
    Valamar: Dwarf Hunter - Level 100
    Valdicta: Dwarf Rune-keeper - Level 100
    Valanne: Beorning - Level 80

  16. #41
    Join Date
    Mar 2007
    Posts
    1,158

    Lights, Camera, ACTION

    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:
    Code:
    object.KeyDown=function(sender,args)
        Turbine.Shell.WriteLine("Action:"..tostring(args.Action));
    end
    where object is a Control.

    To make handling Actions easier, Turbine started defining an enumeration for the available Actions. Unfortunately, they gave up after only defining a few:
    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;
    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:
    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)
    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.

    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).
    Code:
    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);
    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.

  17. #42
    Join Date
    Feb 2009
    Location
    Illinois
    Posts
    95
    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 01:24 AM.

  18. #43
    Join Date
    Mar 2007
    Posts
    1,158
    Quote Originally Posted by Davinna View Post
    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
    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.

  19. #44
    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?

  20. #45
    Join Date
    Mar 2007
    Posts
    1,158

    On Target

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

    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 09:41 AM.

  21. #46
    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:
    Code:
    target = player:GetTarget();
    party = player:GetParty();
    if (party:IsMember(target)) then
        -- get the player object using party:GetMember()
    end
    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.

    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
    Thurallor, Warden of Landroval

  22. #47
    Join Date
    Mar 2007
    Posts
    1,158
    Quote Originally Posted by Thurallor View Post
    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:
    Code:
    target = player:GetTarget();
    party = player:GetParty();
    if (party:IsMember(target)) then
        -- get the player object using party:GetMember()
    end
    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.

    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.

  23. #48
    One correction to my post above:
    If the player isn't in your party, GetEffects() seems to work fine.
    The above statement is incorrect. Not sure what I was doing before that made me think I could see effects for non-party players.
    Thurallor, Warden of Landroval

  24. #49
    Join Date
    Mar 2007
    Posts
    1,158
    Quote Originally Posted by Thurallor View Post
    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.
    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.

  25. #50
    Quote Originally Posted by Garan View Post
    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.
    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 05:37 PM.
    Thurallor, Warden of Landroval

 

 
Page 2 of 2 FirstFirst 1 2

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  

This form's session has expired. You need to reload the page.

Reload