lotrointerface.com
Search Downloads

LoTROInterface SVN SequenceBars

[/] [tags/] [2.23/] [Thurallor/] [SequenceBars/] [SequenceEditor.lua] - Rev 98

Compare with Previous | Blame | View Log

SequenceEditor = class(Turbine.UI.Lotro.Window);

-- Class variable keeping track of all SequenceEditor instances:
SequenceEditor.instances = {};

function SequenceEditor:Constructor(bar, settings)
    Turbine.UI.Lotro.Window.Constructor(self);
    SequenceEditor.instances[self] = true;

    self.bar = bar;
    self.manager = self.bar.manager;
    self.settings = settings;
    self.globals = self.manager.settings;

    self:SetText(bar:GetName());
    self:SetZOrder(2);
    self:SetVisible(true);
    
    local minWidth = L:GetText("/SequenceEditor/MinWidth");
    if (not self.settings.sequenceEditor) then
        self.settings.sequenceEditor = {};
        self.settings.sequenceEditor.defaultIcon = resources.BlankIcons[3];
    end
    if (self.settings.sequenceEditor.position) then
        self:SetPosition(unpack(self.settings.sequenceEditor.position));
    else
        self:SetPosition(self.bar:GetPosition());
    end
    if (self.settings.sequenceEditor.size) then
        self:SetSize(unpack(self.settings.sequenceEditor.size));
    else
        self:SetSize(minWidth, 187);
    end
    
    self.relatedSlots = {};
    
    -- Enforce minimum size
    self:SetMinimumWidth(minWidth);
    if (self:GetWidth() < minWidth) then
        self:SetWidth(minWidth);
    end
    self:SetMinimumHeight(142);
    self:SetResizable(true);
    
    self:Redraw();
    AddCallback(self, "SizeChanged", function()
        self:_SizeChanged();
    end);
    
    self:SelectSlot(nil);
end

function SequenceEditor:UpdateBar()
    self.bar:ShortcutChanged();
    self.manager:NotifyIncluders(self.bar:GetID());
end

function SequenceEditor:Activated()
    -- Find the maximum Z order among all other Sequence Editors.
    local maxZ = self:GetZOrder() - 1;
    for editor in keys(SequenceEditor.instances) do
        if (editor ~= self) then
            local z = editor:GetZOrder();
            if (z > maxZ) then
                maxZ = z;
            end
        end
    end
    
    if (maxZ == 2147483647) then
        -- Move all other SequenceEditors back to make room at the front.
        for editor in keys(SequenceEditor.instances) do
            editor:SetZOrder(editor:GetZOrder() - 1);
        end
        maxZ = maxZ - 1;
    end

    -- Move the newly-activated Sequence Editor to the front of the stack.
    self:SetZOrder(maxZ + 1);
end

function SequenceEditor:SetText(barName)
    local title = L:GetText("/SequenceEditor/Title");
    title = string.gsub(title, "<name>", barName);
    Turbine.UI.Lotro.Window.SetText(self, title);
end

function SequenceEditor:SaveSettings()
    self.bar:SaveSettings();
end

function SequenceEditor:Redraw()
    local width, height = self:GetSize();

    local xMargin = 16;
    local topMargin = 35;
    local bottomMargin = 42;
    local slotTabCardWidth = L:GetNumber("/SequenceEditor/SlotTabCardWidth");

    if (not self.slotTabCard) then
        self.slotTabCard = Thurallor.UI.TabCard();
        self.slotTabCard:SetParent(self);
        self.slotTabCard:SetTop(topMargin);
        self.slotTabCard:SetWidth(slotTabCardWidth);
        self.slotTabCard:SetTabWidth(L:GetNumber("/SequenceEditor/SlotTabWidth"));
        self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab"));
        
        -- When mouse reenters the area, redisplay the current slot in case options have changed.
        self.slotTabCard.MouseEnter = function(ctl)
            if (not ctl.mouseInside) then
                ctl.mouseInside = true;
                self:DisplaySlot(self.selectedSlot);
            end
        end
        self.MouseEnter = function()
            if (self.slotTabCard.mouseInside) then
                local left, top = self.slotTabCard:GetMousePosition();
                local width, height = self.slotTabCard:GetSize();
                if ((left < 0) or (left >= width) or (top < 0) or (top >= height)) then
                    self.slotTabCard.mouseInside = false;
                end
            end
        end
    end
    self.slotTabCard:SetLeft(width - slotTabCardWidth - xMargin);
    self.slotTabCard:SetHeight(height - topMargin - bottomMargin);
    
    if (not self.sequenceTabCard) then
        self.sequenceTabCard = Thurallor.UI.TabCard();
        self.sequenceTabCard:SetParent(self);
        self.sequenceTabCard:SetPosition(xMargin, topMargin);
        self.sequenceTabCard:SetTabWidth(120);
        self.sequenceTabCard:SetTabText(L:GetText("/SequenceEditor/SequenceTab"));
        self.slotPanel = Turbine.UI.Control();
        self.sequenceTabCard:SetInteriorControl(self.slotPanel);
        self.sequenceTabCard:SetInteriorAlignment(Turbine.UI.ContentAlignment.TopLeft);
    end
    self.sequenceTabCard:SetSize(self.slotTabCard:GetLeft() - xMargin - 8, self.slotTabCard:GetHeight());

    self:DeleteEmptyTrailingSlots();
    local slotPanelWidth = self.sequenceTabCard:GetWidth() - 6;
    self.slotSize = self.settings.slotSize + self.settings.slotSpacing;
    self.slotsWide = math.floor((slotPanelWidth - 3) / self.slotSize);
    self.slotsHigh = math.max(math.ceil(#self.settings.sequenceItemInfo / self.slotsWide) + 1, 2);
    self:ExtendSequenceTo(self.slotsHigh * self.slotsWide);
    self.slotPanel:SetSize(self.slotsWide * self.slotSize + 3, self.slotsHigh * self.slotSize + 3);
    self.slotPanel:SetBackColor(self.bar.color);
    
    -- Draw some quickslots
    if (not self.sequenceItems) then
        self.sequenceItems = {};
    end
    local s = 1;
    for y = 1, self.slotsHigh, 1 do
        for x = 1, self.slotsWide, 1 do
            self:RebuildSlot(s, x, y);
            s = s + 1;
        end
    end
    
    if (not self.instructions) then
        self.instructions = Turbine.UI.Label();
        self.instructions:SetParent(self);
        self.instructions:SetMouseVisible(false);
        self.instructions:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter);
        self.instructions:SetFont(Turbine.UI.Lotro.Font.TrajanPro15);
        self.instructions:SetMultiline(true);
        self.instructions:SetForeColor(Turbine.UI.Color.PaleGoldenrod);
        self.instructions:SetText(L:GetText("/SequenceEditor/Instructions"));
    end
    self.instructions:SetSize(self:GetWidth() - 2 * xMargin, 30);
    self.instructions:SetPosition(xMargin, self:GetHeight() - 40);

    if (self.selectedSlot) then
        self:SelectSlot(self.selectedSlot);
    end
end

function SequenceEditor:SetMinimumHeight(height)
    Turbine.UI.Window.SetMinimumHeight(self, height);
    if (self:GetHeight() < height) then
        self:SetHeight(height);
    end
end

function SequenceEditor:_SizeChanged()
    self:Redraw();
    self.settings.sequenceEditor.size = {self:GetSize()};
    self:SaveSettings();
end

function SequenceEditor:PositionChanged()
    self.settings.sequenceEditor.position = {self:GetPosition()};
    self:SaveSettings();
end

function SequenceEditor:DisplaySlot(s)
    if (self.ItemMovedCallback) then
        RemoveCallback(Thurallor.Utils.Watcher, "ItemMoved", self.ItemMovedCallback);
        self.ItemMovedCallback = nil;
    end

    self.slotTabCard:SetInteriorControl(nil);
    self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab"));
    if ((s == nil) or (s > #self.settings.sequenceItemInfo)) then
        self.slotTabCard:SetVisible(false);
        return;
    else
        self.slotTabCard:SetVisible(true);
    end
    self:ExtendSequenceTo(s);
    local info = self.settings.sequenceItemInfo[s];
    local slotTabCardWidth = L:GetNumber("/SequenceEditor/SlotTabCardWidth");

    -- Update slot tab caption and create properties container
    self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab") .. " " .. tostring(s));
    self.slotProperties = Turbine.UI.Control();
    self.slotTabCard:SetInteriorControl(self.slotProperties);
    local width = self.slotTabCard:GetWidth() - 17;
    self.slotProperties:SetWidth(width);
    
    local function UpdateIcon()
        local center = math.floor((slotTabCardWidth - 20) / 2);
        self.selectedIcon = self:BuildSlot(s, self.slotProperties, self.selectedIcon, center - 16, 10);
        self.selectedIcon.numLabel:SetText(nil);     -- don't display slot number overlay
        self.selectedIcon.numLabel.MouseClick = nil; -- disable right-click menu
        self.selectedIcon.numLabel.MouseDown = nil;  -- disable dragging
        self.selectedIcon:SetActionEnabled(true);
        self.selectedIcon:SetAllowDrop(true);
        self:RebuildSlot(s, nil, nil, true);
    end
    UpdateIcon();

    -- Create a function for adding a centered line of text to the properties control
    local AddLabel = function(top, text, color)
        local label = Turbine.UI.Label();
        label:SetParent(self.slotProperties);
        label:SetPosition(0, top);
        label:SetSize(slotTabCardWidth - 20, 16);
        top = top + 16;
        label:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter);
        label:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        label:SetForeColor(color);
        label:SetMarkupEnabled(true);
        label:SetText(text);
        return top, label;
    end
    
    -- Create a function for adding a centered button to the properties control
    local AddButton = function(top, width, text)
        local button = Turbine.UI.Lotro.Button();
        button:SetParent(self.slotProperties);
        local left = math.floor((self.slotProperties:GetWidth() - width) / 2);
        button:SetPosition(left, top + 2);
        button:SetWidth(width);
        button:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        button:SetText(text);
        top = top + 2 + button:GetHeight();
        return top, button;
    end
    
    -- Create a function for adding a centered text box to the properties control
    local AddTextBox = function(top, text, color)
        local textBox = Turbine.UI.Lotro.TextBox();
        textBox:SetParent(self.slotProperties);
        textBox:SetPosition(0, top);
        textBox:SetSize(slotTabCardWidth - 20, 20);
        top = top + 20;
        textBox:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
        textBox:SetMultiline(false);
        textBox:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        textBox:SetForeColor(color);
        textBox:SetText(text);
        return top, textBox;
    end

    -- Create a function for adding a left-justified checkbox to the properties control
    local AddCheckBox = function(top, text, color, checked)
        local checkBox = Turbine.UI.Lotro.CheckBox();
        checkBox:SetParent(self.slotProperties);
        checkBox:SetPosition(0, top);
        checkBox:SetSize(slotTabCardWidth - 20, 20);
        top = top + 20;
        checkBox:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
        checkBox:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        checkBox:SetForeColor(color);
        checkBox:SetText(text);
        if (checked) then
            checkBox:SetChecked(true);
        end
        return top, checkBox;
    end
    
    -- Create a function for adding a left-justified radio button to the properties control
    local AddRadioButton = function(top, text, color, checked)
        local button = Thurallor.UI.RadioButton(self.slotProperties, text, checked);
        button:SetPosition(0, top);
        button:SetSize(slotTabCardWidth - 20, 16);
        top = top + 16;
        button:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        button:SetForeColor(color);
        return top, button;
    end

    -- Create a function for adding a drop-down listbox to the properties control
    local AddDropDown = function(top, items, default)
        local dropDown = Thurallor.UI.DropDown(items, default);
        dropDown:SetParent(self.slotProperties);
        dropDown:SetPosition(0, top);
        dropDown:SetWidth(slotTabCardWidth - 20);
        top = top + dropDown:GetHeight();
        return top, dropDown;
    end

    -- Create a function for adding a pull-down hierarchical menu to the properties control
    local AddPullDown = function(top, items, default)
        local pullDown = Thurallor.UI.PullDownMenu(items, default);
        pullDown:SetParent(self.slotProperties);
        pullDown:SetPosition(0, top);
        pullDown:SetWidth(slotTabCardWidth - 20);
        top = top + pullDown:GetHeight();
        return top, pullDown;
    end

    -- Create a function for adding a horizontal scrollbar to the properties control
    local AddSlider = function(top, minimum, maximum, value)
        local scrollBar = Turbine.UI.Lotro.ScrollBar();
        scrollBar:SetParent(self.slotProperties);
        scrollBar:SetPosition(0, top);
        scrollBar:SetSize(slotTabCardWidth - 20, 10);
        scrollBar:SetMinimum(minimum);
        scrollBar:SetMaximum(maximum);
        scrollBar:SetSmallChange(1);
        scrollBar:SetValue(value);
        top = top + 10;
        return top, scrollBar;
    end

    -- Create a function for adding an expand/collapse section header to the properties control
    local AddSection = function(top, text, color, expanded)
        local button = Turbine.UI.Control();
        button:SetParent(self.slotProperties);
        button:SetPosition(0, top);
        button:SetSize(16, 16);
        button:SetBlendMode(Turbine.UI.BlendMode.Overlay);
        local label = Turbine.UI.Label();
        label:SetParent(self.slotProperties);
        label:SetPosition(18, top);
        label:SetSize(slotTabCardWidth - 20 - 18, 16);
        top = top + 16;
        label:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
        label:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        label:SetForeColor(color);
        label:SetMarkupEnabled(true);
        label:SetText(text);
        function button.MouseClick()
            if (button.expanded) then
                button:SetBackground(resources.Icon.Expand);
                button.expanded = false;
                DoCallbacks(button, "ExpandedChanged", false);
            else
                button:SetBackground(resources.Icon.Collapse);
                button.expanded = true;
                DoCallbacks(button, "ExpandedChanged", true);
            end
        end
        label.MouseClick = button.MouseClick;
        button.expanded = not expanded;
        button:MouseClick();
        return top, button;
    end
    
    -- Draw the caption
    local top = 54;
    if (info.class == "Turbine.UI.Lotro.Quickslot") then
        top = AddLabel(top, L:GetText("/SequenceEditor/StandardQuickslot"), Turbine.UI.Color.PaleGoldenrod);
    elseif (info.class == "Turbine.UI.Control") then
        top = AddLabel(top, L:GetText("/SequenceEditor/SpecialSlot") .. ":", Turbine.UI.Color.PaleGoldenrod);
        local label;
        top, label = AddLabel(top, info.toolTip, Turbine.UI.Color.PaleGoldenrod);
        -- Some German strings need line wrapping here.
        label:SetHeight(32);
        label:SetTextAlignment(Turbine.UI.ContentAlignment.TopCenter);
    end

    -- Draw the arguments
    if (info.class == "Turbine.UI.Lotro.Quickslot") then
        top = top + 16; -- skip a line
        for key, value in pairs(Turbine.UI.Lotro.ShortcutType) do
            if (info.type == value) then
                top = AddLabel(top, L:GetText("/SequenceEditor/ShortcutType") .. ": " .. L:GetText("/SequenceEditor/ShortcutTypes/" .. key), Turbine.UI.Color.Goldenrod);
                break;
            end
        end
        
        if (info.type == Turbine.UI.Lotro.ShortcutType.Alias) then
            -- Allow the user to specify the command
            top = top + 16; -- skip a line
            top = AddLabel(top, L:GetText("/SequenceEditor/ChatCommand") .. ":", Turbine.UI.Color.Goldenrod);
            local textBox;
            top, textBox = AddTextBox(top, info.Data, Turbine.UI.Color.White);
            textBox.TextChanged = function(sender, args)
                local text = sender:GetText();
                -- Remove carriage returns
                local stripped = string.gsub(text, "\n", "");
                if (stripped ~= text) then
                    sender:SetText(stripped);
                    text = stripped;
                end
                info.Data = text;
                self:UpdateBar();
            end
        elseif (info.type == Turbine.UI.Lotro.ShortcutType.Item) then
            local shortcut = Turbine.UI.Lotro.Shortcut(info.type, info.Data);
            local item = shortcut:GetItem();
            if (item:GetMaxStackSize() == 1) then -- equippable
                                    
                -- Allow the user to specify immediate advance, or wait-for-equip
                top = top + 16; -- skip a line
                top = AddLabel(top, L:GetText("/SequenceEditor/AdvanceToNextSlot") .. ":", Turbine.UI.Color.Goldenrod);
                local button1, button2;
                top, button1 = AddRadioButton(top, L:GetText("/SequenceEditor/WithLeftClick"), Turbine.UI.Color.DarkGoldenrod, (not info.advanceEvent));
                top, button2 = AddRadioButton(top, L:GetText("/SequenceEditor/WhenItemEquipped"), Turbine.UI.Color.DarkGoldenrod, info.advanceEvent);
                Thurallor.UI.RadioButton.LinkPeers({button1, button2});
         
                button1.Clicked = function()
                    info.advanceEvent = nil;
                    self:UpdateBar();
                end
                button2.Clicked = function()
                    info.advanceEvent = "ItemEquipped";
                    self:UpdateBar();
                end
            end
        end
        
    elseif (info.class == "Turbine.UI.Control") then
        if (info.type == "Delay") then
            
            -- Allow the user to specify the delay period
            top = top + 16; -- skip a line
            top = AddLabel(top, L:GetText("/SequenceEditor/DelayPeriod") .. ":", Turbine.UI.Color.Goldenrod);
            local textBox;
            _, textBox = AddTextBox(top, info.delay, Turbine.UI.Color.White);
            textBox:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleRight);
            local center = math.floor((slotTabCardWidth - 20) / 2);
            textBox:SetWidth(50);
            textBox:SetLeft(center - 2 - 50);
            top, label = AddLabel(top + 2, L:GetText("/SequenceEditor/Seconds"), Turbine.UI.Color.DarkGoldenrod);
            label:SetLeft(center + 2);
            label:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
            textBox.TextChanged = function(sender, args)
                info.delay = tonumber(sender:GetText());
                info.action = "local item, s = ...; item.bar:StartSlotCooldown(s, " .. tostring(info.delay) .. ");";
                self:UpdateBar();
            end
        elseif (info.type == "GenerateEvent") then
        
            -- Allow the user to specify the event name
            top = top + 16; -- skip a line
            top = AddLabel(top, L:GetText("/SequenceEditor/EventName") .. ":", Turbine.UI.Color.Goldenrod);
            local textBox;
            top, textBox = AddTextBox(top, info.eventName, Turbine.UI.Color.White);
            textBox.TextChanged = function(sender, args)
                local text = sender:GetText();
                -- Remove carriage returns
                local stripped = string.gsub(text, "\n", "");
                if (stripped ~= text) then
                    sender:SetText(stripped);
                    text = stripped;
                end
                info.eventName = text;
                self:UpdateBar();
            end
        elseif (info.type == "SetUnequipDestination") then

            -- Temporary method to allow users to discover bag slot numbers.
            self.ItemMovedCallback = function(sender, args)
                local str = L:GetText("/ItemMoved");
                str = string.gsub(str, "<source>", tostring(args.OldIndex));
                str = string.gsub(str, "<destination>", tostring(args.NewIndex));
                Puts(str);
            end
            AddCallback(Thurallor.Utils.Watcher, "ItemMoved", self.ItemMovedCallback);
        
            -- Allow the user to specify the desired bag slot
            top = top + 16; -- skip a line
            top = AddLabel(top, L:GetText("/SequenceEditor/PreferredBagSlot") .. ":", Turbine.UI.Color.Goldenrod);
            
            local lp = Turbine.Gameplay.LocalPlayer:GetInstance();
            local bags = lp:GetBackpack();
            local inventory = {}, currentItemName;
            for i = 1, bags:GetSize() do
                table.insert(inventory, tostring(i));
                if (i == info.bagSlot) then
                    currentItemName = itemName;
                end
            end
            top, dropDown = AddDropDown(top, inventory, tostring(info.bagSlot));
            dropDown:SetWidth(50);
            dropDown:SetLeft(65);
            dropDown.ItemChanged = function(sender, args)
                info.bagSlot = tonumber(args.Text);
                self:UpdateBar();
            end
        elseif (string.match(info.type, "^Remove ")) then
        
            -- Allow the user to edit choose the item to be unequipped
            top = top + 16; -- skip a line
            top = AddLabel(top, L:GetText("/SequenceEditor/EquipmentType") .. ":", Turbine.UI.Color.Goldenrod);

            local currentItem = string.sub(info.type, 8);
            local prevContext = L:SetContext("/SequenceEditor/EquipmentSlots");
            local eqSlots, localEqSlots = L:GetItems(), {};
            for slot in values(eqSlots) do
                table.insert(localEqSlots, L:GetText(slot));
            end
            table.sort(localEqSlots);
            local dropDown;
            top, dropDown = AddDropDown(top, localEqSlots, L:GetText(currentItem));
            dropDown.ItemChanged = function(sender, args)
                for slot in values(eqSlots) do
                    if (L:GetText(slot) == args.Text) then
                        self:CreateRemovalSlot(s, slot, info.bagSlot);
                        break;
                    end
                end
            end
        elseif (info.type == "If") then
    
            -- Allow the user to specify the conditional expression
            top = top + 16; -- skip a line
            top = AddLabel(top, L:GetText("/SequenceEditor/Condition") .. ":", Turbine.UI.Color.Goldenrod);

            local prevContext = L:SetContext("/SequenceEditor/ConditionTypes");
            local condInfo = {
                ["Always"] =                      { expr = "return true;" };
                ["Never"] =                       { expr = "return false;" };
                ["SkillReady"] =                  {  arg = "skill";
                                                    expr = "return (Thurallor.Utils.Watcher.SkillReady(<skill>));" };
                ["SkillNotReady"] =               {  arg = "skill";
                                                    expr = "return (not Thurallor.Utils.Watcher.SkillTrained(<skill>));" };
                ["SkillTrained"] =                {  arg = "skill";
                                                    expr = "return (Thurallor.Utils.Watcher.SkillReady(<skill>));" };
                ["SkillNotTrained"] =             {  arg = "skill";
                                                    expr = "return (not Thurallor.Utils.Watcher.SkillTrained(<skill>));" };
                ["PlayerEffectDisease"] =         { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Disease);" };
                ["PlayerEffectFear"] =            { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Fear);" };
                ["PlayerEffectPoison"] =          { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Poison);" };
                ["PlayerEffectWound"] =           { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Wound);" };
                ["PlayerEffectOther"] =           {  arg = "effect";
                                                    expr = "return Thurallor.Utils.Watcher.PlayerHasEffect(<effect>);" };
                ["NotPlayerEffectOther"] =        {  arg = "effect";
                                                    expr = "return (not Thurallor.Utils.Watcher.PlayerHasEffect(<effect>));" };
-- Target effects tracking is so buggy as to be useless.  Hoping Turbine addresses this bug soon.                                                    
--                ["TargetEffectDisease"] =         { expr = "return Thurallor.Utils.Watcher.TargetHasEffectCategory(Turbine.Gameplay.EffectCategory.Disease);" };
--                ["TargetEffectFear"] =            { expr = "return Thurallor.Utils.Watcher.TargetHasEffectCategory(Turbine.Gameplay.EffectCategory.Fear);" };
--                ["TargetEffectPoison"] =          { expr = "return Thurallor.Utils.Watcher.TargetHasEffectCategory(Turbine.Gameplay.EffectCategory.Poison);" };
--                ["TargetEffectWound"] =           { expr = "return Thurallor.Utils.Watcher.TargetHasEffectCategory(Turbine.Gameplay.EffectCategory.Wound);" };
--                ["TargetEffectOther"] =           {  arg = "effect";
--                                                    expr = "return Thurallor.Utils.Watcher.TargetHasEffect(<effect>);" };
--                ["NotTargetEffectOther"] =        {  arg = "effect";
--                                                    expr = "return (not Thurallor.Utils.Watcher.TargetHasEffect(<effect>));" };
                ["PlayerMorale < x"] =            {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetMorale() < <x>)" };
                ["PlayerMorale < x%"] =           {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetMorale() < player:GetBaseMaxMorale() * <x> / 100)" };
                ["PlayerMorale > x"] =            {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetMorale() > <x>)" };
                ["PlayerMorale > x%"] =           {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetMorale() > player:GetBaseMaxMorale() * <x> / 100)" };
                ["TargetMorale < x"] =            {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and (target:GetMorale() < <x>))" };
                ["TargetMorale < x%"] =           {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and (target:GetMorale() < target:GetBaseMaxMorale() * <x> / 100))" };
                ["TargetMorale > x"] =            {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and (target:GetMorale() > <x>))" };
                ["TargetMorale > x%"] =           {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and (target:GetMorale() > target:GetBaseMaxMorale() * <x> / 100))" };
                ["PlayerPower < x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetPower() < <x>)" };
                ["PlayerPower < x%"] =            {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetPower() < player:GetBaseMaxPower() * <x> / 100)" };
                ["PlayerPower > x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetPower() > <x>)" };
                ["PlayerPower > x%"] =            {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetPower() > player:GetBaseMaxPower() * <x> / 100)" };
                ["TargetPower < x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and (target:GetPower() < <x>))" };
                ["TargetPower < x%"] =            {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and (target:GetPower() < target:GetBaseMaxPower() * <x> / 100))" };
                ["TargetPower > x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and (target:GetPower() > <x>)" };
                ["TargetPower > x%"] =            {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and (target:GetPower() > target:GetBaseMaxPower() * <x> / 100))" };
                ["BeorningPlayerWrath < x%"] =    {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetWrath and (attribs:GetWrath() < <x>);" };
                ["BeorningPlayerWrath > x%"] =    {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetWrath and (attribs:GetWrath() > <x>);" };
                ["BeorningInBearForm"] =          { expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.IsInBearForm and attribs:IsInBearForm();" };
                ["BeorningNotInBearForm"] =       { expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.IsInBearForm and (not attribs:IsInBearForm());" };
                ["ItemEquipped"] =                {  arg = { "eqItemName", "eqSlot" };
                                                    expr = "return IsEquipped(<eqItemName>, <eqSlot>);" };
                ["ItemNotEquipped"] =             {  arg = { "eqItemName", "eqSlot" };
                                                    expr = "return (not IsEquipped(<eqItemName>, <eqSlot>));" };
                ["ItemQuantity > x"] =            {  arg = { "stackItemName", "x" };
                                                    expr = "return (Thurallor.Utils.Watcher.GetItemQuantity(<stackItemName>) > <x>);" };
                ["ItemQuantity < x"] =            {  arg = { "stackItemName", "x" };
                                                    expr = "return (Thurallor.Utils.Watcher.GetItemQuantity(<stackItemName>) < <x>);" };
                ["LuaScript"] =                   {  arg = "script";
                                                    expr = "<script>" };
            };
            local condNames = {};
            for k in keys(condInfo) do
                table.insert(condNames, L:GetText(k));
            end
            table.sort(condNames);

            local function SwitchArgs(arg)
                if (not info.condArgs) then
                    info.condArgs = {};
                end
                if (not arg) then
                    info.condArgs = nil;
                elseif ((arg == "x") and not info.condArgs.x) then
                    info.condArgs = { x = 0 };
                elseif ((arg == "skill") and not info.condArgs.skill) then
                    info.condArgs = { skill = 0 };
                elseif ((arg == "effect") and not info.condArgs.effect) then
                    info.condArgs = { effect = "nil" };
                elseif ((arg == "script") and not info.condArgs.script) then
                    if (info.condExpr) then
                        local script = info.condExpr;
                        if (info.condArgs) then
                            for name, value in pairs(info.condArgs) do
                                script = string.gsub(script, "<" .. name .. ">", tostring(value));
                            end
                        end
                        info.condArgs = { script = script };
                    else
                        info.condArgs = { script = "return true;" };
                    end
                elseif (type(arg) == "table") then
                    -- Multiple args
                    local prevArgs = info.condArgs;
                    info.condArgs = {};
                    for argName in values(arg) do
                        if (prevArgs[argName]) then
                            info.condArgs[argName] = prevArgs[argName];
                        end
                        if ((argName == "eqItemName") and not info.condArgs.eqItemName) then
                            info.condArgs.eqItemName = "nil";
                        elseif ((argName == "stackItemName") and not info.condArgs.stackItemName) then
                            info.condArgs.stackItemName = "nil";
                        elseif ((argName == "eqSlot") and not info.condArgs.eqSlot) then
                            info.condArgs.eqSlot = "nil";
                        elseif ((argName == "x") and not info.condArgs.x) then
                            info.condArgs.x  = 0;
                        end
                    end
                end
                info.condExpr = condInfo[info.condName].expr;
            end
            
            local dropDown;
            top, dropDown = AddDropDown(top, condNames, L:GetText(info.condName));
            dropDown:SetExpandedWidth(300);
            dropDown:SetAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
            dropDown.ItemChanged = function(sender, args)
                local prevContext = L:SetContext("/SequenceEditor/ConditionTypes");
                info.condName = L:GetItem(args.Text);
                L:SetContext(prevContext);
                local arg = condInfo[info.condName].arg;
                SwitchArgs(arg);
                self:UpdateBar();
                self:DisplaySlot(self.selectedSlot);
            end
            
            SwitchArgs(condInfo[info.condName].arg);
            L:SetContext(prevContext);
            
            -- Display additional arguments depending on the current condition type
            if (info.condArgs) then
                if (info.condArgs.stackItemName) then
                    local function GetStackableItems()
                        local hash = {};
                        local backpack = self.manager.player:GetBackpack();
                        for i = 1, backpack:GetSize(), 1 do
                            local item = backpack:GetItem(i);
                            if (item and (item:GetMaxStackSize() > 1)) then
                                hash[item:GetName()] = true;
                            end
                        end
                        local items = {};
                        for key in sorted_keys(hash) do
                            table.insert(items, key);
                        end
                        return items;
                    end
                    local stackItemNames = GetStackableItems();
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, stackItemNames);
                    dropDown:SetExpandedWidth(400);
                    dropDown.ItemChanged = function(sender, args)
                        info.condArgs.stackItemName = "\"" .. args.Text .. "\"";
                        self:UpdateBar();
                    end
                    dropDown:SetParent(self.slotProperties);
                    if (info.condArgs.stackItemName ~= "nil") then
                        local name = string.sub(info.condArgs.stackItemName, 2, -2);
                        dropDown:SetText(name);
                    end
                end
                if (info.condArgs.x) then
                    local _, xLabel = AddLabel(top + 3, "", Turbine.UI.Color.DarkGoldenrod);
                    xLabel:SetWidth(30);
                    xLabel:SetFont(Turbine.UI.Lotro.Font.Verdana16);
                    xLabel:SetText("x =");
                    
                    local xField;
                    top, xField = AddTextBox(top + 2, "", Turbine.UI.Color.White);
                    xField:SetLeft(30);
                    xField:SetWidth(slotTabCardWidth - 50);
                    xField.TextChanged = function(sender)
                        info.condArgs.x = tonumber(sender:GetText());
                        if (not info.condArgs.x) then
                            info.condArgs.x = 0;
                        end
                        self:UpdateBar();
                    end
                    xField:SetText(info.condArgs.x);
                    xLabel:SetParent(self.slotProperties);
                    xField:SetText(tostring(info.condArgs.x));
                    xField:SetParent(self.slotProperties);
                end
                if (info.condArgs.skill) then
                    local skills = Thurallor.Utils.Watcher.GetSkillsInfo();
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, skills.names);
                    dropDown:SetExpandedWidth(300);
                    dropDown.ItemChanged = function(sender, args)
                        local skills = Thurallor.Utils.Watcher.GetSkillsInfo();
                        local skill = skills.byName[args.Text];
                        info.condArgs.skill = 0;
                        if (skill) then
                            info.condArgs.skill = skill:GetSkillInfo():GetIconImageID();
                        end
                        self:UpdateBar();
                    end
                    dropDown:SetParent(self.slotProperties);
                    local skill = skills.byIcon[info.condArgs.skill];
                    if (skill) then
                        local name = skill:GetSkillInfo():GetName();
                        dropDown:SetText(name);
                    end
                end
                if (info.condArgs.effect) then
                    local effectNames = Thurallor.Utils.Watcher.GetKnownEffectNames();
                    if (#effectNames >= 1) then
                        table.insert(effectNames, 1, L:GetText("/SequenceEditor/ConditionTypes/OtherEffect"));
                        local dropDown;
                        top, dropDown = AddDropDown(top + 2, effectNames);
                        dropDown:SetExpandedWidth(300);
                        function dropDown.ItemChanged(ctl, args)
                            local effectNames = Thurallor.Utils.Watcher.GetKnownEffectNames();
                            local other = L:GetText("/SequenceEditor/ConditionTypes/OtherEffect");
                            table.insert(effectNames, 1, other);
                            if (args.Text == other) then
                                local ok = L:GetText("/ExportWindow/OK");
                                local window = Alert(other,
                                    L:GetText("/SequenceEditor/ConditionTypes/OtherEffectHelp"),
                                    ok);
                                CenterWindow(window);
                                window.label:SetFont(Turbine.UI.Lotro.Font.Verdana14);
                                window.label:SetText(window.label:GetText());
                                window.buttons[ok].Click = function()
                                    window:Close();
                                end
                                dropDown:SetText(string.gsub(info.condArgs.effect, "\"", ""));
                            elseif (not args.Text) then
                                return;
                            else
                                info.condArgs.effect = "\"" .. args.Text .. "\"";
                                self:UpdateBar();
                            end
                        end
                        dropDown:SetParent(self.slotProperties);
                        local effect = info.condArgs.effect;
                        if (effect == "nil") then
                            effect = effectNames[2];
                        else
                            effect = string.gsub(effect, "\"", "")
                        end
                        dropDown:SetText(effect);
                        dropDown:ItemChanged({Text = effect});
                    end
                end
                if (info.condArgs.script) then
                    top, self.scriptBox = AddTextBox(top + 2, info.condArgs.script, Turbine.UI.Color.White)
                    self.scriptBox:SetFont(Turbine.UI.Lotro.Font.Verdana10);
                    self.scriptBox.prevText = nil;
                    self.scriptBox:SetWantsUpdates(true);
                    self.scriptBox.Update = function(sender)
                        local script = sender:GetText();
                        if (script ~= sender.prevText) then
                            local success, errorMsg = loadstring(script, "Slot " .. tostring(s));
                            if (success) then
                                self.scriptBox:SetBackColor(Turbine.UI.Color.Black);
                                info.condArgs.script = script;
                                self:UpdateBar();
                            else
                                self.scriptBox:SetBackColor(Turbine.UI.Color.Red);
                            end
                        end
                        sender.prevText = script;
                    end
                end
                if (info.condArgs.eqItemName) then
                    local function GetEquippableItems()
                        local hash = { [L:GetText("/SequenceEditor/AnyEqItem")] = true };
                        for container in values({ self.manager.player:GetBackpack(), self.manager.player:GetEquipment() }) do
                            for i = 1, container:GetSize(), 1 do
                                local item = container:GetItem(i);
                                if (item and (item:GetMaxStackSize() == 1)) then
                                    hash[item:GetName()] = true;
                                end
                            end
                        end
                        local items = {};
                        for key in sorted_keys(hash) do
                            table.insert(items, key);
                        end
                        return items;
                    end
                    local eqItemNames = GetEquippableItems();
                    local anyItem = L:GetText("/SequenceEditor/AnyEqItem");
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, eqItemNames);
                    dropDown:SetExpandedWidth(400);
                    dropDown.ItemChanged = function(sender, args)
                        local anyItem = L:GetText("/SequenceEditor/AnyEqItem");
                        if (args.Text == anyItem) then
                            info.condArgs.eqItemName = "nil";
                        else
                            info.condArgs.eqItemName = "\"" .. args.Text .. "\"";
                        end
                        self:UpdateBar();
                    end
                    dropDown:SetParent(self.slotProperties);
                    if (info.condArgs.eqItemName == "nil") then
                        dropDown:SetText(anyItem);
                    else
                        local name = string.sub(info.condArgs.eqItemName, 2, -2);
                        dropDown:SetText(name);
                    end
                end
                if (info.condArgs.eqSlot) then
                    local prevContext = L:SetContext("/SequenceEditor/EquipmentSlots");
                    local eqSlotNames, localEqSlotNames, eqSlotValues = L:GetItems(), {}, {};
                    for slot in values(eqSlotNames) do
                        local text = L:GetText(slot);
                        table.insert(localEqSlotNames, text);
                        eqSlotValues[text] = tostring(Turbine.Gameplay.Equipment[slot]);
                    end
                    local anySlot = L:GetText("/SequenceEditor/AnyEqSlot");
                    eqSlotValues[anySlot] = "nil";
                    table.insert(localEqSlotNames, anySlot);
                    table.sort(localEqSlotNames);
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, localEqSlotNames);
                    dropDown.ItemChanged = function(sender, args)
                        info.condArgs.eqSlot = eqSlotValues[args.Text];
                        self:UpdateBar();
                    end                    
                    dropDown:SetParent(self.slotProperties);
                    if (info.condArgs.eqSlot == "nil") then
                        dropDown:SetText(anySlot);
                    else
                        local slotName = Search(Turbine.Gameplay.Equipment, tonumber(info.condArgs.eqSlot));
                        dropDown:SetText(L:GetText(slotName));
                    end
                    L:SetContext(prevContext);
                end
            end
        elseif (info.type == "Include") then
            top = top + 16; -- skip a line
            top = AddLabel(top, L:GetText("/SequenceEditor/OtherSequence") .. ":", Turbine.UI.Color.Goldenrod);
            local function GetObjectTree()
                -- Get a list of all bar IDs.
                local objectIDs = self.manager:FindAllDescendentIDs(nil, true, false);
                
                -- Including self doesn't make sense, so prevent that.
                local foundSelf;
                for o = 1, #objectIDs, 1 do
                    local object = self.manager:GetObject(objectIDs[o]);
                    if (object == self.bar) then
                        foundSelf = o;
                    end
                end
                if (foundSelf) then
                    table.remove(objectIDs, foundSelf);
                end
                
                -- Add nonempty groups.
                local groupIDs = {};
                for o = 1, #objectIDs, 1 do
                    local objectID = objectIDs[o];
                    local object = self.manager:GetObject(objectID);
                    while (object.parent) do
                        object = object.parent;
                        objectID = object:GetID();
                        if (objectID) then
                            groupIDs[objectID] = 1;
                        end
                    end
                end
                for groupID in keys(groupIDs) do
                    table.insert(objectIDs, groupID);
                end
                
                -- Create item list for PullDownMenu.
                local items = {};
                for o = 1, #objectIDs, 1 do
                    local objectID = objectIDs[o];
                    local object = self.manager:GetObject(objectID);
                    local parentID = nil;
                    if (object.parent) then
                        parentID = object.parent:GetID();
                    end
                    table.insert(items, { objectID, object:GetName(), parentID });
                end
                return items;
            end
            local pullDown;
            top, pullDown = AddPullDown(top, GetObjectTree(), info.include);
            pullDown.SelectionChanged = function(sender, id)
                info.include = id;
                self:UpdateBar();
                self:SelectSlot(s);
            end
            AddCallback(pullDown, "MouseClick", function(sender, args)
                -- Right-click displays the bar menu.
                if (args.Button == Turbine.UI.MouseButton.Right) then
                    if (info.include) then
                        local bar = self.manager.objects[info.include];
                        if (bar) then
                            bar:ShowSettingsMenu();
                        end
                    end
                end                
            end);

            -- Add the "unlink" button if a sequence is selected.
            if (info.include) then
                local button;
                local text, width = L:GetText("/SequenceEditor/Unlink"), L:GetNumber("/SequenceEditor/UnlinkButtonWidth");
                top, button = AddButton(top, width, text);
                AddCallback(button, "Click", function()
                    self:UnlinkIncludedSequence(s);
                end);
            end
        end

        -- Allow the user to specify whether activation is click-based or automatic.
        if (info.type ~= "Include") then
            top = top + 16; -- skip a line
            top = AddLabel(top, L:GetText("/SequenceEditor/Activation") .. ":", Turbine.UI.Color.Goldenrod);
            local button1, button2;
            top, button1 = AddRadioButton(top, L:GetText("/SequenceEditor/WithLeftClick"), Turbine.UI.Color.DarkGoldenrod, (not info.automatic));
            top, button2 = AddRadioButton(top, L:GetText("/SequenceEditor/Automatic"), Turbine.UI.Color.DarkGoldenrod, info.automatic);
            Thurallor.UI.RadioButton.LinkPeers({button1, button2});
            button1.Clicked = function()
                info.automatic = nil;
                self:UpdateBar();
            end
            button2.Clicked = function()
                info.automatic = true;
                self:UpdateBar();
            end
        end
    end

    -- Allow the user to modify slot appearance (text, icon)
    top = top + 16; -- skip a line
    local button;
    top, button = AddSection(top, L:GetText("/SequenceEditor/Appearance"), Turbine.UI.Color.Goldenrod, self.appearanceExpanded);
    function button.ExpandedChanged(sender, expanded)
        self.appearanceExpanded = expanded;
        self:DisplaySlot(self.selectedSlot);
    end

    if (self.appearanceExpanded) then

        -- Allow the user to optionally overlay some text
        top = top + 16; -- skip a line
        top = AddLabel(top, L:GetText("/SequenceEditor/TextOverlay") .. ":", Turbine.UI.Color.Goldenrod);
        local textBox;
        top, textBox = AddTextBox(top, info.textOverlay, Turbine.UI.Color.White);
        textBox:SetFont(Turbine.UI.Lotro.Font.Verdana12);
        textBox.TextChanged = function(sender, args)
            local text = sender:GetText();
            if (text == "") then
                text = nil;
            end
            info.textOverlay = text;
            UpdateIcon();
            self:UpdateBar();
        end
        
        -- Allow the user to optionally change the icon
        top = top + 16; -- skip a line
        top = AddLabel(top, L:GetText("/SequenceEditor/ChangeIcon") .. ":", Turbine.UI.Color.Goldenrod);
        local button1, button2, slider2, button3, slider3, button4, textbox;
        top, button1 = AddRadioButton(top, L:GetText("/SequenceEditor/DefaultIcon"), Turbine.UI.Color.DarkGoldenrod);
        top, button2 = AddRadioButton(top, L:GetText("/SequenceEditor/BlankIcon"), Turbine.UI.Color.DarkGoldenrod);
        top, slider2 = AddSlider(top, 1, #resources.BlankIcons, 1);
        slider2:SetLeft(12);
        slider2:SetWidth(slider2:GetWidth() - 12);
        top, button3 = AddRadioButton(top, L:GetText("/SequenceEditor/SkillIcon"), Turbine.UI.Color.DarkGoldenrod);
        local skillsInfo = Thurallor.Utils.Watcher.GetSkillsInfo();
        local gambitsInfo = Thurallor.Utils.Watcher.GetGambitsInfo();
        self.skillIcons = {};
        for id in sorted_keys(skillsInfo.byIcon) do
            table.insert(self.skillIcons, id);
        end
        for id in sorted_keys(gambitsInfo.byIcon) do
            table.insert(self.skillIcons, id);
        end
        top, slider3 = AddSlider(top, 1, #self.skillIcons, 1);
        slider3:SetLeft(12);
        slider3:SetWidth(slider3:GetWidth() - 12);
        top, button4 = AddRadioButton(top, L:GetText("/SequenceEditor/SpecificIconID"), Turbine.UI.Color.DarkGoldenrod);
        top, textBox = AddTextBox(top, "", Turbine.UI.Color.White);
        textBox:SetFont(Turbine.UI.Lotro.Font.Verdana10);
        textBox:SetLeft(12);
        textBox:SetWidth(textBox:GetWidth() - 12);
        if (info.altIcon == nil) then
            button1:SetChecked(true);
        else
            local blankIcon = Search(resources.BlankIcons, info.altIcon);
            if (blankIcon) then
                button2:SetChecked(true);
                slider2:SetValue(blankIcon);
            else
                local skillIcon = Search(self.skillIcons, info.altIcon);
                if (skillIcon) then
                    button3:SetChecked(true);
                    slider3:SetValue(skillIcon);
                else
                    button4:SetChecked(true);
                end
            end
        end
        local function UpdateIconID()
            local id = info.altIcon;
            if (id == nil) then
                id = info.background;
            end
            if (type(id) == "number") then
                textBox:SetText(string.format("0x%8.8X", id));
            elseif (type(id) == "string") then
                textBox:SetText(id);
            else
                -- Get icon ID from quickslot
                
            end
        end
        UpdateIconID();
        Thurallor.UI.RadioButton.LinkPeers({button1, button2, button3, button4});
        function button1.Clicked()
            info.altIcon = nil;
            UpdateIcon();
            UpdateIconID();
            self:UpdateBar();
        end
        function button2.Clicked()
            slider2:ValueChanged();
        end
        function button3.Clicked()
            slider3:ValueChanged();
        end
        function button4.Clicked()
            textBox:TextChanged();
        end
        slider2.ValueChanged = function(sender)
            if (not button2:IsChecked()) then
                button2:MouseClick();
                return;
            end
            info.altIcon = resources.BlankIcons[sender:GetValue()];
            UpdateIcon();
            UpdateIconID();
            self:UpdateBar();
            self.settings.sequenceEditor.defaultIcon = info.altIcon;
        end
        slider3.ValueChanged = function(sender)
            if (not button3:IsChecked()) then
                button3:MouseClick();
                return;
            end
            info.altIcon = self.skillIcons[sender:GetValue()];
            UpdateIcon();
            UpdateIconID();
            self:UpdateBar();
            self.settings.sequenceEditor.defaultIcon = info.altIcon;
        end
        function textBox.FocusGained()
            button4:MouseClick();
        end
        textBox.TextChanged = function(sender)
            if (not button4:IsChecked()) then
                return;
            end
            local id = sender:GetText();
            if (tonumber(id) ~= nil) then
                id = tonumber(id);
            end
            local valid = pcall(GetAssetSize, id);
            if (valid) then
                textBox:SetForeColor(Turbine.UI.Color.White);
                info.altIcon = id;
                UpdateIcon();
                self:UpdateBar();
            else
                textBox:SetForeColor(Turbine.UI.Color.Red);
            end
        end
    end
    
    self.slotProperties:SetHeight(top);
    self:SetMinimumHeight(top + 110);
end

function SequenceEditor:FindSlotAtPosition(x, y, drawCaret)
    local originX, originY = self.slotPanel:GetParent():PointToScreen(self.slotPanel:GetPosition());
    local relX, relY = x - originX, y - originY + 3;
    local posX, posY = 1 + math.floor(relX / self.slotSize + 0.5), 1 + math.floor(relY / self.slotSize);
    local s = nil;
    if ((posX >= 1) and (posX <= self.slotsWide + 1) and (posY >= 1) and (posY <= self.slotsHigh)) then
        s = ((posY - 1) * self.slotsWide) + posX;
    end
    if ((posX == self.slotsWide + 1) and (posY == self.slotsHigh)) then
        s = nil;
    end
    
    -- Update caret position.
    if ((not drawCaret) or (not s)) then
        -- Hide carets in all Sequence Editors
        for editor in keys(SequenceEditor.instances) do
            if (editor.caret) then
                editor.caret:SetParent(nil);
                editor.caret = nil;
            end
        end
    else
        if (not self.caret) then
            self.caret = Turbine.UI.Control();
            self.caret:SetParent(self.slotPanel);
            self.caret:SetBackground(resources.Icon.Caret);
            self.caret:SetBlendMode(Turbine.UI.BlendMode.AlphaBlend);
            self.caret:SetSize(3, 36)
        end
        self.caret:SetPosition((posX - 1) * self.slotSize, (posY - 1) * self.slotSize);
    end

    return s;
end

function SequenceEditor:FindForeignSlotAtPosition(x, y, drawCaret)

    -- Find the frontmost SequenceEditor under the mouse cursor
    local frontEditor = self;
    local maxZ = -2147483648;
    for editor in keys(SequenceEditor.instances) do
        if (editor ~= self) then
            local left, top = editor:GetPosition();
            local width, height = editor:GetSize();
            if ((x >= left) and (x <= left + width) and (y >= top) and (y <= top + height)) then
                if (editor:GetZOrder() >= maxZ) then
                    frontEditor = editor;
                    maxZ = editor:GetZOrder();
                end
            end
        end
    end

    return frontEditor, frontEditor:FindSlotAtPosition(x, y, drawCaret);
end

function SequenceEditor:BuildSlot(slot, parent, object, left, top)
--Puts("BuildSlot(" .. tostring(slot) .. ", " .. tostring(parent) .. ", " .. tostring(object) .. ", " .. tostring(left) .. ", " .. tostring(top));

    -- Get slot info, or initialize to default
    local info = self.settings.sequenceItemInfo[slot];
    if (object) then
        -- Reuse the existing object
        object:SetParent(parent);
        object:SetInfo(info);
        object:SetPosition(left, top);
        object:SetNumLabel(slot);
        return object;
    end

    -- Need to create a new object
    object = Slot(info);
    object:SetPosition(left, top);
    object:SetNumLabel(slot);
    object:SetParent(parent);
    object:SetActionEnabled(false);
    object:SetAllowDrop(true);
    object:SetDraggable(true);

    -- Desired mouse behaviors:
    --   Left-click selects the slot and shows its properties in the "Slot" tab.
    --   Right-click displays the settings menu.    
    --   Left-button-drag initiates a drag-and-drop (move) operation.

    object.ShortcutChanged = function(sender, shortcutType, shortcutData)
        local s = sender:GetNumLabel();
        self:ExtendSequenceTo(s);
        local info = sender:GetInfo();
        self.settings.sequenceItemInfo[s] = info;
        
        if (shortcutType == Turbine.UI.Lotro.ShortcutType.Alias) then
            info.textOverlay = string.gsub(info.Data, "^/", "");
            info.textOverlay = string.gsub(info.textOverlay, "%s.*$", "");
            info.background = self.settings.sequenceEditor.defaultIcon;
            sender:SetInfo(info);
        end

        self:UpdateBar();
        self:SelectSlot(s);
    end

    object.MouseClick = function(sender, args)
        -- Left-click selects the slot and shows its properties in the "Slot" tab.
        if (args.Button == Turbine.UI.MouseButton.Left) then
            self:SelectSlot(sender:GetNumLabel());

        -- Right-click displays the settings menu.
        elseif (args.Button == Turbine.UI.MouseButton.Right) then
            self.clickedItem = sender:GetNumLabel();
            self:ShowContextMenu();
        end
    end

    object.DropPositionValid = function(sender, left, top)
        local s = sender:GetNumLabel();

        -- Draw a "caret" showing where the slot will be inserted.
        local editor = self;
        local newSlot = self:FindSlotAtPosition(left, top, true);
        if (not newSlot) then
            editor, newSlot = self:FindForeignSlotAtPosition(left, top, true);
        end
        if ((editor == self) and ((newSlot == s) or (newSlot == s + 1))) then
            self:FindSlotAtPosition(0, 0, false); -- hide caret if returning to the same position
        end
        return newSlot; -- if newSlot is nil, a red "X" will be shown
    end
    
    object.DragDropComplete = function(sender, left, top)
        local s = sender:GetNumLabel();
    
        -- Select destination slot.  Hide the caret.
        local editor = self;
        local newSlot = self:FindSlotAtPosition(left, top, false);
        if (not newSlot) then
            editor, newSlot = self:FindForeignSlotAtPosition(left, top, false);
        end
        
        -- Do the move operation, and redraw.
        if (newSlot and (editor ~= self)) then
            editor:InsertSlot(newSlot, self.settings.sequenceItemInfo[s]);
            self:DeleteSlot(s);
        elseif (newSlot and (newSlot ~= s) and (newSlot ~= s + 1)) then
            self:MoveSlot(s, newSlot);
        else
            self:Redraw();
        end
    end    

    return object;
end

function SequenceEditor:RebuildSlot(s, x, y, alreadyDisplaying)
    local oldObject = self.sequenceItems[s];
    
    -- If x and y aren't specified, then presumably the object has already been positioned previously
    if (x == nil) then
        x = oldObject.x;
        y = oldObject.y;
    end
    local left, top = self:GetSlotPosition(x, y);

    local newObject = self:BuildSlot(s, self.slotPanel, oldObject, left, top);
    newObject.x = x;
    newObject.y = y;
    self.sequenceItems[s] = newObject;

    if (not alreadyDisplaying and (self.selectedSlot == s)) then
        self:DisplaySlot(s);
    end
end

function SequenceEditor:SelectSlot(s)
--Puts("SelectSlot(" .. tostring(s) .. "): self.selectedSlot = " .. tostring(self.selectedSlot));
    if (self.selectedSlot) then
        local object = self.sequenceItems[self.selectedSlot];
        object:SetSelected(false);
        local info = self.settings.sequenceItemInfo[self.selectedSlot];
        if (info and (info.class == "Turbine.UI.Lotro.Quickslot") and (not info.type)) then
            -- Delete empty shortcut
            info.class = nil;
        end
    end
    for o = 1, #self.relatedSlots, 1 do
        local otherSlot, otherObject = unpack(self.relatedSlots[o]);
        otherObject:SetSelected(nil);
        self.relatedSlots[o] = nil;
    end
    if (s ~= nil) then
        local info = self.settings.sequenceItemInfo[s];
        if ((not info) or (not info.class)) then
            s = nil;
        else
            local object = self.sequenceItems[s];
            object:SetSelected(true);
            local sequence = self.bar:GetSequence();
            local barObject = sequence:GetSlot(sequence:GetNewIndex(s));
            if (barObject) then
                for otherSlot, _ in pairs({ifSlot = 1; elseSlot = 1; endIfSlot = 1}) do
                    if (barObject[otherSlot]) then
                        local otherObject = self.sequenceItems[sequence:GetOldIndex(barObject[otherSlot])];
                        otherObject:SetSelected(true, Turbine.UI.Color.Yellow);
                        table.insert(self.relatedSlots, {otherSlot, otherObject});
                    end
                end
            end
        end
    end
    self.selectedSlot = s;
    self:DisplaySlot(s);
end

function SequenceEditor:GetSlotPosition(x, y)
    local left = (x - 1) * (self.settings.slotSize + self.settings.slotSpacing) - self.settings.slotSpacing;
    local top = (y - 1) * (self.settings.slotSize + self.settings.slotSpacing) - self.settings.slotSpacing;
    return left, top;
end

function SequenceEditor:Closing()
    SequenceEditor.instances[self] = nil;
    self:DeleteEmptyTrailingSlots();
    self:UpdateBar();
    if (self.ItemMovedCallback) then
        RemoveCallback(Thurallor.Utils.Watcher, "ItemMoved", self.ItemMovedCallback);
        self.ItemMovedCallback = nil;
    end
end

function SequenceEditor:Close()
    Turbine.UI.Lotro.Window.Close(self);
end

function SequenceEditor:ShowContextMenu()
    -- Build the settings menu.
    self.settingsMenu = Turbine.UI.ContextMenu();
    local prevContext = L:SetContext("/SequenceEditor/RightClickMenu");
    self:AddSettingsMenuItem(self.settingsMenu, "Root", false);
    L:SetContext(prevContext);
    self.settingsMenu:ShowMenu();
end

function SequenceEditor:AddSettingsMenuItem(parent, itemName)
    local item = Turbine.UI.MenuItem(L:GetText(itemName), true, false);
    parent:GetItems():Add(item);

    if (itemName == "Root") then
        parent:GetItems():Clear();
        self:AddSettingsMenuItem(parent, "CreateSpecialSlot");
        self:AddSettingsMenuItem(parent, "InsertEmptySlot");
        self:AddSettingsMenuItem(parent, "CloneSlot");
        self:AddSettingsMenuItem(parent, "DeleteSlot");
    elseif (itemName == "CloneSlot") then
        item.Click = function(sender, args)
            self:CloneSlot(self.clickedItem);
        end
    elseif (itemName == "InsertEmptySlot") then
        item.Click = function(sender, args)
            self:InsertEmptySlot(self.clickedItem);
        end
    elseif (itemName == ("DeleteSlot")) then
        item.Click = function(sender, args)
            self:DeleteSlot(self.clickedItem);
        end
    elseif (itemName == "CreateSpecialSlot") then
        local prevContext = L:SetContext("SpecialSlotMenu");
        local texts = L:GetSortedTexts();
        for i = 1, #texts, 1 do
            self:AddSettingsMenuItem(item, L:GetItem(texts[i]));
        end
        L:SetContext(prevContext);
    elseif (string.find("StopAnimating|GenerateEvent|SetUnequipDestination", itemName)) then
        item.Click = function(sender, args)
            self:CreateCommandSlot(self.clickedItem, itemName, item:GetText());
        end
    elseif (itemName == "ChatCommand") then
        local commands = Turbine.Shell.GetCommands();
        local index = math.random(#commands);
        item.Click = function(sender, args)
            self:CreateAliasSlot(self.clickedItem, "/" .. commands[index]);
        end
    elseif (itemName == "RemoveEquipment") then
        item.Click = function(sender, args)
            self:CreateRemovalSlot(self.clickedItem, "Gloves");
        end
    elseif ((itemName == "IfThen") or (itemName == "IfThenElse")) then
        item.Click = function(sender, args)
            self:CreateConditional(self.clickedItem, itemName);
        end
    elseif (itemName == "Include") then
        item.Click = function(sender, args)
            self:CreateIncludeSlot(self.clickedItem);
        end
    elseif (itemName == "Delay") then
        item.Click = function(sender, args)
            self:CreateDelaySlot(self.clickedItem);
        end
    end
end

function SequenceEditor:CreateAliasSlot(s, command)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Lotro.Quickslot";
    info.background = nil;
    info.type = Turbine.UI.Lotro.ShortcutType.Alias;
    info.Data = command;
    info.altIcon = self.settings.sequenceEditor.defaultIcon;
    if (info.background) then
        info.textOverlay = string.gsub(info.Data, "^/", "");
        info.textOverlay = string.gsub(info.textOverlay, "%s.*$", "");
    end
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateDelaySlot(s)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon.Clock;
    info.altIcon = nil;
    info.type = "Delay";
    info.toolTip = L:GetText("/SequenceEditor/RightClickMenu/SpecialSlotMenu/Delay");
    info.delay = 0.25;
    info.action = "local item, s = ...; item.bar:StartSlotCooldown(s, " .. tostring(info.delay) .. ");";
    info.advanceEvent = "DelayComplete";

    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateIncludeSlot(s)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon.Include;
    info.altIcon = nil;
    info.type = "Include";
    info.toolTip = L:GetText("/SequenceEditor/RightClickMenu/SpecialSlotMenu/Include");
    
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:UnlinkIncludedSequence(s)
    local info = self.settings.sequenceItemInfo[s];
    if (not info.include) then
        return;
    end
    local otherSequenceSettings = self.globals.bars[info.include];
    if (not otherSequenceSettings) then
        return;
    end
    local otherItemInfo = otherSequenceSettings.sequenceItemInfo;
    self:DeleteSlot(s);
    for i = 1, #otherItemInfo, 1 do
        if (otherItemInfo[i].class) then
            table.insert(self.settings.sequenceItemInfo, s + i - 1, {});
            DeepTableCopy(otherItemInfo[i], self.settings.sequenceItemInfo[s + i - 1]);
        end
    end
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateConditional(s, condType)
    local prevContext = L:SetContext("/SequenceEditor");
    local ifSlot = s;
    self:InsertEmptySlot(s);
    if (condType == "IfThenElse") then
        self:InsertEmptySlot(s);
    end
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon["If"];
    info.altIcon = nil;
    info.type = "If";
    info.condName = "Always";
    info.condExpr = "return true;";
    info.toolTip = L:GetText("If");
    info.automatic = true;
    if (condType == "IfThenElse") then
        s = s + 1;
        info = self.settings.sequenceItemInfo[s];
        info.class = "Turbine.UI.Control";
        info.background = resources.Icon["Else"];
        info.altIcon = nil;
        info.type = "Else";
        info.toolTip = L:GetText("Else");
        info.automatic = true;
    end
    s = s + 1;
    info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon["EndIf"];
    info.altIcon = nil;
    info.type = "EndIf";
    info.toolTip = L:GetText("EndIf");
    info.automatic = true;
    self:Redraw();
    self:UpdateBar();
    self:SelectSlot(ifSlot);
    L:SetContext(prevContext);
end

function SequenceEditor:CreateCommandSlot(s, slotName, toolTip)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon[slotName];
    info.altIcon = nil;
    if (slotName == "StopAnimating") then
        info.action = "local item = ...; item.bar.animationStopped = true;";
    elseif (slotName == "GenerateEvent") then
        info.eventName = L:GetText("/SequenceEditor/NewEvent");
        info.action = "local item = ...; item.bar.manager:PropagateEvent(item.info.eventName);";
    elseif (slotName == "SetUnequipDestination") then
        info.bagSlot = 1;
        info.action = "local item = ...; item.bar.manager:SetUnequipDestination(item.info.bagSlot);";
    end
    info.type = slotName;
    info.toolTip = toolTip;
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateRemovalSlot(s, eqSlotName, bagSlot)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon["Unequip" .. eqSlotName];
    info.altIcon = nil;
    info.action = "local item = ...; item.bar.manager:Unequip(Turbine.Gameplay.Equipment." .. eqSlotName .. ");";
    info.type = "Remove " .. eqSlotName;
    
    local prevContext = L:SetContext("/SequenceEditor");
    local toolTip = L:GetText("RemoveItem");
    L:SetContext("EquipmentSlots");
    local localEqSlotName = L:GetText(eqSlotName);
    info.toolTip = string.gsub(toolTip, "<item>", localEqSlotName);
    L:SetContext(prevContext);
    
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:ExtendSequenceTo(s)
    if (s <= #self.settings.sequenceItemInfo) then
        return false;
    end
    while (s > #self.settings.sequenceItemInfo) do
        table.insert(self.settings.sequenceItemInfo, {});
    end
    return true;
end

function SequenceEditor:DeleteEmptyTrailingSlots()
    local sequenceItemInfo = self.settings.sequenceItemInfo;
    for s = #sequenceItemInfo, 1, -1 do
        local info = sequenceItemInfo[s];
        if (not info.class) then
            table.remove(sequenceItemInfo, s);
            -- Remove empty slot displays
            if (self.sequenceItems) then
                local object = self.sequenceItems[s];
                if (object) then
                    if (object.numLabel) then
                        object.numLabel:SetParent(nil);
                    end
                    object:SetParent(nil);
                    self.sequenceItems[s] = nil;
                end
            end
        else
            break;
        end
    end
end

function SequenceEditor:CloneSlot(s)
    self:InsertEmptySlot(s);
    DeepTableCopy(self.settings.sequenceItemInfo[s + 1], self.settings.sequenceItemInfo[s]);
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:MoveSlot(sourceSlot, destSlot)
    self:InsertEmptySlot(destSlot);
    if (sourceSlot >= destSlot) then
        sourceSlot = sourceSlot + 1;
    end
    DeepTableCopy(self.settings.sequenceItemInfo[sourceSlot], self.settings.sequenceItemInfo[destSlot]);
    self:RebuildSlot(destSlot);
    self:DeleteSlot(sourceSlot);
    if (sourceSlot <= destSlot) then
        destSlot = destSlot - 1;
    end
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(destSlot);
end

function SequenceEditor:InsertSlot(s, info)
    if (s > #self.settings.sequenceItemInfo) then
        self:ExtendSequenceTo(s);
    else
        table.insert(self.settings.sequenceItemInfo, s, {});
    end
    if (info) then
        DeepTableCopy(info, self.settings.sequenceItemInfo[s]);
    end
    self:Redraw();
    self:UpdateBar();
    self:SelectSlot(s);
end

function SequenceEditor:InsertEmptySlot(s)
    self:InsertSlot(s, nil);
end

function SequenceEditor:DeleteSlot(s)
    table.remove(self.settings.sequenceItemInfo, s);
    self:Redraw();
    self:UpdateBar();
    self:ExtendSequenceTo(s);
    self:SelectSlot(nil);
end

Compare with Previous | Blame


All times are GMT -5. The time now is 06:17 PM.


Our Network
EQInterface | EQ2Interface | Minion | WoWInterface | ESOUI | LoTROInterface | MMOUI | Swtorui