Compare with Previous | Blame | View Log
--[[
Addon: Taos Group Tools
Author: TProg Taonnor
Created by @Taonnor
]]--
--[[
Global callbacks
]]--
TGT_PLAYER_ACTIVATED = "TGT-PlayerActivated"
TGT_PLAYER_DATA_OFFLINE_CHANGED = "TGT-PlayerDataOfflineChanged"
TGT_PLAYER_DATA_BUFFS_CHANGED = "TGT-PlayerDataBuffsChanged"
TGT_PLAYER_DATA_REMOTE_CHANGED = "TGT-PlayerDataRemoteChanged"
TGT_PLAYER_DATA_REFRESH = "TGT-PlayerHandlerRefresh"
TGT_PLAYER_DATA_CLEAR = "TGT-PlayerDataClear"
TGT_PLAYER_DATA_SUB_GROUP_CHANGED = "TGT-PlayerDataSubGroupChanged"
TGT_PLAYER_DATA_PURGE_CHANGED = "TGT-PlayerDataPurgeChanged"
--[[
Global values
]]--
PLAYERTIMEOUT = 4 -- s; GetTimeStamp() is in seconds
COMBATTIMEOUT = 30000 -- ms
--[[
Local variables
]]--
-- Set to local for faster access
local MESSAGE_PLAYER_RESOURCES = MESSAGE_PLAYER_RESOURCES
local MESSAGE_PLAYER_DPS = MESSAGE_PLAYER_DPS
local MESSAGE_PLAYER_HPS = MESSAGE_PLAYER_HPS
local REFRESHRATE = 1000 -- ms; RegisterForUpdate is in miliseconds
local TIMEOUT = PLAYERTIMEOUT
local EARTHGORE_ID = 97855
local EARTHGORE_CD = 35000 -- ms
local EARTHGORE = EARTHGORE_ICON_ID
local _logger = nil
local _settingsHandler = TGT_SettingsHandler
local _name = "TGT-PlayerHandler"
local _playerMyself = nil
local _groupPlayers = {}
local _lastCombatTimestamp = 0
local _lastGarbageCollectionTimestamp = 0
local _foodBuffs = FOOD_BUFFS
local _trackedBuffs = TRACKED_BUFFS
local _trackedSpecificBuffs = {
[EARTHGORE_ICON_ID] = {},
[DETONATON_ICON_ID] = {},
[SPEEDBUFF_ICON_ID] = {},
}
--[[
Table TGT_PlayerHandler
]]--
TGT_PlayerHandler = {}
TGT_PlayerHandler.__index = TGT_PlayerHandler
--[[
==============
PRIVATE METHODS
==============
]]--
--[[
GetAllReceivedHealing gets healing from all players
]]--
local function GetAllReceivedHealing()
local healingSum = 0
for i, player in pairs(_groupPlayers) do
healingSum = healingSum + player.HealingReceived
end
return healingSum
end
--[[
UpdateAllRelativeHeal updates all relative heal
]]--
local function UpdateAllRelativeHeal()
local allReceivedHealing = GetAllReceivedHealing()
for i, player in pairs(_groupPlayers) do
player.HealingReceivedRelative = player.HealingReceived / allReceivedHealing * 100
end
end
--[[
ResetAllPlayerHeal resets all player heal
]]--
local function ResetAllPlayerHeal()
for i, player in pairs(_groupPlayers) do
player.HealingReceived = 0
player.HealingReceivedRelative = 0
end
end
--[[
UpdatePlayerHps updates player hps from pingPlayer
]]--
local function UpdatePlayerHps(player, pingPlayer)
if (pingPlayer.HealingReceived > 0) then
player.HealingReceived = player.HealingReceived + pingPlayer.HealingReceived
UpdateAllRelativeHeal()
elseif (pingPlayer.HealingReceived == 0) then
ResetAllPlayerHeal()
else
_logger:logError("PlayerHandler -> UpdatePlayerHps; HealingReceived unexpected value", pingPlayer.HealingReceived)
end
end
--[[
GetAllReceivedDamage gets damage from all players
]]--
local function GetAllReceivedDamage()
local damageSum = 0
for i, player in pairs(_groupPlayers) do
damageSum = damageSum + player.DamageReceived
end
return damageSum
end
--[[
UpdateAllRelativeDmg updates all relative dmg
]]--
local function UpdateAllRelativeDmg()
local allReceivedDmg = GetAllReceivedDamage()
for i, player in pairs(_groupPlayers) do
player.DamageReceivedRelative = player.DamageReceived / allReceivedDmg * 100
end
end
--[[
ResetAllPlayerDmg resets all player dmg
]]--
local function ResetAllPlayerDmg()
for i, player in pairs(_groupPlayers) do
player.DamageReceived = 0
player.DamageReceivedRelative = 0
end
end
--[[
UpdatePlayerDps updates player dps from pingPlayer
]]--
local function UpdatePlayerDps(player, pingPlayer)
if (pingPlayer.DamageReceived > 0) then
player.DamageReceived = player.DamageReceived + pingPlayer.DamageReceived
UpdateAllRelativeDmg()
elseif (pingPlayer.DamageReceived == 0) then
ResetAllPlayerDmg()
else
_logger:logError("PlayerHandler -> UpdatePlayerDps; DamageReceived unexpected value", pingPlayer.DamageReceived)
end
end
--[[
UpdatePlayerResources updates player resources from pingPlayer
]]--
local function UpdatePlayerResources(player, pingPlayer)
-- Update UltimateGroup or Earthgore, if procced
if (pingPlayer.IsEarthgoreProcced) then
-- -1000, because of lag
player.IsEarthgoreProccedTimestamp = GetGameTimeMilliseconds() - 1000
else
if (player.UltimateGroup == nil or
player.UltimateGroup.GroupAbilityPing ~= pingPlayer.GroupAbilityPing) then
local ultimateGroup = TGT_UltimateGroupHandler.GetUltimateGroupByAbilityPing(pingPlayer.GroupAbilityPing)
local ultimateName = GetAbilityName(ultimateGroup.GroupAbilityId)
local ultimateIcon = GetAbilityIcon(ultimateGroup.GroupAbilityId)
player.UltimateGroup = ultimateGroup
player.UltimateName = ultimateName
player.UltimateIcon = ultimateIcon
end
end
-- Update relative ultimate
if (player.IsPlayerDead) then
player.RelativeUltimate = 0
else
if (player.RelativeUltimate ~= pingPlayer.RelativeUltimate) then
-- play sound if ultimate ready
if (player.RelativeUltimate < 100 and pingPlayer.RelativeUltimate >= 100) then
local sound = TGT_SettingsHandler.SavedVariables.SoundOnReady
if (sound[1] > 1) then PlaySound(SOUNDS[sound[2]]) end
end
-- play sound if ultimate raised
if (player.RelativeUltimate >= 100 and pingPlayer.RelativeUltimate < 100) then
local sound = TGT_SettingsHandler.SavedVariables.SoundOnThrown
if (sound[1] > 1) then PlaySound(SOUNDS[sound[2]]) end
end
player.RelativeUltimate = pingPlayer.RelativeUltimate
end
end
-- Update relative magicka
if (player.RelativeMagicka ~= pingPlayer.RelativeMagicka) then
player.RelativeMagicka = pingPlayer.RelativeMagicka
end
-- Update relative stamina
if (player.RelativeStamina ~= pingPlayer.RelativeStamina) then
player.RelativeStamina = pingPlayer.RelativeStamina
end
end
--[[
UpdateFoodBuff on player
]]--
local function UpdateFoodBuff(player)
-- Reset first
player.FoodBuffActive = false
player.FoodBuffIcon = nil
-- Iterate buffs and set new
for i = 1, GetNumBuffs(player.PingTag) do
local _, _, _, _, _, iconFile, _, _, _, _, abilityId = GetUnitBuffInfo(player.PingTag, i)
if (_foodBuffs[abilityId] ~= nil) then
player.FoodBuffActive = true
player.FoodBuffIcon = iconFile
break
end
end
end
--[[
GetNewPlayer Gets new empty player
]]--
local function GetNewPlayer(pingTag)
local current, max, effectiveMax = GetUnitPower(pingTag, POWERTYPE_HEALTH)
local relativeHealth = math.floor((current / max * 100))
if (relativeHealth > 100) then
relativeHealth = 100
end
local playerName = GetUnitName(pingTag)
local newPlayer = {
PingTag = pingTag,
PlayerPosition = tonumber(pingTag:match('(%d+)$')),
GroupIdentifer = _settingsHandler.SavedVariables.PlayerFrameGroups[playerName],
PlayerName = playerName,
IsPlayerDead = IsUnitDead(pingTag),
IsPlayerInCombat = IsUnitInCombat(pingTag),
IsPlayerTimedOut = true,
IsPlayerOnline = true,
LastMapPingTimestamp = 0,
UltimateName = nil,
UltimateIcon = nil,
UltimateGroup = nil,
CurentHealth = current,
CurrentHealthPool = max,
RelativeHealth = (current / max * 100),
RelativeUltimate = 0,
RelativeMagicka = 0,
RelativeStamina = 0,
CurrentShield = 0,
RelativeShield = 0,
DamageDone = 0,
HealingDone = 0,
DamageToSend = 0,
HealingToSend = 0,
DamageReceived = 0,
DamageReceivedRelative = 0,
HealingReceived = 0,
HealingReceivedRelative = 0,
IsEarthgoreProcced = false,
IsEarthgoreProccedTimestamp = 0,
Buffs = {},
FoodBuffActive = false,
FoodBuffIcon = nil,
PurgableDebuffs = 0,
IsPurgable = false
}
-- Updates Buffs
UpdateFoodBuff(newPlayer)
return newPlayer
end
--[[
SetUltimate Sets PlayerMyself ultimate; called on TGT_STATIC_ULTIMATE_ID_CHANGED
]]--
local function SetUltimate(staticUltimateID)
_playerMyself.UltimateGroup = TGT_UltimateGroupHandler.GetUltimateGroupByAbilityId(staticUltimateID)
end
--[[
UpdatePlayers Removes player if left; called on TAO_GROUP_CHANGED
]]--
local function UpdatePlayers(playerName, isJoined)
if (playerName ~= "" or playerName ~= nil) then
-- Player joined
if (isJoined) then
if (_groupPlayers[playerName] == nil) then
local isGroupMember, unitTag = IsTargetGroupMember(playerName)
if (isGroupMember) then
local player = GetNewPlayer(unitTag)
_groupPlayers[playerName] = player
FireCallbacksAsync(TGT_PLAYER_DATA_OFFLINE_CHANGED, player)
else
_logger:logDebug("isGroupMember == false", unitTag, playerName)
end
end
-- Player left
else
if (_groupPlayers[playerName] ~= nil) then
local player = _groupPlayers[playerName]
FireCallbacksAsync(TGT_PLAYER_DATA_CLEAR, player)
_groupPlayers[playerName] = nil
end
end
else
_logger:logError("PlayerHandler -> UpdatePlayers; playerName invalid", playerName)
end
-- Prevent spam
if (GetGameTimeMilliseconds() - _lastGarbageCollectionTimestamp > 10000) then
_lastGarbageCollectionTimestamp = GetGameTimeMilliseconds()
collectgarbage()
end
end
--[[
UpdateGroup Add or removes complete groupPlayers list; called on TAO_UNIT_GROUPED_CHANGED
]]--
local function UpdateGroup(isGrouped)
for i,player in pairs(_groupPlayers) do
if (isGrouped) then
local isGroupMember, unitTag = IsTargetGroupMember(player.PlayerName)
if (not isGroupMember) then
FireCallbacksAsync(TGT_PLAYER_DATA_CLEAR, player)
_groupPlayers[player.PlayerName] = nil
end
else
FireCallbacksAsync(TGT_PLAYER_DATA_CLEAR, player)
_groupPlayers[player.PlayerName] = nil
end
end
-- Add all group players, which not already in group list
if (isGrouped) then
for i = 1, GetGroupSize() do
local unitTag = GetGroupUnitTagByIndex(i)
if (unitTag) then
local playerName = GetUnitName(unitTag)
if (_groupPlayers[playerName] == nil) then
local player = GetNewPlayer(unitTag)
_groupPlayers[playerName] = player
FireCallbacksAsync(TGT_PLAYER_DATA_OFFLINE_CHANGED, player)
else
-- Refresh tag / position
local player = _groupPlayers[playerName]
player.PingTag = unitTag
player.PlayerPosition = tonumber(unitTag:match('(%d+)$'))
end
end
end
end
-- Prevent spam
if (GetGameTimeMilliseconds() - _lastGarbageCollectionTimestamp > 10000) then
_lastGarbageCollectionTimestamp = GetGameTimeMilliseconds()
collectgarbage()
end
end
--[[
OnCombatEvent Handles several combat events; called on EVENT_COMBAT_EVENT
]]--
local function OnCombatEvent(eventCode, result, isError, abilityName, graphic, actionSlotType, sourceName, sourceType, targetName, targetType, hitValue, powerType, damageType, log, sourceUnitID, targetUnitID, abilityID)
-- Actions
local isMyAction = (sourceType == COMBAT_UNIT_TYPE_PLAYER) or (sourceType == COMBAT_UNIT_TYPE_PLAYER_PET)
local isDmgAction = (result == ACTION_RESULT_DAMAGE) or (result == ACTION_RESULT_CRITICAL_DAMAGE) or (result == ACTION_RESULT_DOT_TICK) or (result == ACTION_RESULT_DOT_TICK_CRITICAL)
local isHealAction = (result == ACTION_RESULT_HEAL) or (result == ACTION_RESULT_CRITICAL_HEAL) or (result == ACTION_RESULT_HOT_TICK) or (result == ACTION_RESULT_HOT_TICK_CRITICAL)
-- DMG
if (isMyAction and isDmgAction and (sourceName ~= targetName) and (hitValue > 0)) then
_playerMyself.DamageDone = _playerMyself.DamageDone + hitValue
end
-- HEAL
local isTargetGroupMember, unitTag = IsTargetGroupMember(targetName)
if (isMyAction and isHealAction and (sourceName ~= targetName) and (hitValue > 0) and isTargetGroupMember) then
local currentHealth, maxHealth, effectiveMaxHealth = GetUnitPower(unitTag, POWERTYPE_HEALTH)
-- Realy healed?
if (currentHealth ~= maxHealth) then
_playerMyself.HealingDone = _playerMyself.HealingDone + hitValue
end
end
end
--[[
IsTrackedBuff Checks abilityId or IconName, because ZOS buff/debuff tracking sends inconsistend values for abilityId's
]]--
local function IsTrackedBuff(abilityId, iconName)
-- Check specific buffs like speedbuff, deto, earthgore
if (_trackedSpecificBuffs[abilityId] ~= nil) then
return true, abilityId
end
-- Check buffs via icons
for i, buff in ipairs(_trackedBuffs) do
if (string.match(iconName, buff.IconName)) then
return true, buff.IconId -- found
end
end
-- Nothing found
return false, nil
end
--[[
IsPurgableEffect Checks several values, because ZOS buff/debuff tracking sends inconsistend values for abilities
]]--
local function IsPurgableEffect(abilityId, abilityType, statusEffectType)
local purgableAbilities = {
[15775] = {}, -- Öl
[15776] = {}, -- Öl
[28480] = {}, -- Feuerballiste
[66243] = {}, -- Kalthafen Feuerballiste
[25869] = {}, -- Feuertopf-Tribok
[66247] = {}, -- Feuertopf-Tribok
[20528] = {}, -- DK Krallen
[31898] = {}, -- DK Krallen
[104825] = {}, -- Warden Eis
[44549] = {}, -- Bogen Gift
[89491] = {}, -- Heimsuchender Fluch
[28452] = {}, -- Daedrisches Grabmal
}
local isPurgableAbility = purgableAbilities[abilityId] ~= nil
local isPurgableAbilityType =
abilityType == ABILITY_TYPE_STUN or
abilityType == ABILITY_TYPE_DISORIENT or
abilityType == ABILITY_TYPE_FEAR
local isPurgableStatusEffect =
statusEffectType == STATUS_EFFECT_TYPE_DISEASE or
statusEffectType == STATUS_EFFECT_TYPE_MESMERIZE or
statusEffectType == STATUS_EFFECT_TYPE_ROOT or
statusEffectType == STATUS_EFFECT_TYPE_STUN
return isPurgableAbility or isPurgableAbilityType or isPurgableStatusEffect
end
--[[
OnEffectChangedEvent Handles several effect events; called on EVENT_EFFECT_CHANGED
]]--
local function OnEffectChangedEvent(eventCode, changeType, effectSlot, effectName, unitTag, beginTime, endTime, stackCount, iconName, buffType, effectType, abilityType, statusEffectType, unitName, unitId, abilityId, sourceType)
local isMyself = sourceType == COMBAT_UNIT_TYPE_PLAYER
local isGroupPlayer = string.match(unitTag, "group")
local isGained = changeType == EFFECT_RESULT_GAINED
local isFaded = changeType == EFFECT_RESULT_FADED
-- Earthgore
if (abilityId == EARTHGORE_ID and isGained and isMyself) then
_playerMyself.IsEarthgoreProcced = true
end
-- TODO: To find further purgable skills
if (GetUnitName(unitTag) == GetUnitName("player")) then
_logger:logDebug(effectName, unitTag, unitName, abilityId, changeType)
--_logger:logDebug(iconName, buffType, effectType, abilityType, statusEffectType)
end
if (isGroupPlayer) then
-- Purge
if (IsPurgableEffect(abilityId, abilityType, statusEffectType)) then
local playerName = GetUnitName(unitTag)
if (_groupPlayers[playerName] ~= nil) then
local player = _groupPlayers[playerName]
if (changeType == EFFECT_RESULT_GAINED) then
if (player.PurgableDebuffs < 0) then
-- to prevent purgable debuffs are smaller than zero, because ZOS api sends sometimes updates after death or not
player.PurgableDebuffs = 0
end
player.PurgableDebuffs = player.PurgableDebuffs + 1
player.IsPurgable = true
--_logger:logDebug(playerName, "Purge add", effectName, abilityId, player.PurgableDebuffs)
elseif (changeType == EFFECT_RESULT_FADED) then
player.PurgableDebuffs = player.PurgableDebuffs - 1
if (player.PurgableDebuffs <= 0) then
player.PurgableDebuffs = 0
player.IsPurgable = false
--_logger:logDebug(playerName, "Purge remove", effectName, abilityId, player.PurgableDebuffs)
else
--_logger:logDebug(playerName, "Purge remove and update", effectName, abilityId, player.PurgableDebuffs)
end
else
player.IsPurgable = player.PurgableDebuffs > 0
--_logger:logDebug(playerName, "Purge update", effectName, abilityId, player.PurgableDebuffs)
end
FireCallbacksAsync(TGT_PLAYER_DATA_PURGE_CHANGED, player)
end
end
-- TrackedBuffs
local isTrackedBuff, iconId = IsTrackedBuff(abilityId, iconName)
if (isTrackedBuff) then
local playerName = GetUnitName(unitTag)
if (_groupPlayers[playerName] ~= nil) then
local player = _groupPlayers[playerName]
if (changeType == EFFECT_RESULT_GAINED) then
player.Buffs[iconId] = { isActive = true, startTime = beginTime, finishTime = endTime }
elseif (changeType == EFFECT_RESULT_FADED) then
player.Buffs[iconId] = { isActive = false, startTime = beginTime, finishTime = endTime }
else
player.Buffs[iconId] = { isActive = true, startTime = beginTime, finishTime = endTime }
end
FireCallbacksAsync(TGT_PLAYER_DATA_BUFFS_CHANGED, player, iconId)
end
end
end
end
--[[
UpdatePlayerHealth updates player health and fires TGT_PLAYER_DATA_OFFLINE_CHANGED
]]--
local function UpdatePlayerHealth(player, powerPool, powerPoolMax)
local isPlayerDead = IsUnitDead(player.PingTag)
if (isPlayerDead or powerPool == 0) then
player.CurentHealth = 0
player.IsPlayerDead = true
-- to prevent purgable debuffs will not disappear, because ZOS api sends sometimes updates after death or not
player.PurgableDebuffs = 0
player.IsPurgable = false
FireCallbacksAsync(TGT_PLAYER_DATA_PURGE_CHANGED, player)
else
player.CurentHealth = powerPool
player.IsPlayerDead = false
end
player.CurrentHealthPool = powerPoolMax
local relativeHealth = math.floor((powerPool / powerPoolMax * 100))
if (relativeHealth > 100) then
relativeHealth = 100
end
player.RelativeHealth = relativeHealth
FireCallbacksAsync(TGT_PLAYER_DATA_OFFLINE_CHANGED, player)
end
--[[
OnPowerUpdate Handles several player pool events; called on EVENT_POWER_UPDATE
]]--
local function OnPowerUpdate(evt, unitTag, powerPoolIndex, powerType, powerPool, powerPoolMax)
if (powerType == POWERTYPE_HEALTH) then
local playerName = GetUnitName(unitTag)
if (TGT_MOCKED) then
local isNotMyself = string.find(playerName, "reticleover")
if (isNotMyself == nil) then
for i,player in pairs(_groupPlayers) do
UpdatePlayerHealth(player, powerPool, powerPoolMax)
end
end
else
if (_groupPlayers[playerName] ~= nil) then
local player = _groupPlayers[playerName]
UpdatePlayerHealth(player, powerPool, powerPoolMax)
end
end
end
end
--[[
UpdatePlayerShield updates player shield and fires TGT_PLAYER_DATA_OFFLINE_CHANGED
]]--
local function UpdatePlayerShield(player, value)
local isPlayerDead = IsUnitDead(player.PingTag)
player.IsPlayerDead = isPlayerDead
if (isPlayerDead) then
player.CurrentShield = 0
else
player.CurrentShield = value
local relativeShield = math.floor((value / player.CurrentHealthPool * 100))
if (relativeShield > 100) then
relativeShield = 100
end
player.RelativeShield = relativeShield
end
FireCallbacksAsync(TGT_PLAYER_DATA_OFFLINE_CHANGED, player)
end
--[[
HandleVisualEvents Handles visual events from different base handlers
]]--
local function HandleVisualEvents(unitTag, powerType, visualType, value)
if (powerType == POWERTYPE_HEALTH) then
-- Shield
if (visualType == ATTRIBUTE_VISUAL_POWER_SHIELDING) then
local playerName = GetUnitName(unitTag)
if (TGT_MOCKED) then
local isNotMyself = string.find(playerName, "reticleover")
if (isNotMyself == nil) then
for i,player in pairs(_groupPlayers) do
UpdatePlayerShield(player, value)
end
end
else
if (_groupPlayers[playerName] ~= nil) then
local player = _groupPlayers[playerName]
UpdatePlayerShield(player, value)
end
end
end
end
end
--[[
OnVisualAdded Handles EVENT_UNIT_ATTRIBUTE_VISUAL_ADDED events
]]--
local function OnVisualAdded(evt, unitTag, visualType, stat, attribute, powerType, value, maxValue)
HandleVisualEvents(unitTag, powerType, visualType, value)
end
--[[
OnVisualUpdated Handles EVENT_UNIT_ATTRIBUTE_VISUAL_UPDATED events
]]--
local function OnVisualUpdated(evt, unitTag, visualType, stat, attribute, powerType, oldValue, newValue, oldMaxValue, newMaxValue)
HandleVisualEvents(unitTag, powerType, visualType, newValue)
end
--[[
OnVisualRemoved Handles EVENT_UNIT_ATTRIBUTE_VISUAL_REMOVED events
]]--
local function OnVisualRemoved(evt, unitTag, visualType, stat, attribute, powerType, value, maxValue)
HandleVisualEvents(unitTag, powerType, visualType, 0)
end
--[[
OnPlayerActivated fires TGT_PLAYER_ACTIVATED callbacks
]]--
local function OnPlayerActivated(eventCode)
FireCallbacksAsync(TGT_PLAYER_ACTIVATED)
for i,player in pairs(_groupPlayers) do
UpdateFoodBuff(player)
local health, maxHealth = GetUnitPower(player.PingTag, POWERTYPE_HEALTH)
UpdatePlayerHealth(player, health, maxHealth) -- Calls update offline player
FireCallbacksAsync(TGT_PLAYER_DATA_REMOTE_CHANGED, player)
end
FireCallbacksAsync(TGT_PLAYER_DATA_REFRESH)
end
--[[
UpdatePlayerData Updates group player data
]]--
local function UpdatePlayerData(pingPlayer)
local playerName = GetUnitName(pingPlayer.PingTag)
if (string.len(playerName) > 0) then
local player = nil
if (_groupPlayers[playerName] == nil) then
-- Should not happens, especially in MOCKED mode
_logger:logError("PlayerHandler -> UpdatePlayerData; Added player via UpdatePlayerData", playerName)
player = GetNewPlayer(pingPlayer.PingTag)
_groupPlayers[playerName] = player
else
player = _groupPlayers[playerName]
-- Refresh tag / position
player.PingTag = pingPlayer.PingTag
player.PlayerPosition = tonumber(pingPlayer.PingTag:match('(%d+)$'))
end
-- Update timestamp
player.LastMapPingTimestamp = GetTimeStamp()
if (pingPlayer.MessageType == MESSAGE_PLAYER_RESOURCES) then
UpdatePlayerResources(player, pingPlayer)
elseif (pingPlayer.MessageType == MESSAGE_PLAYER_DPS) then
UpdatePlayerDps(player, pingPlayer)
elseif (pingPlayer.MessageType == MESSAGE_PLAYER_HPS) then
UpdatePlayerHps(player, pingPlayer)
else
-- should not happen
_logger:logError(zo_strformat("PlayerHandler -> UpdatePlayerData; Message Type not valid (<<1>>)", messageType))
end
-- Update Earthgore procc; If last earthgore timestamp < EARTHGORE_CD, procc is on CD (true); otherwhise false
local lastProcDifference = GetGameTimeMilliseconds() - player.IsEarthgoreProccedTimestamp
player.Buffs[EARTHGORE] = { isActive = lastProcDifference < EARTHGORE_CD, startTime = 0, finishTime = 0 }
-- update the player
FireCallbacksAsync(TGT_PLAYER_DATA_REMOTE_CHANGED, player)
else
_logger:logError("PlayerHandler -> UpdatePlayerData; Ping Player name empty", pingPlayer.PingTag)
end
-- Clear ping player
pingPlayer = nil
end
--[[
GetMyselfData Gets PlayerMyself and updates values before
]]--
local function GetMyselfData(messageType)
-- Update combat timestamp
if (IsGroupInCombat()) then
_lastCombatTimestamp = GetGameTimeMilliseconds()
end
-- get combatPause
local combatPause = math.abs(_lastCombatTimestamp - GetGameTimeMilliseconds())
-- update needed
local updateNeeded = true
-- Get current image of player resources
if (messageType == MESSAGE_PLAYER_RESOURCES) then
local currentUltimate, maxUltimate, effectiveMaxUltimate = GetUnitPower("player", POWERTYPE_ULTIMATE)
local ultimateCost = math.max(1, GetAbilityCost(_playerMyself.UltimateGroup.GroupAbilityId))
local relativeUltimate = math.floor((currentUltimate / ultimateCost) * 100)
if (relativeUltimate > 100) then
relativeUltimate = 100
end
local currentMagicka, maxMagicka, effectiveMaxMagicka = GetUnitPower("player", POWERTYPE_MAGICKA)
local relativeMagicka = math.floor((currentMagicka / maxMagicka) * 100)
if (relativeMagicka > 100) then
relativeMagicka = 100
end
local currentStamina, maxStamina, effectiveMaxStamina = GetUnitPower("player", POWERTYPE_STAMINA)
local relativeStamina = math.floor((currentStamina / maxStamina) * 100)
if (relativeStamina > 100) then
relativeStamina = 100
end
_playerMyself.RelativeUltimate = relativeUltimate
_playerMyself.RelativeMagicka = relativeMagicka
_playerMyself.RelativeStamina = relativeStamina
elseif (messageType == MESSAGE_PLAYER_DPS) then
if (_playerMyself.DamageDone > 0) then
_playerMyself.DamageToSend = _playerMyself.DamageDone
_playerMyself.DamageDone = _playerMyself.DamageDone - _playerMyself.DamageToSend
else
if (combatPause >= COMBATTIMEOUT) then
_playerMyself.DamageToSend = 0
else
-- Damage not changed
updateNeeded = false
end
end
elseif (messageType == MESSAGE_PLAYER_HPS) then
if (_playerMyself.HealingDone > 0) then
_playerMyself.HealingToSend = _playerMyself.HealingDone
_playerMyself.HealingDone = _playerMyself.HealingDone - _playerMyself.HealingToSend
else
if (combatPause >= COMBATTIMEOUT) then
_playerMyself.HealingToSend = 0
else
-- Healing not changed
updateNeeded = false
end
end
end
return _playerMyself, updateNeeded
end
--[[
UpdatePlayerStatus updates offline, online timedout, combat values and fires TGT_PLAYER_DATA_REFRESH callbacks, if needed
]]--
local function UpdatePlayerStatus()
for i,player in pairs(_groupPlayers) do
-- Update timeout
local isPlayerTimedOut = (GetTimeStamp() - player.LastMapPingTimestamp) >= PLAYERTIMEOUT
if (player.IsPlayerTimedOut ~= isPlayerTimedOut) then
player.IsPlayerTimedOut = isPlayerTimedOut
FireCallbacksAsync(TGT_PLAYER_DATA_REMOTE_CHANGED, player)
end
-- Update Offline/Online
local isOnline = IsUnitOnline(player.PingTag)
if (player.IsPlayerOnline ~= isOnline) then
player.IsPlayerOnline = isOnline
end
-- Update Combat
local isInCombat = IsUnitInCombat(player.PingTag)
if (player.IsPlayerInCombat ~= isInCombat) then
player.IsPlayerInCombat = isInCombat
end
UpdateFoodBuff(player)
local health, maxHealth = GetUnitPower(player.PingTag, POWERTYPE_HEALTH)
UpdatePlayerHealth(player, health, maxHealth) -- Calls update offline player
end
end
--[[
OnTimedUpdate fires TGT_PLAYER_DATA_REFRESH callbacks, if needed
]]--
local function OnTimedUpdate()
-- Only if player is in group
if (GetIsUnitGrouped()) then
local functionTimestamp = GetGameTimeMilliseconds()
UpdatePlayerStatus()
FireCallbacksAsync(TGT_PLAYER_DATA_REFRESH)
-- Send data
if (_settingsHandler.SavedVariables.IsSendingDataActive) then
if (_lastMessageType == nil or _lastMessageType == MESSAGE_PLAYER_HPS) then
TGT_Communicator.SendData(GetMyselfData(MESSAGE_PLAYER_RESOURCES), MESSAGE_PLAYER_RESOURCES)
_lastMessageType = MESSAGE_PLAYER_RESOURCES
elseif (_lastMessageType == nil or _lastMessageType == MESSAGE_PLAYER_RESOURCES) then
local player, updateNeeded = GetMyselfData(MESSAGE_PLAYER_DPS)
if (updateNeeded) then
TGT_Communicator.SendData(player, MESSAGE_PLAYER_DPS)
else
TGT_Communicator.SendData(GetMyselfData(MESSAGE_PLAYER_RESOURCES), MESSAGE_PLAYER_RESOURCES)
end
_lastMessageType = MESSAGE_PLAYER_DPS
elseif (_lastMessageType == nil or _lastMessageType == MESSAGE_PLAYER_DPS) then
local player, updateNeeded = GetMyselfData(MESSAGE_PLAYER_HPS)
if (updateNeeded) then
TGT_Communicator.SendData(player, MESSAGE_PLAYER_HPS)
else
TGT_Communicator.SendData(GetMyselfData(MESSAGE_PLAYER_RESOURCES), MESSAGE_PLAYER_RESOURCES)
end
_lastMessageType = MESSAGE_PLAYER_HPS
end
end
_logger:logTrace("TGT_PlayerHandler -> OnTimedUpdate", GetGameTimeMilliseconds() - functionTimestamp)
end
end
--[[
Called on new data from Communication
]]--
local function MockPlayerData(pingPlayer)
local pingTag = pingPlayer.PingTag
for i = 1, GetGroupSize() do
pingPlayer.PingTag = pingTag .. tostring(i)
if (i <= 4) then
pingPlayer.GroupAbilityPing = 1
elseif (i <= 8) then
pingPlayer.GroupAbilityPing = 6
elseif (i <= 12) then
pingPlayer.GroupAbilityPing = 13
elseif (i <= 16) then
pingPlayer.GroupAbilityPing = 15
elseif (i <= 20) then
pingPlayer.GroupAbilityPing = 25
else
pingPlayer.GroupAbilityPing = 27
end
UpdatePlayerData(pingPlayer)
end
end
--[[
Called on new data from Communication
]]--
local function OnData(pingPlayer)
if (pingPlayer ~= nil) then
local functionTimestamp = GetGameTimeMilliseconds()
if (TGT_MOCKED) then
MockPlayerData(pingPlayer)
else
UpdatePlayerData(pingPlayer)
end
_logger:logTrace("PlayerHandler -> OnData", GetGameTimeMilliseconds() - functionTimestamp)
else
_logger:logError("PlayerHandler -> OnMapPing; Ping invalid ultimateGroup: " .. tostring(ultimateGroup) .. "; relativeUltimate: " .. tostring(relativeUltimate))
end
end
--[[
==============
PUBLIC METHODS
==============
]]--
--[[
GetRemoteGroupPlayers returns internal group players, filtered by remote attribute
]]--
function TGT_PlayerHandler.GetRemoteGroupPlayers()
local remotePlayers = {}
for i,player in pairs(_groupPlayers) do
if (player.LastMapPingTimestamp ~= 0) then
remotePlayers[player.PlayerName] = player
end
end
return remotePlayers
end
--[[
GetGroupPlayers returns internal group players
]]--
function TGT_PlayerHandler.GetGroupPlayers()
return _groupPlayers
end
--[[
SetPlayerSubGroup sets sub group for player and sets in SettingsHandler
]]--
function TGT_PlayerHandler.SetPlayerSubGroup(groupIdentifer, player)
if (_groupPlayers[player.PlayerName] ~= nil) then
_settingsHandler.SavedVariables.PlayerFrameGroups[player.PlayerName] = groupIdentifer
player.GroupIdentifer = groupIdentifer
FireCallbacksAsync(TGT_PLAYER_DATA_SUB_GROUP_CHANGED, player)
else
_logger:logError("PlayerHandler -> SetPlayerSubGroup; Player not in group list", player.PlayerName)
end
end
--[[
Initialize initializes TGT_PlayerHandler
]]--
function TGT_PlayerHandler.Initialize()
_logger = TGT_LOGGER
_playerMyself = GetNewPlayer("player")
_playerMyself.UltimateGroup =
TGT_UltimateGroupHandler.GetUltimateGroupByAbilityId(TGT_SettingsHandler.GetStaticUltimateIDSettings())
-- Register events
EVENT_MANAGER:RegisterForEvent(_name, EVENT_COMBAT_EVENT, OnCombatEvent)
EVENT_MANAGER:RegisterForEvent(_name, EVENT_EFFECT_CHANGED, OnEffectChangedEvent)
EVENT_MANAGER:RegisterForEvent(_name, EVENT_PLAYER_ACTIVATED, OnPlayerActivated)
EVENT_MANAGER:RegisterForEvent(_name, EVENT_POWER_UPDATE, OnPowerUpdate)
EVENT_MANAGER:RegisterForEvent(_name, EVENT_UNIT_ATTRIBUTE_VISUAL_ADDED, OnVisualAdded)
EVENT_MANAGER:RegisterForEvent(_name, EVENT_UNIT_ATTRIBUTE_VISUAL_UPDATED, OnVisualUpdated)
EVENT_MANAGER:RegisterForEvent(_name, EVENT_UNIT_ATTRIBUTE_VISUAL_REMOVED, OnVisualRemoved)
-- Start update timer
EVENT_MANAGER:RegisterForUpdate(_name, REFRESHRATE, OnTimedUpdate)
-- Register callbacks
CALLBACK_MANAGER:RegisterCallback(TGT_MAP_PING_CHANGED, OnData)
CALLBACK_MANAGER:RegisterCallback(TGT_STATIC_ULTIMATE_ID_CHANGED, SetUltimate)
CALLBACK_MANAGER:RegisterCallback(TAO_GROUP_CHANGED, UpdatePlayers)
CALLBACK_MANAGER:RegisterCallback(TAO_UNIT_GROUPED_CHANGED, UpdateGroup)
_logger:logTrace("TGT_PlayerHandler -> Initialized")
end