Code:
import "Turbine.Gameplay"
import "Turbine.UI.Lotro"
-- configuration
WindowLeft = Turbine.UI.Display:GetWidth() - 410
WindowTop = 1
WindowWidth = 170
Font1 = Turbine.UI.Lotro.Font.Verdana16
Font1Height = 16
Font2 = Turbine.UI.Lotro.Font.Verdana12
Font2Height = 12
debug = false
verbose = true
HopePriority = {1, 1.01, 1.02, 1.03, 1.04, 1.05} -- high to low priority for picking between ambiguous results
MotivatedPriority = {1.05, 1.1, 1.06, 1.065, 1.07, 1.075, 1.08, 1.085, 1.09, 1.095} -- captain legendary weapon legacy
InspiredPriority = {4.5} -- what are the other possible values?
-- window appearance
DreadWindow = Turbine.UI.Window()
DreadWindow:SetPosition(WindowLeft, WindowTop)
DreadWindow:SetSize(WindowWidth, Font1Height + 5 * Font2Height)
DreadWindow:SetMouseVisible(false)
DreadWindow:SetVisible(true)
DreadLabel = Turbine.UI.Label()
DreadLabel:SetParent(DreadWindow)
DreadLabel:SetSize(WindowWidth, Font1Height)
DreadLabel:SetFont(Font1)
DreadLabel:SetBackColor(Turbine.UI.Color(0.1, 0, 0, 0))
DreadLabel:SetMouseVisible(false)
DreadEffectLabel = Turbine.UI.Label()
DreadEffectLabel:SetParent(DreadWindow)
DreadEffectLabel:SetPosition(0, 16)
DreadEffectLabel:SetSize(WindowWidth, 5 * Font2Height)
DreadEffectLabel:SetFont(Font2)
DreadEffectLabel:SetBackColor(Turbine.UI.Color(0.1, 0, 0, 0))
DreadEffectLabel:SetMouseVisible(false)
-- non-configuration
MyChar = Turbine.Gameplay.LocalPlayer:GetInstance()
MyAttributes = MyChar:GetAttributes()
MyClass = MyChar:GetClass()
MyEffects = MyChar:GetEffects()
DreadMorale = {}
DreadMorale[1] = 0
DreadMorale[0.95] = 1
DreadMorale[0.9] = 2
DreadMorale[0.85] = 3
DreadMorale[0.8] = 4
DreadMorale[0.7] = 5
DreadMorale[0.6] = 6
DreadMorale[0.5] = 7
DreadMorale[0.4] = 8
DreadMorale[0.35] = 9
DreadMorale[0.2] = 10
DreadMorale[0.15] = 11
DreadMorale[0.1] = 12
DreadMorale[0.05] = 13
DreadMorale[0.03] = 14
DreadMorale[0.01] = 15
DreadEffects = {}
DreadEffects[0] = ""
DreadEffects[1] = "95% Maximum Morale\n99% Healing Received\n101% Damage Received\n99% Damage Dealt"
DreadEffects[2] = "90% Maximum Morale\n98% Healing Received\n102% Damage Received\n95% Damage Dealt"
DreadEffects[3] = "85% Maximum Morale\n97% Healing Received\n103% Damage Received\n90% Damage Dealt\n-1 Skill Effect Level"
DreadEffects[4] = "80% Maximum Morale\n96% Healing Received\n104% Damage Received\n85% Damage Dealt\n-2 Skill Effect Level"
DreadEffects[5] = "70% Maximum Morale\n92% Healing Received\n106% Damage Received\n80% Damage Dealt\n-3 Skill Effect Level"
DreadEffects[6] = "60% Maximum Morale\n90% Healing Received\n108% Damage Received\n75% Damage Dealt\n-4 Skill Effect Level"
DreadEffects[7] = "50% Maximum Morale\n85% Healing Received\n110% Damage Received\n70% Damage Dealt\n-5 Skill Effect Level"
DreadEffects[8] = "40% Maximum Morale\n75% Healing Received\n115% Damage Received\n65% Damage Dealt\n-6 Skill Effect Level"
DreadEffects[9] = "35% Maximum Morale\n65% Healing Received\n120% Damage Received\n60% Damage Dealt\n-7 Skill Effect Level"
DreadEffects[10] = "20% Maximum Morale\n55% Healing Received\n125% Damage Received\n55% Damage Dealt\n-8 Skill Effect Level"
DreadEffects[11] = "15% Maximum Morale\n45% Healing Received\n135% Damage Received\n50% Damage Dealt\n-9 Skill Effect Level"
DreadEffects[12] = "10% Maximum Morale\n35% Healing Received\n145% Damage Received\n45% Damage Dealt\n-10 Skill Effect Level"
DreadEffects[13] = "5% Maximum Morale\n25% Healing Received\n155% Damage Received\n40% Damage Dealt\n-11 Skill Effect Level"
DreadEffects[14] = "3% Maximum Morale\n15% Healing Received\n175% Damage Received\n35% Damage Dealt\n-12 Skill Effect Level"
DreadEffects[15] = "1% Maximum Morale\n5% Healing Received\n200% Damage Received\n30% Damage Dealt\n-13 Skill Effect Level"
DreadEffects["?"] = "dread calculation error"
HopeMorale = {}
HopeMorale[1] = 0
HopeMorale[1.01] = 1
HopeMorale[1.02] = 2
HopeMorale[1.03] = 3
HopeMorale[1.04] = 4
HopeMorale[1.05] = "5+"
HopeEffects = {}
HopeEffects[0] = ""
HopeEffects[1] = "+1% Maximum Morale\n101% Damage Dealt"
HopeEffects[2] = "+2% Maximum Morale\n102% Damage Dealt"
HopeEffects[3] = "+3% Maximum Morale\n103% Damage Dealt"
HopeEffects[4] = "+4% Maximum Morale\n103% Damage Dealt"
HopeEffects["5+"] = "+5% Maximum Morale\n103% Damage Dealt"
HopeEffects["?"] = "hope calculation error"
-- raw morale by level for character with 0 vitality
-- calculated from Lorebook info, not trustworthy
-- The Lorebook doesn't give raw morale. It cooks in the vitality, and assumes 3 morale per vitality even for Guardians and Wardens.
-- Lore-masters, Minstrels, Rune-keepers
RawSquishy = {75, 97, 119, 141, 163, 185, 207, 229, 251, 273, 295, 317, 339, 361, 383, 405, 427, 449, 471, 493,
508, 523, 538, 553, 568, 583, 598, 613, 628, 643, 658, 673, 688, 703, 718, 733, 748, 763, 778, 793,
808, 823, 838, 853, 868, 883, 898, 913, 928, 943, 958, 973, 988, 1003, 1018, 1033, 1048, 1063, 1078,
1093, 1108, 1123, 1138, 1153, 1168, 1186, 1208, 1234, 1264, 1297, 1334, 1375, 1420, 1465, 1510}
-- Burglars, Hunters
RawMedium = {75, 104, 133, 162, 191, 220, 249, 278, 307, 336, 365, 394, 423, 452, 481, 510, 539, 568, 597, 626, 648,
670, 692, 714, 736, 758, 780, 802, 824, 846, 868, 890, 912, 934, 956, 978, 1000, 1022, 1044, 1066, 1088,
1110, 1132, 1154, 1176, 1198, 1220, 1242, 1264, 1286, 1308, 1330, 1352, 1374, 1396, 1418, 1440, 1462,
1484, 1506, 1528, 1550, 1572, 1594, 1616, 1643, 1676, 1714, 1758, 1807, 1862, 1922, 1988, 2054, 2120}
-- Captains
RawCap = {85, 119, 153, 187, 221, 255, 289, 323, 357, 391, 410, 444, 478, 512, 546, 580, 614, 648, 682, 716, 738,
760, 782, 804, 826, 848, 870, 892, 914, 936, 958, 980, 1002, 1024, 1046, 1068, 1090, 1112, 1134, 1156, 1178,
1200, 1222, 1244, 1266, 1288, 1310, 1332, 1354, 1376, 1398, 1420, 1442, 1464, 1486, 1508, 1530, 1552, 1574,
1596, 1618, 1640, 1662, 1684, 1706, 1733, 1766, 1804, 1848, 1897, 1952, 2012, 2078, 2144, 2210}
-- Champions
RawChamp = {100, 139, 178, 217, 256, 295, 334, 373, 412, 451, 475, 514, 553, 592, 631, 670, 709, 748, 787, 826,
853, 880, 907, 934, 961, 988, 1015, 1042, 1069, 1096, 1123, 1150, 1177, 1204, 1231, 1258, 1285, 1312, 1339,
1366, 1393, 1420, 1447, 1474, 1501, 1528, 1555, 1582, 1609, 1636, 1651, 1678, 1705, 1732, 1759, 1786, 1813,
1840, 1867, 1894, 1921, 1948, 1975, 2002, 2029, 2062, 2102, 2149, 2203, 2263, 2330, 2404, 2485, 2566, 2647}
-- Guardians, Wardens
RawTank = {100, 139, 178, 217, 256, 295, 334, 373, 412, 451, 490, 529, 568, 607, 646, 685, 724, 763, 802, 841, 868,
895, 922, 949, 976, 1003, 1030, 1057, 1084, 1111, 1138, 1165, 1192, 1219, 1246, 1273, 1300, 1327, 1354,
1381, 1408, 1435, 1462, 1489, 1516, 1543, 1570, 1597, 1624, 1651, 1678, 1705, 1732, 1759, 1786, 1813, 1840,
1867, 1894, 1921, 1948, 1975, 2002, 2029, 2056, 2089, 2129, 2176, 2230, 2290, 2357, 2431, 2512, 2593, 2674}
-- event handling boilerplate
function AddCallback(object, event, callback)
if (object[event] == nil) then
object[event] = callback
else
if (type(object[event]) == "table") then
table.insert(object[event], callback)
else
object[event] = {object[event], callback}
end
end
return callback
end
function RemoveCallback(object, event, callback)
if (object[event] == callback) then
object[event] = nil
else
if (type(object[event]) == "table") then
local size = table.getn(object[event])
for i = 1, size do
if (object[event][i] == callback) then
table.remove(object[event], i)
break
end
end
end
end
end
-- rounding function
function round(number, zeroes)
zeroes = zeroes or 0
return math.floor(number * (10 ^ zeroes) + .5) / (10 ^ zeroes)
end
-- calculate minimum possible maxmorale
-- used as a sanity check for HopeGuess
-- I'm not sure this is worth the effort given that it's unaware of raw morale bonuses from virtues, equipment and various buffs
function MinimumMorale(bonus)
mult = 3
raw = 0
if MyClass == Turbine.Gameplay.Class.LoreMaster
or MyClass == Turbine.Gameplay.Class.Minstrel
or MyClass == Turbine.Gameplay.Class.RuneKeeper then
raw = RawSquishy[MyChar:GetLevel()]
elseif MyClass == Turbine.Gameplay.Class.Burglar
or MyClass == Turbine.Gameplay.Class.Hunter then
raw = RawMedium[MyChar:GetLevel()]
elseif MyClass == Turbine.Gameplay.Class.Captain then
raw = RawCap[MyChar:GetLevel()]
elseif MyClass == Turbine.Gameplay.Class.Champion then
raw = RawChamp[MyChar:GetLevel()]
elseif MyClass == Turbine.Gameplay.Class.Guardian
or MyClass == Turbine.Gameplay.Class.Warden then
raw = RawTank[MyChar:GetLevel()]
mult = 5
end
minimum = raw + bonus + mult * MyAttributes:GetVitality()
if debug then
Turbine.Shell.WriteLine(string.format("%s %+d +(%s * %s) = %s minimum", raw, bonus, mult, MyAttributes:GetVitality(), minimum))
end
return minimum
end
-- guess hope level
-- often works, surprisingly
-- if MaxMorale at Hope: 0 is a multiple of 20 or 25, the result is always ambiguous (8% of numbers)
-- if MaxMorale at Hope: 0 is a multiple of 34, 35, 51, 52, 101, 103, the result is sometimes ambiguous (an additional ~8.3% of numbers)
function HopeGuess(max)
CurrentHope = "?"
ambiguous = false
InspiredSet = {1}
MotivatedSet = {1}
BonusMorale = 0
if MyChar:GetRace() == Turbine.Gameplay.Race.Elf then
BonusMorale = -20
end
for i = 1, MyEffects:GetCount() do
effect = MyEffects:Get(i)
effectName = effect:GetName()
if effectName == "Inspired Greatness" then
InspiredSet = InspiredPriority
elseif effectName == "Motivated" then
MotivatedSet = MotivatedPriority
elseif effectName == "Enhanced Abilities" then
BonusMorale = BonusMorale + 50
end
end
minimum = MinimumMorale(BonusMorale)
for i = 1, #InspiredSet do
for j = 1, #MotivatedSet do
for k = 1, #HopePriority do
ratio = max / (InspiredSet[i] * MotivatedSet[j] * HopePriority[k])
rounded = round(ratio, 3)
if rounded == math.floor(rounded) and rounded >= minimum then
if CurrentHope == "?" then
CurrentHope = HopeMorale[HopePriority[k]]
else
ambiguous = true
end
if debug then
Turbine.Shell.WriteLine(string.format("?Hope: %s (%s * %s * %s * %s = %s)", HopeMorale[HopePriority[k]], rounded, InspiredSet[i], MotivatedSet[j], HopePriority[k], max))
end
end
end
end
end
if ambiguous then
DreadLabel:SetText("Hope: "..CurrentHope.." ?")
DreadEffectLabel:SetText("ambiguous\n"..HopeEffects[CurrentHope])
else
DreadLabel:SetText("Hope: "..CurrentHope)
DreadEffectLabel:SetText(HopeEffects[CurrentHope])
end
end
-- calculate dread level and update display
-- calls HopeGuess when dread = 0
function UpdateDread(sender, args)
max = MyChar:GetMaxMorale()
base = MyChar:GetBaseMaxMorale()
ratio = max / base
current = DreadMorale[round(ratio, 3)] or "?"
if debug then
date = Turbine.Engine.GetDate()
output = string.format("%02d:%02d:%02d Dread: %s (%s / %s = %s)", date.Hour, date.Minute, date.Second, current, max, base, ratio)
Turbine.Shell.WriteLine(output)
end
if current ~= 0 then
DreadLabel:SetForeColor(Turbine.UI.Color(1, 0, 0))
DreadLabel:SetText("Dread: "..current)
DreadEffectLabel:SetText(DreadEffects[current])
else
DreadLabel:SetForeColor(Turbine.UI.Color(0, 1, 0))
HopeGuess(base)
end
end
AddCallback(MyChar, "BaseMaxMoraleChanged", UpdateDread)
-- command-line processing
CommandLine = Turbine.ShellCommand()
CommandLine.Execute = function(sender, cmd, args)
if args == "debug" then
if debug then
debug = false
Turbine.Shell.WriteLine("DreadMeter debugging off.")
else
debug = true
Turbine.Shell.WriteLine("DreadMeter debugging on.")
end
elseif args == "verbose" then
if DreadEffectLabel:IsVisible() then
DreadEffectLabel:SetVisible(false)
Turbine.Shell.WriteLine("DreadMeter verbose mode off.")
else
DreadEffectLabel:SetVisible(true)
Turbine.Shell.WriteLine("DreadMeter verbose mode on.")
end
else
Turbine.Shell.WriteLine("Toggle debugging: /dreadmeter debug\nToggle verbosity: /dreadmeter verbose")
end
UpdateDread()
end
Turbine.Shell.AddCommand("DreadMeter", CommandLine)
-- create a dummy window, and update until loaded, at which point set loaded=true and set the unload commands.
local loaded = false
tmpWindow = Turbine.UI.Window()
tmpWindow.Update = function()
if Plugins["DreadMeter"] ~= nil and not loaded then
loaded = true
Plugins["DreadMeter"].Unload = function(self, sender, args)
RemoveCallback(MyChar, "BaseMaxMoraleChanged", UpdateDread)
Turbine.Shell.RemoveCommand(CommandLine)
end
tmpWindow:SetWantsUpdates(false)
Turbine.Shell.WriteLine("DreadMeter plugin loaded. Hello, "..MyChar:GetName().."!")
UpdateDread()
end
end
tmpWindow:SetWantsUpdates(true)