Plugin Along Episode 5: Improving the Quest Tracker Opacity Plugin

Welcome back to Plugin Along, where we talk about LOTRO Plugins! Last time we continued making improvements to the Opaque Quest Tracker plugin by adding load and unload messages, localizing the plugin, and fixing the save/load code to handle decimal numbers correctly across different client language and processor combinations. Then we published the plugin to lotrointerface.com, asked for it to be included in the LOTRO Plugin Compendium, and announced it on the LOTRO forums!

This time we’re going to cover making updates to an already-published plugin as we tackle some of the “nice-to-have” items on our list.

See the accompanying video at https://youtu.be/ccXvpFofQXE

Replace Default values with current values

Being able to reposition the opaque window is great, but many players use the same layout for all of their characters by first doing the command /ui layout save [optional name], and later using the command /ui layout load [optional name]. For these players, having to redo the position every time would be more of a hassle then a help. We can allow per-player settings with per-account defaults by taking advantage of the different storage areas (“Data Scopes”) for save files.

The Turbine.DataScope enumeration offers three scopes for plugin data save and load: Account, Server, and Character. By taking advantage of multiple save locations, we can use any data we find in Character, and if there isn’t a file there we can then check another location before using the hard-coded defaults.

To start, we already have a helper function to save the current settings:

settingsDataScope = Turbine.DataScope.Character;
settingsFilename = "OpaqueQuestTracker_Settings";

function SaveSettings()
    PatchDataSave(
        settingsDataScope,
        settingsFilename,
        SETTINGS);
end

Let’s add a second function to save the current settings as the new default settings. Both of these could be combined into a single function, passing a parameter to choose the behavior, but let’s keep it simple for now. In order to be the default value for any of the player’s characters, we want to save in the biggest scope possible, which is Account. We should also use a slightly different filename, so a casual viewer can tell the difference:

defaultSettingsDataScope = Turbine.DataScope.Account;
defaultSettingsFilename = "OpaqueQuestTracker_DefaultSettings";

function SaveSettingsAsDefault()
    PatchDataSave(
        defaultSettingsDataScope,
        defaultSettingsFilename,
        SETTINGS);
end

Next, let’s modify the Options Control to add a way to save account-wide defaults.

First, let’s add a new string to the localization table:

-- in _LANG.OPTIONS:

        ["MAKE_DEFAULT"] = {
            [EN] = "Use Current Settings As Default";
        };

Then, let’s give ourselves more vertical space to work with and a button to make the current layout the default:

-- in DrawOptionsControl():

    options:SetSize(250, 200);


    -- add a button to make the current settings the default for other characters
    local makeDefaultButton = Turbine.UI.Lotro.Button();
    makeDefaultButton:SetParent(options);
    makeDefaultButton:SetPosition(10, 120);
    makeDefaultButton:SetSize(250, 25);
    makeDefaultButton:SetText(GetString(_LANG.OPTIONS.MAKE_DEFAULT));
    makeDefaultButton.Click = function(sender, args)
        SaveSettingsAsDefault();
    end

Finally, we need to modify the LoadSettings() function to look for the account-wide save file. Just after trying to load the character file, we can check for a custom defaults file:

-- if we didn't find character settings, look for custom default values:
if (type(loadedSettings) ~= 'table') then
    loadedSettings = PatchDataLoad(
        defaultSettingsDataScope,
        defaultSettingsFilename);
end

Success! Now, if there is a character-specific save file the plugin will prefer that, otherwise it will use an account-wide defaults file, and finally it will use the hard-coded values as a fallback.

We can update our todo file:

v1.0.1 features:
    Save current setup as default for characters that are new to the plugin.
    Color Picker for background color

Possible features:
    Only get screen size when it changes, not during every move/resize

And our readme file:

v1.0.1
    Player can use their current settings to be the default for other characters that don't have their own settings.

Color Picker for background color

The LOTRO Lua interface does not come with a built-in Color Picker control, but that doesn’t have to stop us! We could try to create our own, but in this case others in the Plugin community have already done the work. Today we’ll make use of the Color Picker library at lotrointerface.com, since we don’t need colors with less than 100% saturation. If you need a more fully featured color picker, check out Thurallor’s Another Color Picker.

Step 1: Download the files from lotrointerface.com. They’re compressed in RAR format, so you may need to download a program like WinRAR in order to extract them.

Step 2: Copy ColorPicker.lua and Resources/picker.jpg into your plugin directory.

Step 3: Add an import statement and tell the ColorPicker where to find the image:

import "CubePlugins.OpaqueQuestTracker.ColorPicker"
PICKER_JPG_DIR = "CubePlugins/OpaqueQuestTracker/Resources/picker.jpg";

Step 4: Prepare the code for customizable red, green, and blue values:

Update DEFAULT_SETTINGS with a default of black:

        ["RED"] = 0.0;
        ["GREEN"] = 0.0;
        ["BLUE"] = 0.0;

Update each of the euroNormalize() definitions to handle a nil value gracefully:

    function euroNormalize(value)
        if (value == nil) then return 0.0; end

Add the floating point coercing steps in LoadSettings():

        SETTINGS.OPAQUE_WIN.RED = euroNormalize(SETTINGS.OPAQUE_WIN.RED);
        SETTINGS.OPAQUE_WIN.GREEN = euroNormalize(SETTINGS.OPAQUE_WIN.GREEN);
        SETTINGS.OPAQUE_WIN.BLUE = euroNormalize(SETTINGS.OPAQUE_WIN.BLUE);

Finally, the function ChangeWindowOpacity() should be repurposed to update both opacity and color, and each existing call should be updated to not pass in the opacity value:

function UpdateWindowBackColor()
    local opacity = SETTINGS.OPAQUE_WIN.OPACITY;
    local red = SETTINGS.OPAQUE_WIN.RED;
    local green = SETTINGS.OPAQUE_WIN.GREEN;
    local blue = SETTINGS.OPAQUE_WIN.BLUE;
    window:SetBackColor(Turbine.UI.Color(opacity, red, green, blue));
end

Now, we’re ready for a Color Picker control to be able to update these values.

Step 5: We can use the sample code in the ColorPicker’s main.lua to open a window containing the color picker, and integrate that into the options panel.

function CreateColorPickerWindow()
    -- Color Picker window
    ColorPickerWindow = Turbine.UI.Lotro.Window();
    ColorPickerWindow:SetSize(300,180);
    ColorPickerWindow:SetPosition(100,100);
    ColorPickerWindow:SetText("Color Picker");

    -- slightly not-black background to see the edges more easily:
    local background = Turbine.UI.Control();
    background:SetParent(ColorPickerWindow);
    background:SetSize(284, 107);
    background:SetPosition(8, 38);
    background:SetBackColor(Turbine.UI.Color(0.1, 0.1, 0.1));

    -- Color Picker
    local colorPicker = ColorPicker.Create();
    colorPicker:SetParent(ColorPickerWindow);
    colorPicker:SetSize(280,70);
    colorPicker:SetPosition(10,40);

    local red = SETTINGS.OPAQUE_WIN.RED;
    local green = SETTINGS.OPAQUE_WIN.GREEN;
    local blue = SETTINGS.OPAQUE_WIN.BLUE;

    local redNum = math.floor((red * 255) + 0.5);
    local greenNum = math.floor((green * 255) + 0.5);
    local blueNum = math.floor((blue * 255) + 0.5);

    -- Current color preview
    local colorPreview = Turbine.UI.Control();
    colorPreview:SetParent(ColorPickerWindow);
    colorPreview:SetSize(23,23);
    colorPreview:SetPosition(95,120);
    colorPreview:SetBackColor(Turbine.UI.Color(red, green, blue));

    -- Color Label
    local textColor = Turbine.UI.Color((229/255),(209/255),(136/255)); -- beige
    local textFont = Turbine.UI.Lotro.Font.TrajanPro14;
    local colorLabel = Turbine.UI.Label();
    colorLabel:SetParent(ColorPickerWindow);
    colorLabel:SetPosition(colorPreview:GetLeft() + 30,120);
    colorLabel:SetSize(220,23);
    colorLabel:SetForeColor(textColor);
    colorLabel:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
    colorLabel:SetFont(textFont);
    colorLabel:SetText(string.format("Hex: #%02x%02x%02x", redNum, greenNum, blueNum));

    -- respond to clicks:
    colorPicker.LeftClick = function ()
        colorPreview:SetBackColor(colorPicker:GetTurbineColor());
        colorLabel:SetText("Hex: #" .. colorPicker:GetHexColor());
    end

    -- Button to save:
    local saveButton = Turbine.UI.Lotro.Button();
    saveButton:SetParent(ColorPickerWindow);
    saveButton:SetSize(100, 25);
    saveButton:SetPosition(100, 150);
    saveButton:SetText("Save");
    saveButton.Click = function(sender, args)
        local r,g,b = colorPicker:GetRGBColor();
        SETTINGS.OPAQUE_WIN.RED = r / 255;
        SETTINGS.OPAQUE_WIN.GREEN = g / 255;
        SETTINGS.OPAQUE_WIN.BLUE = b / 255;
        UpdateWindowBackColor();
    end
end

Call this function in Main():

    CreateMainWindow();
    CreateColorPickerWindow();

And finally, add a new string and a button in the Options window to launch the color picker:

-- _LANG:
        ["CHANGE_COLOR"] = {
            [EN] = "Change Background Color";
        };

-- DrawOptionsControl():
    -- Add a button to open the color picker to choose the background color:
    local changeColorButton = Turbine.UI.Lotro.Button();
    changeColorButton:SetParent(options);
    changeColorButton:SetPosition(10, 150);
    changeColorButton:SetSize(250, 25);
    changeColorButton:SetText(GetString(_LANG.OPTIONS.CHANGE_COLOR));
    changeColorButton.Click = function(sender, args)
        ColorPickerWindow:SetVisible(true);
    end

Success! We have integrated a 3rd-party library for the color picker, and can now change the background from black to a wide variety of colors.

We can update our todo file:

v1.0.1 features:
    Color Picker for background color

Possible features:
    Only get screen size when it changes, not during every move/resize

And our readme file:

v1.0.1
    Player can use their current settings to be the default for other characters that don't have their own settings.
    Added option for the background color.

Summary

That’s it for today’s topics. We have added in two nice-to-have features: Overriding the hard-coded defaults and changing the background color. While modifying an already-published plugin, we have continued to track our changes in our readme document so it is easy to tell users what has changed.

Leave a Reply

Your email address will not be published. Required fields are marked *