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