lotrointerface.com
Search Downloads

LoTROInterface SVN SequenceBars

[/] [trunk/] [Thurallor/] [Common/] [Utils/] [Watcher.lua] - Rev 163

Go to most recent revision | Compare with Previous | Blame | View Log

-- Singleton object for efficiently observing and keeping track of game state,
-- providing convenient functions and events.

local Watcher = {};

AddCallback(Turbine.Plugin, "Unload", function()
--Puts("Saving...");
    -- Workaround for Turbine localization bug.
    local saveData = ExportTable(Watcher.settings);
    Turbine.PluginData.Save(Turbine.DataScope.Account, "Thurallor_GameInfo", saveData, function()
--Puts("Save complete.");
    end);
end);

local startupTime = Turbine.Engine.GetGameTime();

local function DoDelayedCallback(_time, event, args)
    if (not Watcher.delayedCallbacks) then
        Watcher.delayedCallbacks = {};
    end
    table.insert(Watcher.delayedCallbacks, { time = _time, event = event, args = args } );
    Watcher.window:SetWantsUpdates(true);
end

local function AddActorEffect(effectsObject, effect)
    local name = effect:GetName();
    if (not effectsObject.cache[name]) then
        effectsObject.cache[name] = 0;
    end
    effectsObject.cache[name] = effectsObject.cache[name] + 1;

    local knownEffects = Watcher.settings.knownEffects;
    local categories, curable;
    if (not knownEffects[name]) then
        -- This is an effect we have never seen before.  Learn some info about it and save for future reference.
        categories, curable = effect:GetCategory(), effect:IsCurable();
        knownEffects[name] = { categories, effect:GetIcon(), curable, effect:IsDebuff()};

        local bits = categories;
        if (not Watcher.AddCategories[bits]) then
            -- This is a GetCategory value we have never seen before.  Create functions for adding/removing.
            local AddCategories = "local categories = ...; ";
            local RemoveCategories = "local categories = ...; ";
            if (bits >= 2048) then
                -- Unknown bits; discard them.
                bits = bits % 2048;
            end
            if (bits >= 1226) then
                -- Turbine.Gameplay.EffectCategory.Dispellable is the OR of several bits
                AddCategories = AddCategories .. "categories[1226] = categories[1226] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[1226] = categories[1226] - 1; ";
            end
            if (bits >= 1024) then
                AddCategories = AddCategories .. "categories[1024] = categories[1024] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[1024] = categories[1024] - 1; ";
                bits = bits - 1024;
            end
            if (bits >= 512) then
                AddCategories = AddCategories .. "categories[512] = categories[512] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[512] = categories[512] - 1; ";
                bits = bits - 512;
            end
            if (bits >= 256) then
                AddCategories = AddCategories .. "categories[256] = categories[256] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[256] = categories[256] - 1; ";
                bits = bits - 256;
            end
            if (bits >= 128) then
                AddCategories = AddCategories .. "categories[128] = categories[128] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[128] = categories[128] - 1; ";
                bits = bits - 128;
            end
            if (bits >= 64) then
                AddCategories = AddCategories .. "categories[64] = categories[64] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[64] = categories[64] - 1; ";
                bits = bits - 64;
            end
            if (bits >= 32) then
                AddCategories = AddCategories .. "categories[32] = categories[32] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[32] = categories[32] - 1; ";
                bits = bits - 32;
            end
            if (bits >= 16) then
                AddCategories = AddCategories .. "categories[16] = categories[16] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[16] = categories[16] - 1; ";
                bits = bits - 16;
            end
            if (bits >= 8) then
                AddCategories = AddCategories .. "categories[8] = categories[8] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[8] = categories[8] - 1; ";
                bits = bits - 8;
            end
            if (bits >= 4) then
                AddCategories = AddCategories .. "categories[4] = categories[4] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[4] = categories[4] - 1; ";
                bits = bits - 4;
            end
            if (bits >= 2) then
                AddCategories = AddCategories .. "categories[2] = categories[2] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[2] = categories[2] - 1; ";
                bits = bits - 2;
            end
            if (bits >= 1) then
                AddCategories = AddCategories .. "categories[1] = categories[1] + 1; ";
                RemoveCategories = RemoveCategories .. "categories[1] = categories[1] - 1; ";
            end
            
            Watcher.settings.funcs.AddCategories[categories] = AddCategories;
            Watcher.AddCategories[categories] = loadstring(AddCategories);
            Watcher.settings.funcs.RemoveCategories[categories] = RemoveCategories;
            Watcher.RemoveCategories[categories] = loadstring(RemoveCategories);
        end
    else
        categories, curable = knownEffects[name][1], knownEffects[name][3];
    end

    Watcher.AddCategories[categories](effectsObject.activeCategories);
    if (curable) then
        Watcher.AddCategories[categories](effectsObject.activeCurableCategories);
    end
end

local function RemoveActorEffect(effectsObject, effect)
    local categories, name, curable = effect:GetCategory(), effect:GetName(), effect:IsCurable();
    Watcher.RemoveCategories[categories](effectsObject.activeCategories);
    if (curable) then
        Watcher.RemoveCategories[categories](effectsObject.activeCurableCategories);
    end
    effectsObject.cache[name] = effectsObject.cache[name] - 1;
    if (effectsObject.cache[name] == 0) then
        effectsObject.cache[name] = nil;
    end
end

local function GetActorEffects(effectsObject)
    effectsObject.cache = {};
    effectsObject.activeCategories = {
        [Turbine.Gameplay.EffectCategory.Dispellable] = 0;
        [Turbine.Gameplay.EffectCategory.Corruption] = 0;
        [Turbine.Gameplay.EffectCategory.Elemental] = 0;
        [Turbine.Gameplay.EffectCategory.Tactical] = 0;
        [Turbine.Gameplay.EffectCategory.Poison] = 0;
        [Turbine.Gameplay.EffectCategory.Fear] = 0;
        [Turbine.Gameplay.EffectCategory.Song] = 0;
        [Turbine.Gameplay.EffectCategory.Cry] = 0;
        [Turbine.Gameplay.EffectCategory.Wound] = 0;
        [Turbine.Gameplay.EffectCategory.Physical] = 0;
        [Turbine.Gameplay.EffectCategory.Disease] = 0;
        [1] = 0;
    };
    effectsObject.activeCurableCategories = {
        [Turbine.Gameplay.EffectCategory.Dispellable] = 0;
        [Turbine.Gameplay.EffectCategory.Corruption] = 0;
        [Turbine.Gameplay.EffectCategory.Elemental] = 0;
        [Turbine.Gameplay.EffectCategory.Tactical] = 0;
        [Turbine.Gameplay.EffectCategory.Poison] = 0;
        [Turbine.Gameplay.EffectCategory.Fear] = 0;
        [Turbine.Gameplay.EffectCategory.Song] = 0;
        [Turbine.Gameplay.EffectCategory.Cry] = 0;
        [Turbine.Gameplay.EffectCategory.Wound] = 0;
        [Turbine.Gameplay.EffectCategory.Physical] = 0;
        [Turbine.Gameplay.EffectCategory.Disease] = 0;
        [1] = 0;
    };
    for e = 1, effectsObject:GetCount(), 1 do
        local effect = effectsObject:Get(e);
        AddActorEffect(effectsObject, effect);
    end
end

local function GetEquipment()
    local eq = Watcher.playerEquipmentObject;
    eq.cache = { byName = {}; bySlot = {}; };
    for slot = 1, eq:GetSize() do
        local item = eq:GetItem(slot);
        if (item) then
            local name = item:GetName();
            eq.cache.byName[name] = { item, slot };
            eq.cache.bySlot[slot] = { item, name };
        end
    end
end

local function GetBackpack()
    local bp = Watcher.playerBackpackObject;
    bp.cache = { byName = {}; bySlot = {}; };
    for slot = 1, bp:GetSize() do
        local item = bp:GetItem(slot);
        if (item) then
            local name = item:GetName();
            if (not bp.cache.byName[name]) then
                bp.cache.byName[name] = { slot };
            else
                table.insert(bp.cache.byName[name], slot);
            end
            bp.cache.bySlot[slot] = { item, name };
        end
    end
    return bp.cache;
end

local function ResetTimeChanged(skill, args)
    DoCallbacks(self, "ResetTimeChanged", args);
end

local function StanceChanged()
    local stance = Watcher.playerClassAttributes:GetStance();
        if (stance ~= Watcher.stance) then
        -- Lua API bug workaround:  If invalid stance reported, keep the previous value.
        local validStance = {
            [Turbine.Gameplay.Class.Warden] = { 
                [Turbine.Gameplay.Attributes.WardenStance.Assailment] = true;
                [Turbine.Gameplay.Attributes.WardenStance.Determination] = true;
            };
            [Turbine.Gameplay.Class.Hunter] = { 
                [Turbine.Gameplay.Attributes.HunterStance.Endurance] = true;
                [Turbine.Gameplay.Attributes.HunterStance.Precision] = true;
                [Turbine.Gameplay.Attributes.HunterStance.Strength] = true;
            };
            [Turbine.Gameplay.Class.Minstrel] = { 
                [Turbine.Gameplay.Attributes.MinstrelStance.Melody] = true;
                [Turbine.Gameplay.Attributes.MinstrelStance.Dissonance] = true;
                [Turbine.Gameplay.Attributes.MinstrelStance.Resonance] = true;
            };
        };
        if (validStance[Watcher.playerObject:GetClass()][stance]) then
            Watcher.stance = stance;
            DoCallbacks(Watcher, "PlayerStanceChanged", { Stance = stance });
        end
    end
end

local function GetSkills(skills)
    skills.cache = { names = {}; byName = {}; byIcon = {} };
    local cache = skills.cache;
    for c = 1, skills:GetCount(), 1 do
        local skill = skills:GetItem(c);
        local skillInfo = skill:GetSkillInfo();
        local icon = skillInfo:GetIconImageID();
        local name = skillInfo:GetName();
        cache.byName[name] = skill;
        cache.byIcon[icon] = skill;
        table.insert(cache.names, name);
    end
    table.sort(cache.names);
    return skills.cache;
end

local function GetGambits()
    local gambits = Watcher.playerTrainedGambits;
    gambits.cache = { names = {}; byName = {}; byIcon = {} };
    if (Watcher.playerObject:GetClass() == Turbine.Gameplay.Class.Warden) then
        for c = 1, gambits:GetCount(), 1 do
            local skill = gambits:GetItem(c);
            local skillInfo = skill:GetSkillInfo();
            local icon = skillInfo:GetIconImageID();
            local name = skillInfo:GetName();
            gambits.cache.byName[name] = skill;
            gambits.cache.byIcon[icon] = skill;
        end
    end
    return gambits.cache;
end

local function GetParty()
    local party = Watcher.playerPartyObject;
    party.cache = { byName = {}; byNameUpper = {}; };
    for c = 1, party:GetMemberCount(), 1 do
        local member = party:GetMember(c);
        local name = member:GetName();
        party.cache.byName[name] = member;
        party.cache.byNameUpper[string.upper(name)] = member;
        member.num = c;
    end
    return party.cache;
end

local function PlayerEffectAdded(effectsObject, args)
    local effect = effectsObject:Get(args.Index);
    AddActorEffect(effectsObject, effect);
    DoCallbacks(Watcher, "PlayerEffectAdded", effect);
end

local function PlayerEffectRemoved(effectsObject, args)
    RemoveActorEffect(effectsObject, args.Effect);
    DoCallbacks(Watcher, "PlayerEffectRemoved", args.Effect);
end

local function PlayerEffectsCleared(effectsObject, args)
    GetActorEffects(effectsObject);
    DoCallbacks(Watcher, "PlayerEffectsCleared", args);
end

local function TargetEffectAdded(effectsObject, args)
    local effect = effectsObject:Get(args.Index);
    AddActorEffect(effectsObject, effect);
    DoCallbacks(Watcher, "TargetEffectAdded", effect);
end

function TargetEffectRemoved(effectsObject, args)
    RemoveActorEffect(effectsObject, args.Effect);
    DoCallbacks(Watcher, "TargetEffectRemoved", args.Effect);
end

local function TargetEffectsCleared(effectsObject, args)
    GetActorEffects(effectsObject);
    DoCallbacks(Watcher, "TargetEffectsCleared", args);
end

local function PartyMemberAdded(sender, args)
    -- Event does not work if you're in a raid (only a single fellowship)
    -- Event doesn't fire when the party is created (initial two members)
    -- rare occurrence, out-of-combat; efficiency not important
    GetParty();
end

local function PartyMemberRemoved(sender, args)
    -- Event does not work if you're in a raid (only a single fellowship)
    -- Event doesn't fire when the party is disbanded
    -- rare occurrence, out-of-combat; efficiency not important
    GetParty();
end

local function PartyAssistTargetAdded(sender, args)
    -- Event does not work if you're in a raid (only a single fellowship)
    -- rare occurrence, out-of-combat; efficiency not important
    GetParty();
end

local function PartyAssistTargetRemoved(sender, args)
    -- Event does not work if you're in a raid (only a single fellowship)
    -- rare occurrence, out-of-combat; efficiency not important
    GetParty();
end

local function PlayerPartyChanged(sender, args)
    Watcher.playerPartyObject = Watcher.playerObject:GetParty();
    if (Watcher.playerPartyObject) then
        AddCallback(Watcher.playerPartyObject, "MemberAdded", PartyMemberAdded);
        AddCallback(Watcher.playerPartyObject, "MemberRemoved", PartyMemberRemoved);
        AddCallback(Watcher.playerPartyObject, "AssistTargetAdded", PartyAssistTargetAdded);
        AddCallback(Watcher.playerPartyObject, "AssistTargetRemoved", PartyAssistTargetRemoved);
        GetParty();
    end
end

local function UpdatePlayerTarget()
    Watcher.targetIsPartyMember = nil;
    local target = Watcher.targetObject;

    if (target and target.GetBaseMaxMorale) then
        local targetName, targetMorale = target:GetName(), target:GetBaseMaxMorale();
        local pet = Watcher.playerObject:GetPet();
        local party = Watcher.playerPartyObject;

        -- Detect if the new target is the player's self.  If so, use the full LocalPlayer object
        -- instead of the Actor object returned by GetTarget().
        if (target:IsLocalPlayer()) then
            Watcher.targetObject = Watcher.playerObject;

        -- Detect if the new target is the player's pet.  If so, use the full Pet object instead of the
        -- Actor object returned by GetTarget().
        elseif (pet and (targetName == pet:GetName()) and (targetMorale == pet:GetBaseMaxMorale())) then
            Watcher.targetObject = pet;

        -- Detect if the new target is a member of the player's party.  If so, use the full Player object
        -- instead of the Actor object returned by GetTarget().
        elseif (party) then
            local member = party.cache.byName[targetName];
            if (member and (targetMorale == member:GetBaseMaxMorale())) then
                Watcher.targetIsPartyMember = member;
                Watcher.targetObject = member;
                -- Also save this for Watcher.GetNextPartyMember():
                Watcher.prevPartyMember = member;
            end
        end

    end

    -- Need to get the effects object now so it can start observing effects.
-- Target effects currently are too buggy to use.    
--[[
    if (Watcher.targetObject and Watcher.targetObject.GetEffects) then
        Watcher.targetEffectsObject = Watcher.targetObject:GetEffects();
    end
]]

    DoCallbacks(Watcher, "PlayerTargetChanged", Watcher.targetObject);
end

local function TargetsTargetChanged()
    Watcher.targetsTargetObject = nil;
    DoCallbacks(Watcher, "TargetsTargetChanged", Watcher.targetObject);
end

local function PlayerTargetChanged()
    if (Watcher.targetObject) then
        RemoveCallback(Watcher.targetObject, "TargetChanged", TargetsTargetChanged);
    end
    Watcher.targetObject = Watcher.playerObject:GetTarget();
    Watcher.targetEffectsObject = nil;
    Watcher.targetsTargetObject = nil;
    if (Watcher.targetObject) then
        AddCallback(Watcher.targetObject, "TargetChanged", TargetsTargetChanged);
    end

    -- Client may not immediately have information about the target.  When
    -- the info arrives, we'll get a BaseMaxPowerChanged event, which seems
    -- to occur near the end of the burst of events that will arrive.
    if (Watcher.targetObject and Watcher.targetObject.GetBaseMaxPower and (Watcher.targetObject:GetBaseMaxPower() == 0)) then
        Watcher.targetObject.BaseMaxPowerChanged = function()
            Watcher.targetObject.BaseMaxPowerChanged = nil;
            Watcher.targetObject = Watcher.playerObject:GetTarget();
            Watcher.targetEffectsObject = nil;
            UpdatePlayerTarget();
        end
    end
    
    UpdatePlayerTarget();
end

local function ItemEquipped(sender, args)
--Puts("ItemEquipped: args = " .. Serialize(args));    
    local slot, item, name = args.Index, args.Item, args.Item:GetName();
    local byName = Watcher.playerEquipmentObject.cache.byName;
    if (byName[name] == nil) then
        byName[name] = {};
    end
    table.insert(byName[name], item);
    table.insert(byName[name], slot);
    Watcher.playerEquipmentObject.cache.bySlot[slot] = { item, name };
    DoCallbacks(Watcher, "ItemEquipped", args);
end

local function ItemUnequipped(sender, args)
--Puts("ItemUnequipped: args = " .. Serialize(args));    
    local slot, name = args.Index, args.Item:GetName();
    Watcher.playerEquipmentObject.cache.bySlot[slot] = nil;
    local byName = Watcher.playerEquipmentObject.cache.byName;
    if (byName[name]) then
        table.remove(byName[name], 1);
        table.remove(byName[name], 1);
        if (#byName[name] == 0) then
            byName[name] = nil;
        end
    end
    DoCallbacks(Watcher, "ItemUnequipped", args);
end

local function ItemAdded(sender, args)
--Puts("ItemAdded: args = " .. Serialize(args));    
    -- occurs out-of-combat; efficiency not important
    Watcher.playerBackpackObject.cache = nil;
end

local function ItemRemoved(sender, args)
--Puts("ItemRemoved: args = " .. Serialize(args));    
    -- occurs out-of-combat; efficiency not important
    Watcher.playerBackpackObject.cache = nil;
end

local function ItemMoved(sender, args)
    -- occurs out-of-combat; efficiency not important
    Watcher.playerBackpackObject.cache = nil;
    DoCallbacks(Watcher, "ItemMoved", args);
end

local function BackpackSizeChanged(sender, args)
    -- rare occurrence, out-of-combat; efficiency not important
    Watcher.playerBackpackObject.cache = nil;
end

local function ChatReceived(sender, args)
    if (args.ChatType == Turbine.ChatType.Advancement) then
        if (string.match(args.Message, Watcher.SkillsChangedStr)) then
            -- Trait line changed.  Reread skills lists.
            Watcher.playerTrainedSkills.cache = nil;
            Watcher.playerUntrainedSkills.cache = nil;
        elseif (string.match(args.Message, Watcher.TraitTreeChangedStr)) then
            DoDelayedCallback(Turbine.Engine.GetGameTime() + 1.5, "TraitTreeChanged", args);
        end
    end
end

local function SkillAdded(sender, args)
    -- rare occurrence, out-of-combat; efficiency not important
    GetSkills(Watcher.playerTrainedSkills);
    GetSkills(Watcher.playerUntrainedSkills);
end

local function SkillRemoved(sender, args)
    -- rare occurrence, out-of-combat; efficiency not important
    GetSkills(Watcher.playerTrainedSkills);
    GetSkills(Watcher.playerUntrainedSkills);
end

local function InCombatChanged()
        local inCombat = Watcher.playerObject:IsInCombat();
    if (inCombat) then
                collectgarbage("stop");
    else
                collectgarbage("restart");
    end
        DoCallbacks(Watcher, "InCombatChanged", { InCombat = inCombat });
end

local function LoadSettings()
--Puts("Loading...");
    Turbine.PluginData.Load(Turbine.DataScope.Account, "Thurallor_GameInfo", function(loadData, args)
        if (not loadData) then
            return;
        end
        
        -- Workaround for Turbine localization bug.
        local settings = ImportTable(loadData);
        if (not settings) then
            return;
        end
        
        -- Previously-saved settings override the defaults
        DeepTableCopy(settings, Watcher.settings);
        
        -- Compile loaded functions
        for categories, sourceCode in pairs(settings.funcs.AddCategories) do
            Watcher.AddCategories[categories] = loadstring(sourceCode);
        end
        for categories, sourceCode in pairs(settings.funcs.RemoveCategories) do
            Watcher.RemoveCategories[categories] = loadstring(sourceCode);
        end
        
        -- Lua API bug workaround: Target effects object will not contain valid
        -- data until a few ticks after plugin load.
        if (Watcher.targetEffectsObject) then
            Watcher.targetEffectsObject.cache = nil;
        end
--Puts("Load complete.");
    end);
end

function Watcher.GetTrainedSkillsInfo()
    local cache = Watcher.playerTrainedSkills.cache;
    if (not cache) then
        cache = GetSkills(Watcher.playerTrainedSkills);
    end
    return cache;
end

function Watcher.GetUntrainedSkillsInfo()
    local cache = Watcher.playerUntrainedSkills.cache;
    if (not cache) then
        cache = GetSkills(Watcher.playerUntrainedSkills);
    end
    return cache;
end

function Watcher.GetSkillByName(name)
    local cache = Watcher.playerTrainedSkills.cache;
    if (not cache) then
        cache = GetSkills(Watcher.playerTrainedSkills);
    end
    local skill = cache.byName[name];
    if (skill) then
        return skill;
    end
    local cache = Watcher.playerUntrainedSkills.cache;
    if (not cache) then
        cache = GetSkills(Watcher.playerUntrainedSkills);
    end
    return cache.byName[name];
end

-- accepts a list of names (aliases for the same skill)
function Watcher.GetSkillByNames(names)
    for _, name in pairs(names) do
        local skill = Watcher.GetSkillByName(name);
        if (skill) then
            return skill;
        end
    end
end

function Watcher.GetSkillsInfo(trainedSkillsFirst)
    local trainedSkillsCache = Watcher.GetTrainedSkillsInfo();
    local untrainedSkillsCache = Watcher.GetUntrainedSkillsInfo();
    if (#untrainedSkillsCache.names > 0) then
        local cache = { names = {}; byName = {}; byIcon = {} };
        for c in values({ trainedSkillsCache, untrainedSkillsCache }) do
            for k, v in pairs(c.byIcon) do
                cache.byIcon[k] = v;
            end
            for k, v in pairs(c.byName) do
                cache.byName[k] = v;
            end
            for k, v in pairs(c.names) do
                table.insert(cache.names, v);
            end
        end
        if (not trainedSkillsFirst) then
            table.sort(cache.names);
        end
        return cache;
    else
        return trainedSkillsCache;
    end
end

function Watcher.GetGambitsInfo()
    local cache = Watcher.playerTrainedGambits.cache;
    if (not cache) then
        cache = GetGambits();
    end
    for name in sorted_keys(cache.byName) do
        table.insert(cache.names, name);
    end
    return cache;
end

function Watcher.GetKnownEffectNames()
    local names = {};
    for name in sorted_keys(Watcher.settings.knownEffects) do
        table.insert(names, name);
    end
    return names;
end

-- If itemName is specified, the equipped item (if any) with the specified name is returned.
-- If itemSlot is specified, the equipped item (if any) at the specified slot is returned.
-- If neither is specified, a random equipped item (if any) is returned.
-- Returns slot, item (or nil).
function Watcher.GetEquippedItem(itemName, itemSlot)
    if ((not itemSlot) and (not itemName)) then
        for name, byName in pairs(Watcher.playerEquipmentObject.cache.byName) do
            local item, slot = unpack(byName);
            return slot, item;
        end
    elseif (not itemSlot) then
        local byName = Watcher.playerEquipmentObject.cache.byName[itemName];
        if (byName) then
            local item, slot = unpack(byName);
            return slot, item;
        end
    elseif (not itemName) then
        local bySlot = Watcher.playerEquipmentObject.cache.bySlot[itemSlot];
        if (bySlot) then
            local item, name = unpack(bySlot);
            return itemSlot, item;
        end
    else -- itemName and itemSlot specified
        local bySlot = Watcher.playerEquipmentObject.cache.bySlot[itemSlot];
        if (bySlot) then
            local item, name = unpack(bySlot);
            if (name == itemName) then
                return itemSlot, item;
            end
        end
    end
end

function Watcher.GetItemQuantity(itemName)
    local quantity = 0;
    local cache = Watcher.playerBackpackObject.cache or GetBackpack();
    local slots = cache.byName[itemName];
    if (slots) then
        for i, s in pairs(slots) do
            local item = Watcher.playerBackpackObject:GetItem(s);
            if (item) then
                quantity = quantity + item:GetQuantity();
            end
        end
    end
    return quantity;
end

-- Can accept either an iconID (number) or a skill name (string).
function Watcher.SkillReady(arg)
    local cache = Watcher.playerTrainedSkills.cache;
    if (not cache) then
        cache = GetSkills(Watcher.playerTrainedSkills);
    end
    if (type(arg) == "string") then
        skill = cache.byName[arg];
    elseif (type(arg) == "number") then
        skill = cache.byIcon[arg];
    end
--    return (skill and skill:IsUsable() and (skill:GetResetTime() == -1));
    return (skill and (skill:GetResetTime() == -1));
end

-- Can accept either an iconID (number) or a skill name (string).
function Watcher.SkillUsable(arg)
    local cache = Watcher.playerTrainedSkills.cache;
    if (not cache) then
        cache = GetSkills(Watcher.playerTrainedSkills);
    end
    if (type(arg) == "string") then
        skill = cache.byName[arg];
    elseif (type(arg) == "number") then
        skill = cache.byIcon[arg];
    end
    return (skill and skill:IsUsable());
end

-- Can accept either an iconID (number) or a skill name (string).
function Watcher.SkillTrained(arg)
    local cache, skill = Watcher.playerTrainedSkills.cache;
    if (not cache) then
        cache = GetSkills(Watcher.playerTrainedSkills);
    end
    if (type(arg) == "string") then
        skill = cache.byName[arg];
    elseif (type(arg) == "number") then
        skill = cache.byIcon[arg];
    end
    return (skill ~= nil);
end

local function WatchPlayerEffects()
    -- Start watching player effects.
    Watcher.playerEffectsObject = Watcher.playerObject:GetEffects();
    AddCallback(Watcher.playerEffectsObject, "EffectAdded", PlayerEffectAdded);
    AddCallback(Watcher.playerEffectsObject, "EffectRemoved", PlayerEffectRemoved);
    AddCallback(Watcher.playerEffectsObject, "EffectsCleared", PlayerEffectsCleared);
    GetActorEffects(Watcher.playerEffectsObject);
end

-- Returns whether effects of the specified category exist on the player.
-- If 'curable' is true, then this function returns whether there are curable effects of the specified category.
function Watcher.PlayerHasEffectCategory(category, curable)
    if (not Watcher.playerEffectsObject) then
        WatchPlayerEffects();
    end
    if (category) then
        if (curable) then
            return Watcher.playerEffectsObject.activeCurableCategories[category] > 0;
        else
            return Watcher.playerEffectsObject.activeCategories[category] > 0;
        end
    end
end

function Watcher.PlayerHasEffect(name)
    if (not Watcher.playerEffectsObject) then
        WatchPlayerEffects();
    end
    if (name) then
        return Watcher.playerEffectsObject.cache[name];
    end
end

local function WatchTargetEffects()
    -- Start watching target effects.
    if (Watcher.targetObject and Watcher.targetObject.GetEffects) then
        Watcher.targetEffectsObject = Watcher.targetObject:GetEffects();
        AddCallback(Watcher.targetEffectsObject, "EffectAdded", TargetEffectAdded);
        AddCallback(Watcher.targetEffectsObject, "EffectRemoved", TargetEffectRemoved);
        AddCallback(Watcher.targetEffectsObject, "EffectsCleared", TargetEffectsCleared);
        GetActorEffects(Watcher.targetEffectsObject);
    end
end

function Watcher.TargetHasEffectCategory(category)
    if (not Watcher.targetEffectsObject) then
        WatchTargetEffects();
    end
    if (Watcher.targetEffectsObject) then
        return Watcher.targetEffectsObject.activeCategories[category] > 0;
    end
end

function Watcher.TargetHasEffect(name)
    if (not Watcher.targetEffectsObject) then
        WatchTargetEffects();
    end
    if (Watcher.targetEffectsObject) then
        return Watcher.targetEffectsObject.cache[name];
    end
end

function Watcher.TargetIsPartyMember()
    return Watcher.targetIsPartyMember;
end

function Watcher.GetStance()
    return Watcher.stance;
end

function Watcher.GetParty()
    return Watcher.playerPartyObject;
end

function Watcher.GetPartyMemberByName(name, ignoreCase)
    if (Watcher.playerPartyObject) then
        -- Bug: MemberAdded, MemberRemoved events don't fire for raids (only fellowships)
        -- Therefore we have to reread the object every time, because we don't know if it's changed.
        GetParty(); -- workaround
        if (ignoreCase) then
            return Watcher.playerPartyObject.cache.byNameUpper[string.upper(name)];
        else
            return Watcher.playerPartyObject.cache.byName[name];
        end
    end
end

function Watcher.GetPartyAssistTarget(num)
    local party = Watcher.playerPartyObject;
    if (party and (num <= party:GetAssistTargetCount())) then
        return party:GetAssistTarget(num);
    end
end

-- Returns party members one after the other, in round-robin fashion
-- "prevMember" argument is an optional Player object
function Watcher.GetNextPartyMember(prevMember)
    if (prevMember) then
        Watcher.prevPartyMember = prevMember;
    else
        local party = Watcher.playerPartyObject;
        if (party) then
            local member = Watcher.prevPartyMember or party:GetMember(1);
            local num = (member and member.num) or 0;
            while true do
                num = num + 1;
                if (num > party:GetMemberCount()) then
                    num = 1;
                end
                member = party:GetMember(num);
                if (member) then
                    return member;
                end
            end
        end
    end
end

function Watcher.GetNextAssistTarget()
    local party = Watcher.playerPartyObject;
    if (party and (party:GetAssistTargetCount() > 0)) then
        local member = Watcher.prevPartyMember or party:GetMember(1);
        local num = (member and member.num) or 0;
        while true do
            num = num + 1;
            if (num > party:GetMemberCount()) then
                num = 1;
            end
            member = party:GetMember(num);
            if (member and party:IsAssistTarget(member)) then
                return member;
            end
        end
    end
end

function Watcher.GetPlayerTarget()
    return Watcher.targetObject;
end

function Watcher.GetTargetsTarget()
    -- If target's target not previously cached, get it now.
    if (not Watcher.targetsTargetObject and Watcher.targetObject and Watcher.targetObject.GetTarget) then
        Watcher.targetsTargetObject = Watcher.targetObject:GetTarget();
    end
    return Watcher.targetsTargetObject;
end

-- Saves the selected target, assigning the specified ID.
function Watcher.SaveTarget(id)
    Watcher.savedTargets[id] = Watcher.targetObject;
end

-- Returns the saved target with the given ID.
function Watcher.GetSavedTarget(id)
    local target = Watcher.savedTargets[id];
    if (target and target.GetName and target:GetName() and (target:GetName() ~= "")) then
        return target;
    end
end

function Watcher.TraitTreeChanged()
    -- Trait line changed.  Reread skills lists.
    Watcher.playerTrainedSkills.cache = nil;
    Watcher.playerUntrainedSkills.cache = nil;
end

local function Constructor()
    
    -- Default settings
    Watcher.settings = {};
    Watcher.settings.knownEffects = {};
    Watcher.settings.funcs = {};
    Watcher.settings.funcs.AddCategories = {};
    Watcher.settings.funcs.RemoveCategories = {};

    -- Optimized functions generated on-the-fly
    Watcher.AddCategories = {};
    Watcher.RemoveCategories = {};

    Watcher.savedTargets = {};
    
    -- Object for monitoring display updates:
    Watcher.window = Turbine.UI.Window();
    function Watcher.window:Update()
        self:SetWantsUpdates(false);
        if (Watcher.delayedCallbacks) then
            local notyet = {};
            for k, v in pairs(Watcher.delayedCallbacks) do
                if (Turbine.Engine.GetGameTime() >= v.time) then
                    DoCallbacks(Watcher, v.event, v.args);
                else
                    table.insert(notyet, v);
                end
            end
            Watcher.delayedCallbacks = nil;
            if (#notyet > 0) then
                Watcher.delayedCallbacks = notyet;
                self:SetWantsUpdates(true);
            end
        end
    end
    
    -- Object for monitoring player events
    Watcher.playerObject = Turbine.Gameplay.LocalPlayer:GetInstance();
    AddCallback(Watcher.playerObject, "InCombatChanged", InCombatChanged);
    AddCallback(Watcher.playerObject, "PartyChanged", PlayerPartyChanged);
    AddCallback(Watcher.playerObject, "TargetChanged", PlayerTargetChanged);
    PlayerPartyChanged();
    PlayerTargetChanged();
    
    Watcher.playerEquipmentObject = Watcher.playerObject:GetEquipment();
    AddCallback(Watcher.playerEquipmentObject, "ItemEquipped", ItemEquipped);
    AddCallback(Watcher.playerEquipmentObject, "ItemUnequipped", ItemUnequipped);
    GetEquipment();

    Watcher.playerBackpackObject = Watcher.playerObject:GetBackpack();
    AddCallback(Watcher.playerBackpackObject, "ItemAdded", ItemAdded);
    AddCallback(Watcher.playerBackpackObject, "ItemRemoved", ItemRemoved);
    AddCallback(Watcher.playerBackpackObject, "ItemMoved", ItemMoved);
    AddCallback(Watcher.playerBackpackObject, "SizeChanged", BackpackSizeChanged);
    GetBackpack();
    
    Watcher.playerTrainedSkills = Watcher.playerObject:GetTrainedSkills();
    AddCallback(Watcher.playerTrainedSkills, "SkillAdded", PlayerSkillAdded);
    AddCallback(Watcher.playerTrainedSkills, "SkillRemoved", PlayerSkillRemoved);
    GetSkills(Watcher.playerTrainedSkills);
    
    Watcher.playerUntrainedSkills = Watcher.playerObject:GetUntrainedSkills();
    GetSkills(Watcher.playerUntrainedSkills);
    
    Watcher.playerClassAttributes = Watcher.playerObject:GetClassAttributes();
    if (Watcher.playerObject:GetClass() == Turbine.Gameplay.Class.Warden) then
        Watcher.playerTrainedGambits = Watcher.playerClassAttributes:GetTrainedGambits();
    else
        Watcher.playerTrainedGambits = {};
    end
    
    -- Lua API bug workaround:
    -- Need to cache the GetStance() result, because it returns incorrect values
    -- after the user changes trait trees, until he selects a new stance.
    if (Watcher.playerClassAttributes.GetStance) then
        Watcher.stance = Watcher.playerClassAttributes:GetStance();
        Watcher.playerClassAttributes.StanceChanged = StanceChanged;
    end
    
    -- For monitoring chat messages
    AddCallback(Turbine.Chat, "Received", ChatReceived);
    local language = Turbine.Engine:GetLanguage();
    if ((language == Turbine.Language.EnglishGB) or (language == Turbine.Language.English)) then
        Watcher.SkillsChangedStr = "^You have acquired the .* skill%.";
        Watcher.TraitTreeChangedStr = "^You have acquired the Class Specialization Bonus Trait";
    elseif (language == Turbine.Language.German) then
        Watcher.SkillsChangedStr = "^Ihr habt Euch die Fertigkeit .* angeeignet%.";
        Watcher.TraitTreeChangedStr = "^Ihr habt diese Bonus-Eigenschaft für Klassenspezialisierung erlangt";
    elseif (language == Turbine.Language.French) then
        Watcher.SkillsChangedStr = "^Vous avez acquis la compétence .*%.";
        Watcher.TraitTreeChangedStr = "^Vous avez obtenu le trait bonus de spécialisation de classe";
    end
end


-- Create single instance and load saved settings (if any).
Constructor();
LoadSettings();


--t = Turbine.UI.Window();
--t:SetVisible(true);
--t:SetBackColor(Turbine.UI.Color.Red);
--t:SetMouseVisible(true);
--function t:MouseClick()
--    Puts("cache = " .. PrettyPrint(Watcher.playerEffectsObject.cache, ""));
--end

Thurallor = Thurallor or {};
Thurallor.Utils = Thurallor.Utils or {};
Thurallor.Utils.Watcher = Watcher;

Go to most recent revision | Compare with Previous | Blame


All times are GMT -5. The time now is 03:12 AM.


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