-
Notifications
You must be signed in to change notification settings - Fork 0
/
SlippyCheezeReadItOnce.lua
245 lines (208 loc) · 8.26 KB
/
SlippyCheezeReadItOnce.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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
-- Copyright © 2018 Daniel Pittman <daniel@rimspace.net>
-- See LICENSE for more details.
SlippyCheeze = SlippyCheeze or {}
if not SlippyCheeze.ReadItOnce then
SlippyCheeze.ReadItOnce = {
IS_RELEASE_VERSION = false,
NAME="SlippyCheezeReadItOnce",
DISPLAY_NAME = "|c798BD2ReadItOnce|r",
-- for double-tap bypass of the block
previousBook = {
id = nil,
time = 0,
count = 0,
},
DOUBLE_TAP_TIME = 1000,
-- used for reporting on our background achievement scan
async = nil,
lore = {
added = 0,
scanned = 0,
start = 0,
},
-- seen holds our saved variables, eg, seen books.
seen = {}
}
end
local addon = SlippyCheeze.ReadItOnce
local unpack = unpack
local insert = table.insert
-- reduce consing at runtime in debug message display
local msg_prefix = addon.DISPLAY_NAME..": "
local function msg(fmt, ...)
local args = {}
for n=1, select('#', ...) do
insert(args, tostring(select(n, ...)))
end
d(msg_prefix..zo_strformat(fmt, unpack(args)))
end
-- return bool, have we seen this before. never called before saved variables
-- are loaded and initialized.
function addon:HaveSeenBookBefore(id, title, body)
if type(id) ~= "number" then
msg("ReadItOnce: id is <<1>> (<<2>>)", type(id), id)
return false
end
-- ensure that we index by string, not number, in the table.
-- luacheck: push noredefined
local id = tostring(id)
-- luacheck: pop
local bodyHash = HashString(body)
local record = self.seen[id]
if record then
-- probably have seen it before, but check for changes
if record.id ~= id then
d("ReadItOnce: book id changed from <<1>> to <<2>>", record.id, id)
end
if record.title ~= title then
d("ReadItOnce: book title changed from '<<1>>' to '<<2>>'", record.title, title)
end
if record.bodyHash ~= bodyHash then
d("ReadItOnce: book body changed")
end
-- don't show.
return true
end
-- have not seen, record it, and return that fact
self.seen[id] = {id=id, title=title, bodyHash=bodyHash}
return false
end
-- Called when we want to skip showing a book. Probably going to be very
-- strange if you call it any other time!
function addon:DoNotShowThisBook(title, onlySound)
PlaySound(SOUNDS.NEGATIVE_CLICK)
if not onlySound then
local params = CENTER_SCREEN_ANNOUNCE:CreateMessageParams(CSA_CATEGORY_SMALL_TEXT, nil)
params:SetText(zo_strformat("You have already read \"<<1>>\"", title))
params:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_LORE_BOOK_LEARNED)
params:SetLifespanMS(850)
CENTER_SCREEN_ANNOUNCE:AddMessageWithParams(params)
end
EndInteraction(INTERACTION_BOOK)
end
-- Sadly, we have to override the original method, which is a local anonymous
-- function, and which we have apparently no access to in order to hook nicely.
--
-- The bulk of this is a direct copy-paste from the lore reader, as of USOUI
-- 100023
--
-- The HaveSeenBook logic is my addition.
function addon:OnShowBookOverride(eventCode, title, body, medium, showTitle, bookId)
-- never block a book if we are not in the most basic state, which is the
-- world interaction state.
if not SCENE_MANAGER:IsShowingBaseScene() then
return self:DoNotShowThisBook(title)
end
-- seen before, block unless is double-tap within the limit
if self:HaveSeenBookBefore(bookId, title, body) then
-- different book from the last time?
local sameBook = (self.previousBook.id == bookId)
-- last book was more than our double-tap time ago?
local now = GetGameTimeMilliseconds()
local timeSinceLastTap = (now - self.previousBook.time)
local doubleTap = (timeSinceLastTap <= addon.DOUBLE_TAP_TIME)
-- if not self.IS_RELEASE_VERSION then
-- msg('show-p: sameBook=<<1>> doubleTap=<<2>> count=<<3>> timeSinceLastTap=<<4>>',
-- sameBook, doubleTap, self.previousBook.count, timeSinceLastTap)
-- end
if sameBook then
-- allow a double-tap after a failed double-tap
self.previousBook.time = now
-- remember if we are being real spammy here, but reset that tracker if
-- they give a long enough pause.
if timeSinceLastTap < 3000 then
self.previousBook.count = self.previousBook.count + 1
else
self.previousBook.count = 1
end
if not doubleTap then
-- don't keep on yelling if they spam interact too much, just beep.
local onlySound = (self.previousBook.count > 1)
return self:DoNotShowThisBook(title, onlySound)
end
else
-- otherwise record this state for the future.
self.previousBook.id = bookId
self.previousBook.count = 1
self.previousBook.time = now
-- and block the book.
return self:DoNotShowThisBook(title)
end
end
-- meh, this is copied from the local function in the ZOS code. :(
if LORE_READER:Show(title, body, medium, showTitle) then
PlaySound(LORE_READER.OpenSound)
else
EndInteraction(INTERACTION_BOOK)
end
end
function addon:ScanOneLoreCategory(category)
local _, numCollections, _ = GetLoreCategoryInfo(category)
self.async:For(1, numCollections):Do(function(collection) self:ScanOneLoreCollection(category, collection) end)
end
function addon:ScanOneLoreCollection(category, collection)
local _, _, _, numBooks, _, _, _ = GetLoreCollectionInfo(category, collection)
self.async:For(1, numBooks):Do(function(book) self:ScanOneLoreBook(category, collection, book) end)
end
function addon:ScanOneLoreBook(category, collection, book)
self.lore.scanned = self.lore.scanned + 1
local title, _, known, id = GetLoreBookInfo(category, collection, book)
if known then
local body = ReadLoreBook(category, collection, book)
if not self:HaveSeenBookBefore(id, title, body) then
self.lore.added = self.lore.added + 1
end
end
end
function addon:ReportAfterLoreScan()
if self.lore.added > 0 then
-- ZOS quirk: the number **must** be the third argument. the plural must
-- be a substitution of text.
msg('added <<2>> <<m:1>> found in your achievements.', 'previously read book', self.lore.added)
end
if not self.IS_RELEASE_VERSION then
local duration = FormatTimeMilliseconds(
GetGameTimeMilliseconds() - self.lore.start,
TIME_FORMAT_STYLE_DESCRIPTIVE_MINIMAL_SHOW_TENTHS_SECS,
TIME_FORMAT_PRECISION_TENTHS_RELEVANT,
TIME_FORMAT_DIRECTION_NONE)
msg('SyncFromLoreBooks: scan ran for <<1>>', duration)
end
end
function addon:SyncFromLoreBooks()
if not self.IS_RELEASE_VERSION then
msg('SyncFromLoreBooks: starting lore book scan now')
end
self.async = LibStub("LibAsync"):Create(self.NAME)
self.lore.added = 0
self.lore.scanned = 0
self.lore.start = GetGameTimeMilliseconds()
self.async:For(1, GetNumLoreCategories()):Do(function(category) self:ScanOneLoreCategory(category) end)
self.async:Then(function() self:ReportAfterLoreScan() end)
end
function addon:OnAddonLoaded(name)
if name ~= addon.NAME then return end
EVENT_MANAGER:UnregisterForEvent(addon.NAME, EVENT_ADD_ON_LOADED)
-- if the second argument, the version, changes then the data is wiped and
-- replaced with the defaults.
self.seen = ZO_SavedVars:NewAccountWide("SlippyCheezeReadItOnceData", 1)
-- replace the original event handler with ours; sadly, we don't have
-- access to the original implementation to do anything nicer. :/
LORE_READER.control:UnregisterForEvent(EVENT_SHOW_BOOK)
LORE_READER.control:RegisterForEvent(EVENT_SHOW_BOOK,
function(...) self:OnShowBookOverride(...) end)
-- and once we actually log in, scan the collections for missing records in
-- our data on what we have seen, since this is the only in-game history we
-- can use...
--
-- When the player logs in, we delay for ten seconds, then perform
-- the scan. That gives the system a chance to settle down from all the
-- other addons that want to do something at login as well...
local function SyncFromLoreBooksShim(...)
EVENT_MANAGER:UnregisterForEvent(addon.NAME, EVENT_PLAYER_ACTIVATED)
zo_callLater(function() addon:SyncFromLoreBooks() end, 10000 --[[ 10s an ms --]])
end
EVENT_MANAGER:RegisterForEvent(addon.NAME, EVENT_PLAYER_ACTIVATED, SyncFromLoreBooksShim)
end
-- bootstrapping
EVENT_MANAGER:RegisterForEvent(addon.NAME, EVENT_ADD_ON_LOADED, function(_, name) addon:OnAddonLoaded(name) end)