Plugin Along Episode 3: Developing a Quest Tracker Opacity Plugin part 2 of 3

Welcome back to Plugin Along, where we talk about LOTRO Plugins! Last time we covered the problem we’re trying to solve (lack of opacity behind quest tracker text), Thurallor’s simple solution and some possible improvements we could make, some basic project management with readme.txt and todo.txt files, using source control to take snapshots of our work, and making a window movable and resizable.

This time we’re going to add options so the player can change the opacity of the window and lock the window, localize the plugin, and support saving and loading plugin data.

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

Change Opacity

For this change, we can take advantage of the (mostly undocumented, but talked about here) LOTRO Plugin Options Panel feature. You can assign a function to the global variable plugin that returns a control:

options = Turbine.UI.Control();
options:SetBackColor(Turbine.UI.Color(0.1, 0.1, 0.1));
plugin.GetOptionsPanel = function(self) return options; end

The only thing this control does is change its background color to dark grey, and it looks like this:

Let’s add a scrollbar that can be used to change the opacity from 0 to 100, and a label that shows the current value. When the scrollbar value changes we update the label and change the background of the window.

local options = Turbine.UI.Control();
plugin.GetOptionsPanel = function(self) return options; end

options:SetBackColor(Turbine.UI.Color(0.1, 0.1, 0.1));
options:SetWidth(250);
options:SetHeight(125);

-- Add a label:
local opacityLabel = Turbine.UI.Label();
opacityLabel:SetParent(options);
opacityLabel:SetSize(200, 25);
opacityLabel:SetPosition(10, 70);
opacityLabel:SetText(string.format("Opacity: %d%%", opacity));

-- Add a scrollbar to control the opacity:
local opacityScrollbar = Turbine.UI.Lotro.ScrollBar();
opacityScrollbar:SetParent(options);
opacityScrollbar:SetSize(200, 10);
opacityScrollbar:SetOrientation(Turbine.UI.Orientation.Horizontal);
opacityScrollbar:SetPosition(10, 90);
opacityScrollbar:SetValue(opacity * 100);
opacityScrollbar.ValueChanged = function(sender, args)
    opacity = sender:GetValue() / 100;
    opacityLabel:SetText(string.format("Opacity: %d%%", sender:GetValue()));
    window:SetBackColor(Turbine.UI.Color(opacity, 0, 0, 0));
end

Note that in the initial plugin, opacity was as number between 0 and 1, where 0 means 0% opaque (fully see-through), .5 means 50% opaque (partially see-through), and 1 means 100% opaque (not at all see-through). This is because the SetBackColor() function takes a value between 0 and 1. The scrollbar, however, uses values between 0 and 100. This means that we need to do opacity * 100 when setting the scrollbar value from the opacity, and sender:GetValue() / 100 when converting the scrollbar value back into opacity.

Now that the window is resizable by the player, we can update our readme:

v1.0.0 changes:
  Window can be dragged.
  Window can be resized.
  Opacity can be changed.

Our todo file can also be updated:

Known bugs:
  Window absorbs mouse clicks, means you can't click things behind it.

v1.0.0 features:
  Adjust the opacity of the window in-game
  Save the window size/position separately for each character
  Localize the window text

Potential features:
  Save the current window size/location/opacity as the default for other characters.

Mouse Absorbing Bug

Now that we have an options panel, we can address the “known bug” that the window absorbs mouse clicks. That means you can’t click on things that are behind the window, because you’ll end up moving the window instead. We can address this by deciding that there is an “unlocked” state where you can move or resize the window, and a “locked” state where the window ignores the mouse completely.

-- Add checkbox to "lock" the window
local lockedCheckbox = Turbine.UI.Lotro.CheckBox();
lockedCheckbox:SetParent(options);
lockedCheckbox:SetSize(200, 40);
lockedCheckbox:SetPosition(10, 10);
lockedCheckbox:SetText("Lock the window in place");
lockedCheckbox:SetChecked(false);
lockedCheckbox.CheckedChanged = function(sender, args)
    window:SetMouseVisible(not sender:IsChecked());
end

Now that the bug is fixed, we can update our readme:

v1.0.0 changes:
  Window can be dragged.
  Window can be resized.
  Opacity can be changed.
  Bug fix: window can be locked, so it does not respond to the mouse.

Our todo file can also be updated:

Known bugs:
  Window absorbs mouse clicks, means you can't click things behind it.

v1.0.0 features:
  Save the window size/position separately for each character
  Localize the window text

Potential features:
  Save the current window size/location/opacity as the default for other characters.

Save and Load Settings

All of our new features are great, but currently we have to redo them each time the plugin loads. We’d prefer to save things like window position so that the window shows up in the same place next time the plugin is loaded.

LOTRO Plugins can use the Turbine.PluginData.Save() and Turbine.PluginData.Load() functions to save data to a file and load data from a file. When you save or load, you specify the scope (Account, Server, or Character), and name of the file. It’s a good idea to include your plugin’s name in the filename so that you don’t accidentally collide with the filename another plugin uses. For instance, “Settings” is not a good choice compared to “OpaqueQuestTracker_Settings”.

To start, let’s set up what data we’re expecting, and the defaults to use if there is no save file:

DEFAULT_SETTINGS = {
    ["OPAQUE_WIN"] = {
        ["LEFT"] = .5;
        ["TOP"] = .5;
        ["WIDTH"] = .25;
        ["HEIGHT"] = .25;
        ["OPACITY"] = .5; -- 50%
        ["LOCKED"] = false;
    };
};

SETTINGS = {};

Early on during plugin load, we can try to load our settings from a file, and use these defaults if there is no file:

local savedSettings = {};
savedSettings = Turbine.PluginData.Load(Turbine.DataScope.Character, "OpaqueQuestTracker_Settings");

-- did we load something good?
if (type(savedSettings) == 'table') then
    -- yes, use those
    SETTINGS = savedSettings;
else
    -- no, start with the default values
    SETTINGS = DEFAULT_SETTINGS;
end

Next, we can request to be notified when the plugin is being unloaded, and save the current state:

-- Ask to know when our plugin will be unloaded:
Turbine.Plugin.Unload = function(sender, args)
    Turbine.PluginData.Save(Turbine.DataScope.Character, "OpaqueQuestTracker_Settings", SETTINGS);
    Turbine.Shell.WriteLine("'Opaque Quest Tracker' unloaded");
end

Finally, we need to use SETTINGS instead of hard-coded values, and update SETTINGS when they change:

left = 0.5; top = 0.3; width = 0.25; height = 0.25; opacity = 0.5;

...

-- Use settings while creating the window:
window:SetPosition(SETTINGS.OPAQUE_WIN.LEFT * screenWidth, SETTINGS.OPAQUE_WIN.TOP * screenHeight);
window:SetSize(SETTINGS.OPAQUE_WIN.WIDTH * screenWidth, SETTINGS.OPAQUE_WIN.HEIGHT * screenHeight);
window:SetBackColor(Turbine.UI.Color(SETTINGS.OPAQUE_WIN.OPACITY, 0, 0, 0));
window:SetMouseVisible(not SETTINGS.OPAQUE_WIN.LOCKED);

-- Modify the settings in MouseMove after the call to Onscreen():
local screenWidth, screenHeight = Turbine.UI.Display:GetSize();
SETTINGS.OPAQUE_WIN.LEFT = window:GetLeft() / screenWidth;
SETTINGS.OPAQUE_WIN.TOP = window:GetTop() / screenHeight;
SETTINGS.OPAQUE_WIN.WIDTH = window:GetWidth() / screenWidth;
SETTINGS.OPAQUE_WIN.HEIGHT = window:GetHeight() / screenHeight;

...

-- Use SETTINGS when setting up the Options control:
lockedCheckbox:SetChecked(SETTINGS.OPAQUE_WIN.LOCKED);
lockedCheckbox.CheckedChanged = function(sender, args)
    SETTINGS.OPAQUE_WIN.LOCKED = sender:IsChecked();
    window:SetMouseVisible(not SETTINGS.OPAQUE_WIN.LOCKED);
end

...

opacityLabel:SetText(string.format("Opacity: %d%%", SETTINGS.OPAQUE_WIN.OPACITY * 100));

...

opacityScrollbar:SetValue(SETTINGS.OPAQUE_WIN.OPACITY * 100);
opacityScrollbar.ValueChanged = function(sender, args)
    SETTINGS.OPAQUE_WIN.OPACITY = sender:GetValue() / 100;
    opacityLabel:SetText(string.format("Opacity: %d%%", sender:GetValue()));
    window:SetBackColor(Turbine.UI.Color(SETTINGS.OPAQUE_WIN.OPACITY, 0, 0, 0));
end

Now the last-known data for the window will be used when the plugin loads, or if some sensible defaults if there is no save file. Now that the options are saved and loaded, we can update our readme:

v1.0.0 changes:
  Window can be dragged.
  Window can be resized.
  Opacity can be changed.
  Bug fix: window can be locked, so it does not respond to the mouse.
  Window location, size, opacity, and lock status are saved for each character.

Our todo file can also be updated:

v1.0.0 features:
    Add Load and Unload messages.
    Localize any text.
    Ship it!

Potential features:
  Save the current window size/location/opacity as the default for other characters.

Summary

That’s it for today’s topics. We’ve continued making improvements to the Opaque Quest Tracker plugin by adding the ability to change the window opacity and lock state through the options panel, and the ability to save and load options. Next time we’ll cover basic localization, how to save/load decimal numbers correctly, and how to publish our plugin so others can download it.

2 comments

Leave a Reply

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