forked from quisquous/cactbot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoopsyraidsy_config.ts
391 lines (348 loc) · 12.2 KB
/
oopsyraidsy_config.ts
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
import { UnreachableCode } from '../../resources/not_reached';
import UserConfig, { OptionsTemplate, UserFileCallback } from '../../resources/user_config';
import { BaseOptions } from '../../types/data';
import { LooseOopsyTriggerSet, OopsyFileData } from '../../types/oopsy';
import {
CactbotConfigurator,
ConfigLooseOopsyTriggerSet,
ConfigProcessedFile,
ConfigProcessedFileMap,
} from '../config/config';
import { generateBuffTriggerIds } from './buff_map';
import oopsyFileData from './data/oopsy_manifest.txt';
import { OopsyOptions } from './oopsy_options';
const oopsyHelpers: (keyof LooseOopsyTriggerSet)[] = [
'damageWarn',
'damageFail',
'shareWarn',
'shareFail',
'gainsEffectWarn',
'gainsEffectFail',
];
// This could be a checkbox, but it's possible we could add more things here,
// like changing fail->warning or who knows what.
const kTriggerOptions = {
default: {
label: {
en: '✔ Defaults',
de: '✔ Standards',
fr: '✔ Défauts',
ja: '✔ 初期設定',
cn: '✔ 默认设置',
ko: '✔ 기본',
},
},
disabled: {
label: {
en: '❌ Disabled',
de: '❌ Deaktiviert',
fr: '❌ Désactivé',
ja: '❌ 無効',
cn: '❌ 禁用',
ko: '❌ 비활성화',
},
},
};
class OopsyConfigurator {
private base: CactbotConfigurator;
private readonly optionKey = 'oopsyraidsy';
constructor(cactbotConfigurator: CactbotConfigurator) {
this.base = cactbotConfigurator;
}
buildUI(container: HTMLElement, files: OopsyFileData) {
const fileMap = this.processOopsyFiles(files);
const expansionDivs: { [expansion: string]: HTMLElement } = {};
for (const info of Object.values(fileMap)) {
const expansion = info.prefix;
if (!info.triggers || Object.keys(info.triggers).length === 0)
continue;
let expansionDiv = expansionDivs[expansion];
if (!expansionDiv) {
const expansionContainer = document.createElement('div');
expansionContainer.classList.add('trigger-expansion-container', 'collapsed');
container.appendChild(expansionContainer);
const expansionHeader = document.createElement('div');
expansionHeader.classList.add('trigger-expansion-header');
expansionHeader.onclick = () => {
expansionContainer.classList.toggle('collapsed');
};
expansionHeader.innerText = expansion;
expansionContainer.appendChild(expansionHeader);
expansionDiv = expansionDivs[expansion] = expansionContainer;
}
const triggerContainer = document.createElement('div');
triggerContainer.classList.add('trigger-file-container', 'collapsed');
expansionDiv.appendChild(triggerContainer);
const headerDiv = document.createElement('div');
headerDiv.classList.add('trigger-file-header');
headerDiv.onclick = () => {
triggerContainer.classList.toggle('collapsed');
};
const parts = [info.title, info.type, expansion];
for (const part of parts) {
if (!part)
continue;
const partDiv = document.createElement('div');
partDiv.classList.add('trigger-file-header-part');
partDiv.innerText = part;
headerDiv.appendChild(partDiv);
}
triggerContainer.appendChild(headerDiv);
const triggerOptions = document.createElement('div');
triggerOptions.classList.add('trigger-file-options');
triggerContainer.appendChild(triggerOptions);
for (const id of Object.keys(info.triggers ?? {})) {
// Build the trigger label.
const triggerDiv = document.createElement('div');
triggerDiv.innerHTML = id;
triggerDiv.classList.add('trigger');
triggerOptions.appendChild(triggerDiv);
// Container for the right side ui (select boxes, all of the info).
const triggerDetails = document.createElement('div');
triggerDetails.classList.add('trigger-details');
triggerOptions.appendChild(triggerDetails);
triggerDetails.appendChild(this.buildTriggerOptions(id, triggerDiv));
}
}
}
buildTriggerOptions(id: string, labelDiv: HTMLElement): HTMLElement {
const kField = 'Output';
const div = document.createElement('div');
div.classList.add('trigger-options');
const updateLabel = (input: HTMLOptionElement | HTMLSelectElement) => {
if (input.value === 'hidden' || input.value === 'disabled')
labelDiv.classList.add('disabled');
else
labelDiv.classList.remove('disabled');
};
const input = document.createElement('select');
div.appendChild(input);
const selectValue = this.base.getOption(this.optionKey, ['triggers', id, kField], 'default');
for (const [key, value] of Object.entries(kTriggerOptions)) {
const elem = document.createElement('option');
elem.innerHTML = this.base.translate(value.label);
elem.value = key;
elem.selected = key === selectValue;
input.appendChild(elem);
updateLabel(input);
input.onchange = () => {
updateLabel(input);
let value = input.value;
if (value.includes('default'))
value = 'default';
this.base.setOption(this.optionKey, ['triggers', id, kField], input.value);
};
}
return div;
}
processOopsyFiles(files: OopsyFileData): ConfigProcessedFileMap<LooseOopsyTriggerSet> {
const map = this.base.processFiles(files);
// Hackily insert "missed buffs" into the list of triggers.
const generalEntry = map['00-misc-general'];
if (!generalEntry)
throw new UnreachableCode();
const fakeBuffs: ConfigProcessedFile<LooseOopsyTriggerSet> = {
...generalEntry,
fileKey: '00-misc-buffs',
filename: 'buff_map.ts',
title: this.base.translate({
en: 'Missed Buffs',
de: 'Verfehlte Buffs',
fr: 'Buffs manqués',
ja: '欠けバフ',
cn: '遗漏Buff',
ko: '놓친 버프 알림',
}),
triggerSet: {
triggers: generateBuffTriggerIds().map((id) => {
return { id: id };
}),
},
};
map[fakeBuffs.fileKey] = fakeBuffs;
for (const item of Object.values(map)) {
item.triggers = {};
const triggerSet = item.triggerSet;
for (const prop of oopsyHelpers) {
const obj = triggerSet[prop];
if (obj === undefined || obj === null)
continue;
if (typeof obj === 'object') {
for (const id in obj)
item.triggers[id] = { id: id };
}
}
if (!triggerSet.triggers)
continue;
for (const trigger of triggerSet.triggers) {
if (!trigger.id)
continue;
// Skip triggers that just set data, but include triggers that are just ids.
if (trigger.run && !trigger.mistake)
continue;
item.triggers[trigger.id] = trigger;
}
}
return map;
}
}
const templateOptions: OptionsTemplate = {
buildExtraUI: (base, container) => {
const builder = new OopsyConfigurator(base);
builder.buildUI(container, oopsyFileData);
},
processExtraOptions: (baseOptions, savedConfig) => {
// TODO: Rewrite user_config to be templated on option type so that this function knows
// what type of options it is using. Without this, perTriggerAutoConfig is unknown.
const options = baseOptions as OopsyOptions;
const perTriggerAutoConfig = options['PerTriggerAutoConfig'] ??= {};
if (typeof savedConfig !== 'object' || Array.isArray(savedConfig))
return;
const triggers = savedConfig['triggers'];
if (typeof triggers !== 'object' || Array.isArray(triggers))
return;
for (const [id, entry] of Object.entries(triggers)) {
if (typeof entry !== 'object' || Array.isArray(entry))
continue;
const output = entry['Output'];
if (output === undefined)
continue;
perTriggerAutoConfig[id] = {
enabled: output !== 'disabled',
};
}
},
options: [
{
id: 'Debug',
name: {
en: 'Enable debug mode',
de: 'Aktiviere Debugmodus',
fr: 'Activer le mode debug',
ja: 'デバッグモードを有効にする',
cn: '启用调试模式',
ko: '디버그 모드 활성화',
},
type: 'checkbox',
debugOnly: true,
default: false,
},
{
id: 'NumLiveListItemsInCombat',
name: {
en: 'Number of mistakes to show in combat',
de: 'Anzahl der Fehler, die während des Kampfes angezeigt werden',
fr: 'Nombre de fautes à afficher en combat',
ja: '戦闘中に表示するミスをした回数',
cn: '战斗中显示的错误数量',
ko: '전투 중 표시할 실수들의 개수',
},
type: 'integer',
default: 5,
},
{
id: 'MinimumTimeForPullMistake',
name: {
en: 'Minimum time to show early pull (seconds)',
de: 'Minimum Zeit in der Early-Pulls angezeigt werden (in Sekunden)',
fr: 'Durée minimale pour afficher l\'early pull (secondes)',
ja: 'タゲ取るのが早かったら、ミスとして表示する、カウントダウンとの最短時間 (秒)',
cn: '显示提前开怪最小时间 (秒)',
ko: '풀링이 빠르다고 표시 할 최소 시간 (초)',
},
type: 'float',
default: 0.4,
},
{
id: 'TimeToShowDeathReportSeconds',
name: {
en: 'Seconds to show death report on death (0=none)',
de: 'Sekunden um den Todesreport beim Tot anzuzeigen (0=niemals)',
fr: 'Durée d’affichage (en secondes) du rapport de mort (0 = aucun)',
ja: '倒れた時にデスレポートを表示 (0=非表示)',
cn: '死亡时显示死亡报告的秒数 (0=不显示)',
ko: '죽었을 때 사망 보고서를 보여주는 시간(초) (0=비활성화)',
},
type: 'float',
default: 4,
setterFunc: (options, value) => {
let seconds;
if (typeof value === 'string')
seconds = parseFloat(value);
else if (typeof value === 'number')
seconds = value;
else
return;
options['TimeToShowDeathReportMs'] = seconds * 1000;
},
},
{
id: 'DeathReportSide',
name: {
en: 'How to show the death report',
de: 'Wie zeige ich den Todesreport an',
fr: 'Où afficher le rapport de mort',
ja: 'デスレポートの表示方法',
cn: '死亡报告的显示方式',
ko: '사망 보고서 표시 위치',
},
type: 'select',
options: {
en: {
'Left Side': 'left',
'Right Side': 'right',
'❌ Disabled': 'disabled',
},
de: {
'Left Side': 'links',
'Right Side': 'rechts',
'❌ Disabled': 'deaktiviert',
},
fr: {
'Côté gauche': 'gauche',
'Côté droit': 'droite',
'❌ Disabled': 'désactivé',
},
ja: {
'左側': 'left',
'右側': 'right',
'❌ 無効': 'disabled',
},
cn: {
'左侧': 'left',
'右侧': 'right',
'❌ 禁用': 'disabled',
},
ko: {
'왼쪽': 'left',
'오른쪽': 'right',
'❌ 비활성화': 'disabled',
},
},
default: 'left',
},
],
};
const userFileHandler: UserFileCallback = (
name: string,
_files: { [filename: string]: string },
baseOptions: BaseOptions & Partial<OopsyOptions>,
basePath: string,
) => {
// TODO: Rewrite user_config to be templated on option type so that this function knows
// what type of options it is using.
if (!baseOptions.Triggers)
return;
for (const baseTriggerSet of baseOptions.Triggers) {
const set: ConfigLooseOopsyTriggerSet = baseTriggerSet;
// Annotate triggers with where they came from. Note, options is passed in repeatedly
// as multiple sets of user files add triggers, so only process each file once.
if (set.isUserTriggerSet)
continue;
// `filename` here is just cosmetic for better debug printing to make it more clear
// where a trigger or an override is coming from.
set.filename = `${basePath}${name}`;
set.isUserTriggerSet = true;
}
};
UserConfig.registerOptions('oopsyraidsy', templateOptions, userFileHandler);