-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
LibGlobalCooldown.lua
230 lines (203 loc) · 7.2 KB
/
LibGlobalCooldown.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
local MAJOR_VERSION = "LibGlobalCooldown"
local MINOR_VERSION = 1
local lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
if not lib then
return
end
local CreateFrame = CreateFrame
local C_Timer = C_Timer
local GetCombatRatingBonus = GetCombatRatingBonus
local GetSpellCooldown = GetSpellCooldown
local GetSpellInfo = GetSpellInfo
local GetTime = GetTime
local GetShapeshiftForm = GetShapeshiftForm
local math = math
local select = select
local UnitClass = UnitClass
-- Callbacks and event names
lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib)
lib.GCD_OVER = "GCD_OVER"
lib.GCD_STARTED = "GCD_STARTED"
lib.GCD_DURATIONS_UPDATED = "GCD_DURATIONS_UPDATED"
lib.GCD_PHYS_UPDATED = "GCD_PHYS_UPDATED"
lib.GCD_SPELL_UPDATED = "GCD_SPELL_UPDATED"
-- Containers for state tracking
lib.in_combat = false
lib.current_form = nil
-- Containers for active GCD info
lib.gcd_duration = nil
lib.gcd_started = nil
lib.gcd_expires = nil
-- Containers for predicted GCD info
lib.phys_gcd = 1.5 -- will be modified for feral cats
lib.base_spell_gcd = 1.5
lib.current_spell_gcd = nil
lib.has_bloodlust = false
lib.has_breath_haste = false
lib.current_spell_haste = nil
-- A cast time that will be guaranteed to not benefit from a class's
-- instant cast procs. Defaults to Flash of Light, but if the player is a
-- Paladin we'll change this to rank 1 Frostbolt to avoid AoW instant casts.
lib.cast_time_1500ms_spell_id = 19750
function lib:Fire(event, ...)
self.callbacks:Fire(event, ...)
end
-- Rounds down, e.g. if step=0.1 will round down to 1dp.
local simple_floor = function(num, step)
return floor(num / step) * step
end
-----------------------------------------------------------
-- Funcs to manipulate state and fire events
-----------------------------------------------------------
function lib:calculate_expected_spell_gcd()
-- First try to calc the expected GCD from first principles
local rating_percent_reduction = GetCombatRatingBonus(20)
local spell_gcd = self.base_spell_gcd
local reduction_from_haste_rating = 100 / (100 + rating_percent_reduction)
local current = spell_gcd * reduction_from_haste_rating
if lib.has_bloodlust then
current = current * (1/1.3)
end
if lib.has_breath_haste then
current = current * (1/1.25)
end
-- Flash of Light has a 1.5s cast time so can be used to check
-- for the spell GCD duration, in case we miss any multiplicative buffs.
-- If there are any debuffs decreasing cast speed, this will be wrong,
-- and we'll fallback on the first principles calc.
local cast_time_test = select(4, GetSpellInfo(self.cast_time_1500ms_spell_id))
cast_time_test = cast_time_test or 1500
cast_time_test = cast_time_test / 1000 -- change from ms to s
-- Get the minimum of the 2 methods, which should be the more accurate.
-- print(current, cast_time_test)
current = math.min(current, cast_time_test)
-- The minimum GCD for all classes is 1 second.
if current < 1 then
current = 1.0
end
-- print(string.format("current: %f, old: %f", current, self.current_spell_gcd))
-- print(current, self.current_spell_gcd)
-- Round to 4 decimal places
-- current = simple_floor(current, 0.0001)
-- If has changed, fire relevant triggers.
if current ~= self.current_spell_gcd then
self.current_spell_gcd = current
self:Fire(self.GCD_SPELL_UPDATED, self.current_spell_gcd)
self:Fire(self.GCD_DURATIONS_UPDATED, self.phys_gcd, self.current_spell_gcd)
end
-- print(string.format('spell GCD: %f, phys GCD: %f', current, self.phys_gcd))
end
function lib:release_gcd_lock()
self.gcd_duration = nil
self.gcd_started = nil
self:Fire(self.GCD_OVER)
end
function lib:poll_gcd()
-- Function to be called when we have reason to believe a new GCD may
-- have triggered. Locks out new updates when we're on a GCD.
if self.gcd_duration then
return
end
local time_started, duration_reported = GetSpellCooldown(29515)
if duration_reported == 0 then
return
end
local t = GetTime()
local duration_actual = duration_reported - (t - time_started)
local expires = t + duration_actual
self.gcd_duration = duration_reported
self.gcd_started = time_started
self.gcd_expires = expires
-- print(duration_reported)
self:Fire(self.GCD_STARTED, duration_actual, expires)
C_Timer.After(duration_actual, function() self:release_gcd_lock() end)
end
-----------------------------------------------------------
-- Event handlers
-----------------------------------------------------------
function lib:COMBAT_RATING_UPDATE()
local new_spell_haste = GetCombatRating(CR_HASTE_SPELL)
-- print(new_spell_haste)
if new_spell_haste ~= self.current_spell_haste then
-- print('spell haste changed')
self.current_spell_haste = new_spell_haste
self:calculate_expected_spell_gcd()
end
end
function lib:PLAYER_LOGIN()
-- Populate info on class here.
local class = select(2, UnitClass("player"))
-- print("class says: "..tostring(class))
self.class = class
if self.class == "PALADIN" then
self.cast_time_1500ms_spell_id = 116
end
lib.phys_gcd = 1.5
lib.base_spell_gcd = 1.5
-- Set the phys gcds for special cases i.e. rogue/cat.
if class == "ROGUE" then
lib.phys_gcd = 1
elseif self.class == "DRUID" then
local i = GetShapeshiftForm()
if i == 3 then
lib.phys_gcd = 1
end
self.current_form = i
end
self:calculate_expected_spell_gcd()
self:Fire("GCD_DURATIONS_UPDATED", lib.phys_gcd, lib.current_spell_gcd)
end
-- function lib:PLAYER_REGEN_DISABLED()
-- self.in_combat = true
-- end
-- function lib:PLAYER_REGEN_ENABLED()
-- self.in_combat = false
-- end
function lib:SPELL_UPDATE_COOLDOWN()
self:poll_gcd()
end
function lib:UNIT_AURA()
self:calculate_expected_spell_gcd()
end
function lib:UNIT_SPELLCAST_INTERRUPTED()
if self.gcd_duration then
local time_started, duration_reported = GetSpellCooldown(29515)
if duration_reported == 0 then
self:release_gcd_lock()
end
end
end
function lib:UPDATE_SHAPESHIFT_FORM()
-- Only druids have their physical gcd change.
if self.class ~= "DRUID" then
return
end
local i = GetShapeshiftForm()
if i == 3 then
self.phys_gcd = 1.0
else
self.phys_gcd = 1.5
end
self.current_form = i
self:Fire(self.GCD_PHYS_UPDATED, lib.phys_gcd)
self:Fire(self.GCD_DURATIONS_UPDATED, lib.phys_gcd, lib.current_spell_gcd)
end
function lib:activate()
if not self.frame then
local frame = CreateFrame("Frame")
self.frame = frame
frame:RegisterEvent("PLAYER_LOGIN")
-- frame:RegisterEvent("PLAYER_REGEN_DISABLED")
-- frame:RegisterEvent("PLAYER_REGEN_ENABLED")
frame:RegisterEvent("COMBAT_RATING_UPDATE")
frame:RegisterEvent("SPELL_UPDATE_COOLDOWN")
frame:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
frame:RegisterUnitEvent("UNIT_AURA", "player")
frame:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", "player")
end
self.frame:SetScript("OnEvent", function(_, event, ...)
lib[event](lib, event, ...)
end
)
end
lib:activate()