local PSBT_Module = PSBT_Module local PSBT_Combat = PSBT_Module:Subclass() PSBT_Combat._iconRegistry = setmetatable( {}, { __mode = 'kv' } ) local CBM = CALLBACK_MANAGER local MAX_EVENTS = 15 local PlayerName = GetUnitName( 'player' ) local PlayerNameRaw = GetRawUnitName( 'player' ) local COMBAT_UNIT_TYPE_PLAYER = COMBAT_UNIT_TYPE_PLAYER local COMBAT_UNIT_TYPE_PLAYER_PET = COMBAT_UNIT_TYPE_PLAYER_PET local COMBAT_UNIT_TYPE_NONE = COMBAT_UNIT_TYPE_NONE local PSBT_AREAS = PSBT_AREAS local PSBT_EVENTS = PSBT_EVENTS local zo_strformat = zo_strformat local GetString = GetString local select = select local kVerison = 1.0 local function IsPlayerType( targetType ) return targetType == COMBAT_UNIT_TYPE_PLAYER or targetType == COMBAT_UNIT_TYPE_PLAYER_PET end local function IsPlayer( targetType, targetName ) if ( IsPlayerType( targetType ) ) then return true end if ( targetType == COMBAT_UNIT_TYPE_NONE ) then return targetName == PlayerName or targetName == PlayerNameRaw end return false end local combat_events = { [ ACTION_RESULT_ABSORBED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Absorbed <<1>>', abilityName ), area, false end, [ ACTION_RESULT_BLADETURN ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Blocked <<1>>', abilityName ), area, false end, [ ACTION_RESULT_BLOCKED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Blocked <<1>>', abilityName ), area, false end, [ ACTION_RESULT_BLOCKED_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Blocked <<1>>', hitValue ), area, false end, [ ACTION_RESULT_CANT_SEE_TARGET ] = function( ... ) return 'Can\'t See Target!', PSBT_AREAS.STATIC, true end, [ ACTION_RESULT_CRITICAL_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( '<<1>>!', hitValue ), area, true end, [ ACTION_RESULT_CRITICAL_HEAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( '+<<1>>!', hitValue ), area, true end, [ ACTION_RESULT_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType ) local area = nil local format = '<<1>>' if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING format = '-' .. format elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( format, hitValue ), area, false end, [ ACTION_RESULT_DAMAGE_SHIELDED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Shielded <<1>>', hitValue ), area, false end, [ ACTION_RESULT_DEFENDED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType ) local area = nil if ( IsPlayer( targetType ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Defended <<1>>', hitValue ), area, false end, [ ACTION_RESULT_DOT_TICK ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType ) local area = nil local format = '<<1>>' if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING format = '-' .. format elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( format, hitValue ), area, false end, [ ACTION_RESULT_DOT_TICK_CRITICAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue, damageType ) local area = nil local format = '<<1>>!' if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING format = '-' .. format elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( format, hitValue ), area, true end, [ ACTION_RESULT_HEAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( '+<<1>>' , hitValue ), area, false end, [ ACTION_RESULT_HOT_TICK ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( '+<<1>>', hitValue ), area, false end, [ ACTION_RESULT_HOT_TICK_CRITICAL ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( '+<<1>>!', hitValue ), area, true end, [ ACTION_RESULT_DODGED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Dodged <<1>>', abilityName ), area, false end, [ ACTION_RESULT_MISS ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return 'Miss!', area, false end, [ ACTION_RESULT_PARRIED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Parried <<1>>!', abilityName ), area, false end, [ ACTION_RESULT_RESIST ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Resisted <<1>>!', abilityName ), area, false end, [ ACTION_RESULT_PARTIAL_RESIST ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then area = PSBT_AREAS.OUTGOING end return zo_strformat( 'Partially Resisted <<1>>!', abilityName ), nil, false end, [ ACTION_RESULT_FALL_DAMAGE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local area = nil if ( IsPlayer( targetType, targetName ) ) then area = PSBT_AREAS.INCOMING elseif ( IsPlayer( sourceType, sourceName ) ) then return nil, nil, false end return zo_strformat( '-<<1>> falling', hitValue ), area, false end, [ ACTION_RESULT_KILLING_BLOW ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType ) if ( IsPlayer( targetType, targetName ) ) then return 'Looks like you\'re dead.', PSBT_AREAS.STATIC, true elseif ( IsPlayer( sourceType, sourceName ) ) then return zo_strformat( 'Killing Blow |cCC7D5E<<1>>|r!', targetName ), PSBT_AREAS.STATIC, true end return nil, nil, false end, [ ACTION_RESULT_POWER_DRAIN ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local mechanicName = GetString( 'SI_COMBATMECHANICTYPE', mechanicValue ) return zo_strformat( '-<<1>> (<<2>>)', hitValue, mechanicName ), PSBT_AREAS.OUTGOING, false end, [ ACTION_RESULT_POWER_ENERGIZE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) local mechanicName = GetString( 'SI_COMBATMECHANICTYPE', mechanicValue ) return zo_strformat( '+<<1>> (<<2>>)', hitValue, mechanicName ), PSBT_AREAS.INCOMING, false end, --[[[ ACTION_RESULT_BAD_TARGET ] = function( ... ) return 'Bad Target', PSBT_AREAS.STATIC, true end,]] [ ACTION_RESULT_CANNOT_USE ] = function( ... ) return 'Cannot Use', PSBT_AREAS.STATIC, true end, [ ACTION_RESULT_BUSY ] = function( ... ) return 'Busy', PSBT_AREAS.STATIC, true end, [ ACTION_RESULT_FALLING ] = function( ... ) return 'You\'re falling', PSBT_AREAS.STATIC, true end, [ ACTION_RESULT_DISORIENTED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) if ( IsPlayer( targetType, targetName ) ) then return 'Disoriented!', PSBT_AREAS.INCOMING, true elseif ( IsPlayer( sourceType, sourceName ) ) then return 'Disoriented!', PSBT_AREAS.OUTGOING, true end return nil, nil, false end, [ ACTION_RESULT_DISARMED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) if ( IsPlayer( targetType, targetName ) ) then return 'Disarmed!', PSBT_AREAS.OUTGOING, true elseif ( IsPlayer( sourceType, sourceName ) ) then return 'Disarmed!', PSBT_AREAS.INCOMING, true end return nil, nil, false end, [ ACTION_RESULT_FEARED ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) if ( IsPlayer( targetType, targetName ) ) then return 'Feared!', PSBT_AREAS.INCOMING, true elseif ( IsPlayer( sourceType, sourceName ) ) then return 'Feared!', PSBT_AREAS.OUTGOING, true end return nil, nil, false end, [ ACTION_RESULT_IMMUNE ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) if ( IsPlayer( targetType, targetName ) ) then return 'Immune!', PSBT_AREAS.INCOMING, true elseif ( IsPlayer( sourceType, sourceName ) ) then return 'Immune!', PSBT_AREAS.OUTGOING, true end return nil, nil, false end, [ ACTION_RESULT_INTERRUPT ] = function( abilityName, abilityGraphic, abilityActionSlotType, sourceName, sourceType, targetName, targetType, hitValue, mechanicValue ) if ( IsPlayer( targetType, targetName ) ) then return 'Interrupt!', PSBT_AREAS.INCOMING, true elseif ( IsPlayer( sourceType, sourceName ) ) then return 'Interrupt!', PSBT_AREAS.OUTGOING, true end return nil, nil, false end, --ACTION_RESULT_BEGIN --ACTION_RESULT_ABILITY_ON_COOLDOWN --ACTION_RESULT_BEGIN_CHANNEL --ACTION_RESULT_CASTER_DEAD --ACTION_RESULT_COMPLETE --ACTION_RESULT_DEBUFF --ACTION_RESULT_DIED --ACTION_RESULT_DIED_XP --ACTION_RESULT_EFFECT_FADED --ACTION_RESULT_EFFECT_GAINED --ACTION_RESULT_EFFECT_GAINED_DURATION --ACTION_RESULT_FAILED --ACTION_RESULT_FAILED_REQUIREMENTS --ACTION_RESULT_FAILED_SIEGE_CREATION_REQUIREMENTS --ACTION_RESULT_GRAVEYARD_DISALLOWED_IN_INSTANCE --ACTION_RESULT_GRAVEYARD_TOO_CLOSE --ACTION_RESULT_INSUFFICIENT_RESOURCE --ACTION_RESULT_INTERCEPTED --ACTION_RESULT_INTERRUPT --ACTION_RESULT_INVALID --ACTION_RESULT_INVALID_FIXTURE --ACTION_RESULT_INVALID_TERRAIN --ACTION_RESULT_IN_AIR --ACTION_RESULT_IN_COMBAT --ACTION_RESULT_IN_ENEMY_KEEP --ACTION_RESULT_LEVITATED --ACTION_RESULT_LINKED_CAST --ACTION_RESULT_MISSING_EMPTY_SOUL_GEM --ACTION_RESULT_MISSING_FILLED_SOUL_GEM --ACTION_RESULT_MOUNTED --ACTION_RESULT_MUST_BE_IN_OWN_KEEP --ACTION_RESULT_NOT_ENOUGH_INVENTORY_SPACE --ACTION_RESULT_NOT_ENOUGH_SPACE_FOR_SIEGE --ACTION_RESULT_NO_LOCATION_FOUND --ACTION_RESULT_NO_RAM_ATTACKABLE_TARGET_WITHIN_RANGE --ACTION_RESULT_NPC_TOO_CLOSE --ACTION_RESULT_OFFBALANCE --ACTION_RESULT_PACIFIED --ACTION_RESULT_PRECISE_DAMAGE --ACTION_RESULT_QUEUED --ACTION_RESULT_RAM_ATTACKABLE_TARGETS_ALL_DESTROYED --ACTION_RESULT_RAM_ATTACKABLE_TARGETS_ALL_OCCUPIED --ACTION_RESULT_REFLECTED --ACTION_RESULT_REINCARNATING --ACTION_RESULT_ROOTED --ACTION_RESULT_SIEGE_LIMIT --ACTION_RESULT_SIEGE_TOO_CLOSE --ACTION_RESULT_STAGGERED --ACTION_RESULT_STUNNED --ACTION_RESULT_SWIMMING --ACTION_RESULT_TARGET_DEAD --ACTION_RESULT_TARGET_NOT_IN_VIEW --ACTION_RESULT_TARGET_NOT_PVP_FLAGGED --ACTION_RESULT_TARGET_OUT_OF_RANGE --ACTION_RESULT_TARGET_TOO_CLOSE --ACTION_RESULT_UNEVEN_TERRAIN --ACTION_RESULT_WEAPONSWAP --ACTION_RESULT_WRECKING_DAMAGE --ACTION_RESULT_WRONG_WEAPON2 } function PSBT_Combat:Initialize( ... ) PSBT_Module.Initialize( self, ... ) self._buffer = ZO_CircularBuffer:New( DEFAULT_MAX_BUFFERED_EVENTS ) self._index = 1 self._free = nil self:RegisterForEvent( EVENT_COMBAT_EVENT, function( ... ) self:OnCombatEvent( ... ) end ) self:RegisterForEvent( EVENT_SKILLS_FULL_UPDATE, function() self:RefreshAbilityIcons() end ) self:RegisterForEvent( EVENT_SKILL_POINTS_CHANGED, function() self:RefreshAbilityIcons() end ) self:RefreshAbilityIcons() end function PSBT_Combat:RefreshAbilityIcons() for i=1,GetNumAbilities() do local name, icon = GetAbilityInfoByIndex( i ) self._iconRegistry[ name ] = icon end end function PSBT_Combat:OnCombatEvent( ... ) local result = select( 1, ... ) if ( not combat_events[ result ] ) then return end local args = { ... } -- did we get hit or do something? if ( IsPlayer( args[ 7 ], args[ 6 ] ) or IsPlayer( args[ 9 ], args[ 8 ] ) ) then if ( self._free ) then local argCount = #args for i = 1, argCount do self._free[ i ] = args[ i ] end for i = #self._free, argCount + 1, -1 do self._free[ i ] = nil end self._free = self._buffer:Add( self._free ) else self._free = self._buffer:Add( args ) end self._index = self._index - 1 end end function PSBT_Combat:OnUpdate( frameTime ) if ( self._index <= 0 ) then self._index = 1 end local bufferSize = self._buffer:Size() local endPoint = zo_min( self._index + MAX_EVENTS, bufferSize ) for i = self._index, endPoint do local entry = self._buffer:At( i ) if ( entry ) then self:DispatchEvent( unpack( entry ) ) end end if ( endPoint >= bufferSize ) then self._buffer:Clear() else self._index = endPoint + 1 end end --integer result, bool isError, string abilityName, integer abilityGraphic, integer abilityActionSlotType, string sourceName, integer sourceType, string targetName, integer targetType, integer hitValue, integer powerType, integer damageType, bool log function PSBT_Combat:DispatchEvent( result, _, ... ) local func = combat_events[ result ] local text, area, crit = func( ... ) local icon = self._iconRegistry[ select( 1, ... ) ] self:NewEvent( area, crit, icon, text ) end CBM:RegisterCallback( PSBT_EVENTS.LOADED, function( psbt ) psbt:RegisterModule( PSBT_MODULES.COMBAT, PSBT_Combat:New( psbt ), kVerison ) end)