forked from quisquous/cactbot
-
Notifications
You must be signed in to change notification settings - Fork 45
/
oopsy_common.ts
170 lines (140 loc) · 6.46 KB
/
oopsy_common.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
import { Lang } from '../../resources/languages';
import { LocaleText } from '../../types/trigger';
import { OopsyOptions } from './oopsy_options';
// Fields for net log ability lines.
export const kFieldFlags = 8;
export const kFieldDamage = 9;
// If kFieldFlags is any of these values, then consider field 10/11 as 8/9.
// It appears a little bit that flags come in pairs of values, but it's unclear
// what these mean.
export const kShiftFlagValues = ['3E', '113', '213', '313'];
export const kFlagInstantDeath = '36'; // Always 36 ?
// miss, damage, block, parry, instant death
export const kAttackFlags = ['01', '03', '05', '06', kFlagInstantDeath];
export const kHealFlags = ['04'];
// TODO: should we include damage != 0 here?
export const damageFields = {
flags: `[^|]*(?:${kAttackFlags.join('|')})(?=\\|)`,
};
export const playerTargetFields = {
targetId: '1.......',
};
export const playerDamageFields = {
...playerTargetFields,
...damageFields,
};
/* eslint-disable max-len */
/*
Field 7 Flags:
'0' = meditation, aoe with no targets
damage low bytes:
0x01 = dodge
0x03 = damage
0x05 = blocked damage
0x06 = parried damage
0x?? = instant death
damage modifiers:
0x100 = crit damage
0x200 = direct hit damage
0x300 = crit direct hit damage
heal modifiers:
0x00004 = heal
0x10004 = crit heal
Special cases:
* If flags are 3E, shift 9+10 two over to be 7+8. (why???)
* Plenary indulgence has flags=113/213/313 for stacks, shift two as well.
Damage:
* Left-extend zeroes to 8 chars, e.g. 2934001 => 02934001, or 1000 => 00001000.
* Should be interpreted as 4 bytes (8 chars).
* First two bytes are damage.
* 00004000 mask implies extra damage (and some weird math):
bytes = ABCD, where C = 0x40.
total damage = DA(B-D), as three bytes together interpreted as an integer.
e.g. 424E400F => 0F 42 (4E - 0F = 3F) => 0F423F => 999999
* 00001000 mask implies 0 damage, e.g. hallowed.
Examples:
(1) 18216 damage from Grand Cross Alpha (basic damage)
16:40001333:Neo Exdeath:242B:Grand Cross Alpha:1048638C:Tater Tot:750003:47280000:1C:80242B:0:0:0:0:0:0:0:0:0:0:0:0:36906:41241:5160:5160:880:1000:0.009226365:-7.81128:-1.192093E-07:16043015:17702272:12000:12000:1000:1000:-0.01531982:-19.02808:0:
(2) 82538 damage from Hyperdrive (0x4000 extra damage mask)
15:40024FBA:Kefka:28E8:Hyperdrive:106C1DBA:Okonomi Yaki:750003:426B4001:1C:28E88000:0:0:0:0:0:0:0:0:0:0:0:0:35811:62464:4560:4560:940:1000:-0.1586061:-5.753153:0:30098906:31559062:12000:12000:1000:1000:0.3508911:0.4425049:2.384186E-07:
(3) 22109 damage from Grand Cross Omega (:3E:0: shift, unknown 0x40000 flag)
16:40001333:Neo Exdeath:242D:Grand Cross Omega:1048638C:Tater Tot:3E:0:750003:565D0000:1C:80242D:0:0:0:0:0:0:0:0:0:0:41241:41241:5160:5160:670:1000:-0.3251641:6.526299:1.192093E-07:7560944:17702272:12000:12000:1000:1000:0:19:2.384186E-07:
(4) 15732 crit heal from 3 confession stack Plenary Indulgence (:?13:4C3: shift)
16:10647D2F:Tako Yaki:1D09:Plenary Indulgence:106DD019:Okonomi Yaki:313:4C3:10004:3D74:0:0:0:0:0:0:0:0:0:0:0:0:7124:40265:14400:9192:1000:1000:-10.78815:11.94781:0:11343:40029:19652:16451:1000:1000:6.336648:7.710004:0:
(5) instant death twister
16:40004D5D:Twintania:26AB:Twister:10573FDC:Tini Poutini:33:0:1C:26AB8000:0:0:0:0:0:0:0:0:0:0:0:0:43985:43985:5760:5760:910:1000:-8.42179:9.49251:-1.192093E-07:57250:57250:0:0:1000:1000:-8.565645:10.20959:0:
(6) zero damage targetless aoe (E0000000 target)
16:103AAEE4:Potato Chippy:B1:Miasma II:E0000000::0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0::::::::::19400:40287:17649:17633:1000:1000:-0.656189:-3.799561:-5.960464E-08:
*/
/* eslint-enable */
export const Translate = (lang: Lang, obj?: LocaleText | string): string | undefined => {
if (typeof obj !== 'object')
return obj;
return obj[lang] ?? obj['en'];
};
export const GetFormattedTime = (baseTime: number | undefined, time: number): string => {
if (!baseTime)
return '';
const totalSeconds = Math.floor((time - baseTime) / 1000);
const seconds = totalSeconds % 60;
const minutes = Math.floor(totalSeconds / 60);
return `${minutes}:${seconds < 10 ? `0${seconds}` : seconds}`;
};
// Turns a scrambled string damage field into an integer.
// Since fields are modified in place right now, this does nothing if called
// again with an integer. This is kind of a hack, sorry.
export const UnscrambleDamage = (field?: string): number => {
if (field === undefined)
return 0;
const len = field.length;
if (len <= 4)
return 0;
// Get the left two bytes as damage.
let damage = parseInt(field.slice(0, len - 4), 16);
// Check for third byte == 0x40.
if (field[len - 4] === '4') {
// Wrap in the 4th byte as extra damage. See notes above.
const rightDamage = parseInt(field.slice(len - 2, len), 16);
damage = damage - rightDamage + (rightDamage << 16);
}
return damage;
};
export const IsPlayerId = (id?: string): boolean => {
if (id === undefined)
return false;
const firstChar = id[0];
return firstChar !== undefined ? firstChar < '4' : false;
};
export const IsTriggerEnabled = (options: OopsyOptions, id: string): boolean => {
if (id in options.DisabledTriggers)
return false;
const autoConfig = options.PerTriggerAutoConfig[id];
if (autoConfig)
return autoConfig.enabled;
return true;
};
export const GetSoloMistakeText = (ability: string | LocaleText): LocaleText => {
const localeText: LocaleText = typeof ability === 'string' ? { en: ability } : ability;
return {
en: `${localeText['en']} (alone)`,
de: `${localeText['de'] ?? localeText['en']} (allein)`,
fr: `${localeText['fr'] ?? localeText['en']} (seul(e))`,
ja: `${localeText['ja'] ?? localeText['en']} (一人)`,
cn: `${localeText['cn'] ?? localeText['en']} (单吃)`,
ko: `${localeText['ko'] ?? localeText['en']} (혼자 맞음)`,
};
};
export const GetShareMistakeText = (
ability: string | LocaleText,
numTargets: number,
): LocaleText => {
const localeText: LocaleText = typeof ability === 'string' ? { en: ability } : ability;
return {
en: `${localeText['en']} (share x${numTargets})`,
de: `${localeText['de'] ?? localeText['en']} (geteilt mit ${numTargets})`,
fr: `${localeText['fr'] ?? localeText['en']} (partage x${numTargets})`,
ja: `${localeText['ja'] ?? localeText['en']} (頭割り: ${numTargets}人)`,
cn: `${localeText['cn'] ?? localeText['en']} (重叠: ${numTargets}次)`,
ko: `${localeText['ko'] ?? localeText['en']} (같이 맞음: ${numTargets}명)`,
};
};