wuira 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/icons.md ADDED
@@ -0,0 +1,28 @@
1
+ # Forever icon catalog
2
+
3
+ `Icons` contains native texture **FileDataIDs** from the same verified Forever build as the spell enums. Icon identity is independent of spell or item actions.
4
+
5
+ ```ts
6
+ import { UI } from "wuira";
7
+ import { Icons } from "wuira/icons";
8
+ import { Icon } from "wuira/ui";
9
+
10
+ const image = <Icon width={24} height={24} texture={Icons.BlessingOfFreedom_135968} />;
11
+ ```
12
+
13
+ Every icon has an explicit ID-suffixed member. A readable short alias is generated only when that name maps to one icon. Multiple spells or items may share the same texture; they produce one catalog entry with searchable aliases.
14
+
15
+ The snapshot combines SpellMisc spell/active icon fields with Item icon fields and ItemSparse names from the pinned client build. It covers every positive icon ID referenced by these tables, including unused records and entries without a readable name; it does not claim to list every texture or atlas in the game. No other client branch is merged into it. The names describe associated spells or items, rather than guessing asset filenames. Unnamed records receive a `Spell <id>` or `Item <id>` label.
16
+
17
+ The generated enum is erased to numeric literals by TypeScriptToLua. Editor tools can read the packaged `data/icons/forever.json` to create a searchable catalog without constructing every icon in game. A selected texture is saved as a FileDataID, not a spell ID.
18
+
19
+ Game data belongs to Blizzard Entertainment. Source URLs, row counts, build and SHA-256 hashes are recorded in the snapshot.
20
+
21
+ ## Update
22
+
23
+ ```sh
24
+ npm run icons:update -- --build 1.60.1.70374
25
+ npm run icons:generate
26
+ ```
27
+
28
+ Use `--cached` to regenerate the snapshot from downloaded CSVs. Updating requires the build to match `data/spells/forever.json`. Normal consumers use the committed snapshot and do not download game data during a build.
@@ -0,0 +1,27 @@
1
+ # Source converters
2
+
3
+ `SourceConverter` transforms a readable source with a typed callback. It consumes change notifications, shares one upstream subscription across observers, and does not create a timer or dispose the shared input. Sources without notifications remain available as checked one-time reads.
4
+
5
+ ```ts
6
+ import { SourceConverter, HP, ClassSource, Unit } from "wuira/datasources";
7
+
8
+ const hp = new HP(Unit.Player);
9
+ const doubled = new SourceConverter(hp, value => value * 2);
10
+ const characterClass = new ClassSource(Unit.Player);
11
+ const token = new SourceConverter(characterClass, info => info?.token);
12
+ const stop = doubled.BindTo(value => console.log(value));
13
+
14
+ // On removal:
15
+ stop();
16
+ doubled.Dispose();
17
+ token.Dispose();
18
+ characterClass.Dispose();
19
+ ```
20
+
21
+ `Value` and `Compute()` are checked reads. `TryCompute()` performs one checked read and returns `{ readable: false }` for restricted data without invoking the callback. Converter chains use it to read their leaf once, including when the data is restricted.
22
+
23
+ `onChange()` observes future readable output changes; `BindTo()` also applies the current readable value immediately. Identical scalar outputs do not emit again. `When()` filters emissions without changing the checked getter. `Dispose()` releases observers while leaving the input available to other consumers.
24
+
25
+ Restricted values, including nested secret fields, never reach a conversion callback. Restrictions are checked when consumed; combat alone does not mark a value secret. ClassSource subscriptions follow unit identity changes and native events, releasing listeners when the last consumer unsubscribes.
26
+
27
+ The SDK accepts TypeScript callbacks. Editable case rules and math formulas are LumaUI builder factories, implemented using this generic source. Converters produce data; protected button actions remain subject to native combat restrictions.
@@ -1,3 +1,9 @@
1
+ export type ReadResult<T> = {
2
+ readonly readable: true;
3
+ readonly value: T;
4
+ } | {
5
+ readonly readable: false;
6
+ };
1
7
  /** A value-producing source. Secrecy describes data, not native frame protection. */
2
8
  export declare abstract class DataSource<T> {
3
9
  private value;
@@ -8,6 +14,8 @@ export declare abstract class DataSource<T> {
8
14
  IsSecret(): boolean;
9
15
  get Value(): T;
10
16
  Compute<TResult>(compute: (value: T) => TResult): TResult;
17
+ /** One checked read, without exposing a restricted value or invoking its callback. */
18
+ TryCompute<TResult>(compute: (value: T) => TResult): ReadResult<TResult>;
11
19
  protected GetValue(): T;
12
20
  /** Filters native binding emissions; Value remains a checked, fresh snapshot. */
13
21
  When(condition: (value: T) => boolean): this;
@@ -165,6 +165,13 @@ function DataSource.prototype.Compute(self, compute)
165
165
  self:AssertReadable(value)
166
166
  return compute(nil, value)
167
167
  end
168
+ function DataSource.prototype.TryCompute(self, compute)
169
+ local value = self:GetValue()
170
+ return self:IsValueSecret(value) and ({readable = false}) or ({
171
+ readable = true,
172
+ value = compute(nil, value)
173
+ })
174
+ end
168
175
  function DataSource.prototype.GetValue(self)
169
176
  return self.value
170
177
  end
@@ -0,0 +1,30 @@
1
+ import { DataSource } from "./DataSource";
2
+ import type { ReadResult } from "./DataSource";
3
+ /** Structural contract: converters can consume a shared source without owning it. */
4
+ export interface ValueSource<T> {
5
+ readonly Value: T;
6
+ IsSecret(): boolean;
7
+ TryCompute?<TResult>(compute: (value: T) => TResult): ReadResult<TResult>;
8
+ onChange?(listener: (value: T) => void): () => void;
9
+ }
10
+ /** Checked value transformation. No timers, native widgets or ownership of the input. */
11
+ export declare class SourceConverter<TInput, TOutput> extends DataSource<TOutput> {
12
+ private readonly input;
13
+ private readonly convert;
14
+ private readonly listeners;
15
+ private stop?;
16
+ private disposed;
17
+ private blocked;
18
+ private previous?;
19
+ private readable;
20
+ constructor(input: ValueSource<TInput>, convert: (value: TInput) => TOutput);
21
+ protected GetValue(): TOutput;
22
+ private Convert;
23
+ protected HasSecretFields(value: TOutput): boolean;
24
+ /** Future readable changes. The first observer attaches one upstream subscription. */
25
+ onChange(listener: (value: TOutput) => void): () => void;
26
+ private Refresh;
27
+ /** One immediate readable value, followed by event-driven changes when supported. */
28
+ BindTo(apply: (value: TOutput) => void): () => void;
29
+ Dispose(): void;
30
+ }
@@ -0,0 +1,475 @@
1
+ --[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
2
+ -- Lua Library inline imports
3
+ local function __TS__Class(self)
4
+ local c = {prototype = {}}
5
+ c.prototype.__index = c.prototype
6
+ c.prototype.constructor = c
7
+ return c
8
+ end
9
+
10
+ local __TS__Symbol, Symbol
11
+ do
12
+ local symbolMetatable = {__tostring = function(self)
13
+ return ("Symbol(" .. (self.description or "")) .. ")"
14
+ end}
15
+ function __TS__Symbol(description)
16
+ return setmetatable({description = description}, symbolMetatable)
17
+ end
18
+ Symbol = {
19
+ asyncDispose = __TS__Symbol("Symbol.asyncDispose"),
20
+ dispose = __TS__Symbol("Symbol.dispose"),
21
+ iterator = __TS__Symbol("Symbol.iterator"),
22
+ hasInstance = __TS__Symbol("Symbol.hasInstance"),
23
+ species = __TS__Symbol("Symbol.species"),
24
+ toStringTag = __TS__Symbol("Symbol.toStringTag")
25
+ }
26
+ end
27
+
28
+ local __TS__Iterator
29
+ do
30
+ local function iteratorGeneratorStep(self)
31
+ local co = self.____coroutine
32
+ local status, value = coroutine.resume(co)
33
+ if not status then
34
+ error(value, 0)
35
+ end
36
+ if coroutine.status(co) == "dead" then
37
+ return
38
+ end
39
+ return true, value
40
+ end
41
+ local function iteratorIteratorStep(self)
42
+ local result = self:next()
43
+ if result.done then
44
+ return
45
+ end
46
+ return true, result.value
47
+ end
48
+ local function iteratorStringStep(self, index)
49
+ index = index + 1
50
+ if index > #self then
51
+ return
52
+ end
53
+ return index, string.sub(self, index, index)
54
+ end
55
+ function __TS__Iterator(iterable)
56
+ if type(iterable) == "string" then
57
+ return iteratorStringStep, iterable, 0
58
+ elseif iterable.____coroutine ~= nil then
59
+ return iteratorGeneratorStep, iterable
60
+ elseif iterable[Symbol.iterator] then
61
+ local iterator = iterable[Symbol.iterator](iterable)
62
+ return iteratorIteratorStep, iterator
63
+ else
64
+ return ipairs(iterable)
65
+ end
66
+ end
67
+ end
68
+
69
+ local function __TS__New(target, ...)
70
+ local instance = setmetatable({}, target.prototype)
71
+ instance:____constructor(...)
72
+ return instance
73
+ end
74
+
75
+ local Set
76
+ do
77
+ Set = __TS__Class()
78
+ Set.name = "Set"
79
+ function Set.prototype.____constructor(self, values)
80
+ self[Symbol.toStringTag] = "Set"
81
+ self.size = 0
82
+ self.nextKey = {}
83
+ self.previousKey = {}
84
+ if values == nil then
85
+ return
86
+ end
87
+ local iterable = values
88
+ if iterable[Symbol.iterator] then
89
+ local iterator = iterable[Symbol.iterator](iterable)
90
+ while true do
91
+ local result = iterator:next()
92
+ if result.done then
93
+ break
94
+ end
95
+ self:add(result.value)
96
+ end
97
+ else
98
+ local array = values
99
+ for ____, value in ipairs(array) do
100
+ self:add(value)
101
+ end
102
+ end
103
+ end
104
+ function Set.prototype.add(self, value)
105
+ local isNewValue = not self:has(value)
106
+ if isNewValue then
107
+ self.size = self.size + 1
108
+ end
109
+ if self.firstKey == nil then
110
+ self.firstKey = value
111
+ self.lastKey = value
112
+ elseif isNewValue then
113
+ self.nextKey[self.lastKey] = value
114
+ self.previousKey[value] = self.lastKey
115
+ self.lastKey = value
116
+ end
117
+ return self
118
+ end
119
+ function Set.prototype.clear(self)
120
+ self.nextKey = {}
121
+ self.previousKey = {}
122
+ self.firstKey = nil
123
+ self.lastKey = nil
124
+ self.size = 0
125
+ end
126
+ function Set.prototype.delete(self, value)
127
+ local contains = self:has(value)
128
+ if contains then
129
+ self.size = self.size - 1
130
+ local next = self.nextKey[value]
131
+ local previous = self.previousKey[value]
132
+ if next ~= nil and previous ~= nil then
133
+ self.nextKey[previous] = next
134
+ self.previousKey[next] = previous
135
+ elseif next ~= nil then
136
+ self.firstKey = next
137
+ self.previousKey[next] = nil
138
+ elseif previous ~= nil then
139
+ self.lastKey = previous
140
+ self.nextKey[previous] = nil
141
+ else
142
+ self.firstKey = nil
143
+ self.lastKey = nil
144
+ end
145
+ self.nextKey[value] = nil
146
+ self.previousKey[value] = nil
147
+ end
148
+ return contains
149
+ end
150
+ function Set.prototype.forEach(self, callback)
151
+ for ____, key in __TS__Iterator(self:keys()) do
152
+ callback(nil, key, key, self)
153
+ end
154
+ end
155
+ function Set.prototype.has(self, value)
156
+ return self.nextKey[value] ~= nil or self.lastKey == value
157
+ end
158
+ Set.prototype[Symbol.iterator] = function(self)
159
+ return self:values()
160
+ end
161
+ function Set.prototype.entries(self)
162
+ local function getFirstKey()
163
+ return self.firstKey
164
+ end
165
+ local nextKey = self.nextKey
166
+ local key
167
+ local started = false
168
+ return {
169
+ [Symbol.iterator] = function(self)
170
+ return self
171
+ end,
172
+ next = function(self)
173
+ if not started then
174
+ started = true
175
+ key = getFirstKey(nil)
176
+ else
177
+ key = nextKey[key]
178
+ end
179
+ return {done = not key, value = {key, key}}
180
+ end
181
+ }
182
+ end
183
+ function Set.prototype.keys(self)
184
+ local function getFirstKey()
185
+ return self.firstKey
186
+ end
187
+ local nextKey = self.nextKey
188
+ local key
189
+ local started = false
190
+ return {
191
+ [Symbol.iterator] = function(self)
192
+ return self
193
+ end,
194
+ next = function(self)
195
+ if not started then
196
+ started = true
197
+ key = getFirstKey(nil)
198
+ else
199
+ key = nextKey[key]
200
+ end
201
+ return {done = not key, value = key}
202
+ end
203
+ }
204
+ end
205
+ function Set.prototype.values(self)
206
+ local function getFirstKey()
207
+ return self.firstKey
208
+ end
209
+ local nextKey = self.nextKey
210
+ local key
211
+ local started = false
212
+ return {
213
+ [Symbol.iterator] = function(self)
214
+ return self
215
+ end,
216
+ next = function(self)
217
+ if not started then
218
+ started = true
219
+ key = getFirstKey(nil)
220
+ else
221
+ key = nextKey[key]
222
+ end
223
+ return {done = not key, value = key}
224
+ end
225
+ }
226
+ end
227
+ function Set.prototype.union(self, other)
228
+ local result = __TS__New(Set, self)
229
+ for ____, item in __TS__Iterator(other) do
230
+ result:add(item)
231
+ end
232
+ return result
233
+ end
234
+ function Set.prototype.intersection(self, other)
235
+ local result = __TS__New(Set)
236
+ for ____, item in __TS__Iterator(self) do
237
+ if other:has(item) then
238
+ result:add(item)
239
+ end
240
+ end
241
+ return result
242
+ end
243
+ function Set.prototype.difference(self, other)
244
+ local result = __TS__New(Set, self)
245
+ for ____, item in __TS__Iterator(other) do
246
+ result:delete(item)
247
+ end
248
+ return result
249
+ end
250
+ function Set.prototype.symmetricDifference(self, other)
251
+ local result = __TS__New(Set, self)
252
+ for ____, item in __TS__Iterator(other) do
253
+ if self:has(item) then
254
+ result:delete(item)
255
+ else
256
+ result:add(item)
257
+ end
258
+ end
259
+ return result
260
+ end
261
+ function Set.prototype.isSubsetOf(self, other)
262
+ for ____, item in __TS__Iterator(self) do
263
+ if not other:has(item) then
264
+ return false
265
+ end
266
+ end
267
+ return true
268
+ end
269
+ function Set.prototype.isSupersetOf(self, other)
270
+ for ____, item in __TS__Iterator(other) do
271
+ if not self:has(item) then
272
+ return false
273
+ end
274
+ end
275
+ return true
276
+ end
277
+ function Set.prototype.isDisjointFrom(self, other)
278
+ for ____, item in __TS__Iterator(self) do
279
+ if other:has(item) then
280
+ return false
281
+ end
282
+ end
283
+ return true
284
+ end
285
+ Set[Symbol.species] = Set
286
+ end
287
+
288
+ local function __TS__ObjectValues(obj)
289
+ local result = {}
290
+ local len = 0
291
+ for key in pairs(obj) do
292
+ len = len + 1
293
+ result[len] = obj[key]
294
+ end
295
+ return result
296
+ end
297
+
298
+ local function __TS__ClassExtends(target, base)
299
+ target.____super = base
300
+ local staticMetatable = setmetatable({__index = base}, base)
301
+ setmetatable(target, staticMetatable)
302
+ local baseMetatable = getmetatable(base)
303
+ if baseMetatable then
304
+ if type(baseMetatable.__index) == "function" then
305
+ staticMetatable.__index = baseMetatable.__index
306
+ end
307
+ if type(baseMetatable.__newindex) == "function" then
308
+ staticMetatable.__newindex = baseMetatable.__newindex
309
+ end
310
+ end
311
+ setmetatable(target.prototype, base.prototype)
312
+ if type(base.prototype.__index) == "function" then
313
+ target.prototype.__index = base.prototype.__index
314
+ end
315
+ if type(base.prototype.__newindex) == "function" then
316
+ target.prototype.__newindex = base.prototype.__newindex
317
+ end
318
+ if type(base.prototype.__tostring) == "function" then
319
+ target.prototype.__tostring = base.prototype.__tostring
320
+ end
321
+ end
322
+ -- End of Lua Library inline imports
323
+ local ____exports = {}
324
+ local ____DataSource = require("datasources.DataSource")
325
+ local DataSource = ____DataSource.DataSource
326
+ local function restricted(self, value, seen, depth)
327
+ if seen == nil then
328
+ seen = __TS__New(Set)
329
+ end
330
+ if depth == nil then
331
+ depth = 0
332
+ end
333
+ if issecretvalue(value) or depth > 32 then
334
+ return true
335
+ end
336
+ if type(value) ~= "table" or value == nil or seen:has(value) then
337
+ return false
338
+ end
339
+ seen:add(value)
340
+ for ____, child in ipairs(__TS__ObjectValues(value)) do
341
+ if restricted(nil, child, seen, depth + 1) then
342
+ return true
343
+ end
344
+ end
345
+ return false
346
+ end
347
+ --- Checked value transformation. No timers, native widgets or ownership of the input.
348
+ ____exports.SourceConverter = __TS__Class()
349
+ local SourceConverter = ____exports.SourceConverter
350
+ SourceConverter.name = "SourceConverter"
351
+ __TS__ClassExtends(SourceConverter, DataSource)
352
+ function SourceConverter.prototype.____constructor(self, input, convert)
353
+ DataSource.prototype.____constructor(self, nil)
354
+ self.input = input
355
+ self.convert = convert
356
+ self.listeners = __TS__New(Set)
357
+ self.disposed = false
358
+ self.blocked = false
359
+ self.readable = false
360
+ end
361
+ function SourceConverter.prototype.GetValue(self)
362
+ if self.disposed then
363
+ error("SourceConverter is disposed")
364
+ end
365
+ if self.input.TryCompute ~= nil then
366
+ local result = self.input:TryCompute(function(____, value) return self:Convert(value) end)
367
+ if not result.readable then
368
+ self.blocked = true
369
+ return nil
370
+ end
371
+ return result.value
372
+ end
373
+ self.blocked = self.input:IsSecret()
374
+ if self.blocked then
375
+ return nil
376
+ end
377
+ return self:Convert(self.input.Value)
378
+ end
379
+ function SourceConverter.prototype.Convert(self, value)
380
+ self.blocked = restricted(nil, value)
381
+ local ____table_blocked_0
382
+ if self.blocked then
383
+ ____table_blocked_0 = nil
384
+ else
385
+ ____table_blocked_0 = self:convert(value)
386
+ end
387
+ return ____table_blocked_0
388
+ end
389
+ function SourceConverter.prototype.HasSecretFields(self, value)
390
+ return self.blocked or restricted(nil, value)
391
+ end
392
+ function SourceConverter.prototype.onChange(self, listener)
393
+ if self.disposed then
394
+ error("SourceConverter is disposed")
395
+ end
396
+ if self.listeners.size == 0 then
397
+ local value = self:GetValue()
398
+ self.readable = not self:HasSecretFields(value)
399
+ local ____table_readable_1
400
+ if self.readable then
401
+ ____table_readable_1 = value
402
+ else
403
+ ____table_readable_1 = nil
404
+ end
405
+ self.previous = ____table_readable_1
406
+ end
407
+ self.listeners:add(listener)
408
+ if self.listeners.size == 1 then
409
+ do
410
+ local function ____catch(failure)
411
+ self.listeners:delete(listener)
412
+ error(failure, 0)
413
+ end
414
+ local ____try, ____hasReturned = pcall(function()
415
+ local ____this_3
416
+ ____this_3 = self.input
417
+ local ____opt_2 = ____this_3.onChange
418
+ self.stop = ____opt_2 and ____opt_2(
419
+ ____this_3,
420
+ function() return self:Refresh() end
421
+ )
422
+ end)
423
+ if not ____try then
424
+ ____catch(____hasReturned)
425
+ end
426
+ end
427
+ end
428
+ return function()
429
+ self.listeners:delete(listener)
430
+ if self.listeners.size == 0 then
431
+ local ____opt_4 = self.stop
432
+ if ____opt_4 ~= nil then
433
+ ____opt_4(self)
434
+ end
435
+ self.stop = nil
436
+ end
437
+ end
438
+ end
439
+ function SourceConverter.prototype.Refresh(self)
440
+ local value = self:GetValue()
441
+ if self:HasSecretFields(value) then
442
+ self.readable = false
443
+ return
444
+ end
445
+ local same = self.readable and value == self.previous
446
+ self.previous = value
447
+ self.readable = true
448
+ if not same and self:CanEmit(value) then
449
+ for ____, listener in __TS__Iterator(self.listeners) do
450
+ listener(nil, value)
451
+ end
452
+ end
453
+ end
454
+ function SourceConverter.prototype.BindTo(self, apply)
455
+ if not self:IsSecret() then
456
+ local value = self.Value
457
+ if self:CanEmit(value) then
458
+ apply(nil, value)
459
+ end
460
+ end
461
+ return self:onChange(apply)
462
+ end
463
+ function SourceConverter.prototype.Dispose(self)
464
+ if self.disposed then
465
+ return
466
+ end
467
+ self.disposed = true
468
+ local ____opt_6 = self.stop
469
+ if ____opt_6 ~= nil then
470
+ ____opt_6(self)
471
+ end
472
+ self.stop = nil
473
+ self.listeners:clear()
474
+ end
475
+ return ____exports
@@ -8,11 +8,17 @@ export interface ClassInfo {
8
8
  }
9
9
  export declare class ClassSource extends DataSource<ClassInfo | undefined> {
10
10
  private restricted;
11
+ private readonly listeners;
12
+ private events?;
13
+ private stopUnit?;
11
14
  readonly target: UnitSource;
12
15
  constructor(unit: UnitInput);
13
16
  get unit(): string;
14
17
  protected HasSecretFields(): boolean;
15
18
  protected GetValue(): ClassInfo | undefined;
19
+ onChange(listener: (value: ClassInfo | undefined) => void): () => void;
20
+ private StopListening;
21
+ Dispose(): void;
16
22
  }
17
23
  export interface SpecializationInfo {
18
24
  readonly id: number;