The goal of oopsy is to reduce time in understanding why a wipe or a death happened. Most of the time, a death happens because somebody takes damage that they shouldn't have, and so most of the oopsy file is about making these mistakes visible quickly.
A basic oopsy file should cover:
- abilities nobody should be hit by (e.g. a puddle on the ground)
- effects people only get when they make a mistake (e.g. bleed when walking into the edge of the arena)
- damage that should not be shared (e.g. a spread)
- damage that should not be taken alone (e.g. a stack)
Any triggers past that are usually a bonus.
See e12s or TOP for examples of more complicated triggers.
These complicated triggers are things like:
- whose e12s icicle killed who
- whose e12s statue laser killed who
- which e12s lion cleave killed who
- TOP Looper tower mistakes
- TOP Hello World rot mistakes (extra or missing)
The TypeScript type OopsyMistakeType
has all the different types of mistakes that can be made.
See: oopsy.d.ts.
These each correspond with their own icon.
It is very subjective what you assign to each, so don't worry about it too much.
- pull: early pull mistakes
- warn: most mistakes, "you took damage you shouldn't have"
- fail: bad mistakes (subjective), "you took obviously avoidable damage like a tank cleave that probably will kill you" or "you died from something that nobody should like UCOB twisters" or "you took damage that will probably wipe the raid"
- potion: unused, intended for "you used the wrong grade potion"
- death: somebody died
- wipe: the entire party wiped
- damage: a dps mistake, e.g. somebody got a damage down or missed somebody with a raid buff
- heal: a healing mistake, e.g. somebody missed a heal or overwrote a rez
- good: unused, intended for "somebody went out of their way to do something that saved the raid"
Needless to say warn
and fail
are largely subjective. Don't worry about it.
Oopsy files are often very quick to make, but often people just don't bother to do them.
The util/logtools/make_timeline.ts
script has a -la
parameter
that lists all the abilities for an encounter.
If this is filled out, then it becomes very easy to look through all the ability ids
and move the relevant ones into the oopsy file.
As an example, here is the Zeromus Extreme timeline ability table and here is the oopsy file made from that table.
Each file is a module that exports a single oopsy trigger set.
Most entries in the trigger set are simple maps of string ids to ability ids.
Each entry in a file must start with the same prefix (e.g. UCU
in the file)
and must be globally unique across all oopsy trigger sets.
There's nothing magical about any of the damageWarn
or shareFail
categories.
They are only helpers to make it easier to make a trigger from an id,
but ultimately are just triggers themselves.
Please promote any commonly used triggers to be helpers as needed.
import ZoneId from '../path/to/resources/zone_id';
// Other imports here.
export default {
zoneId: ZoneId.TheUnendingCoilOfBahamutUltimate,
zoneLabel: {
en: 'The Unending Coil of Bahamut (Ultimate)',
},
damageWarn: {
'UCU Lunar Dynamo': '26BC',
// ...
},
damageFail: {
'UCU Twister': '26AB',
// ...
},
gainsEffectWarn: {
'UCU Doom': 'D2',
// ...
},
gainsEffectFail: {
'UCU Doom': 'D2',
// ...
},
shareWarn: {
'UCU Megaflare': '26DB',
// ...
},
shareFail: {
'UCU Megaflare': '26DB',
// ...
},
soloWarn: {
'UCU Thermionic Beam': '26BD',
// ...
},
soloFail: {
'UCU Thermionic Beam': '26BD',
// ...
},
triggers: [
{ /* ..trigger 1.. */ },
{ /* ..trigger 2.. */ },
{ /* ..trigger 3.. */ },
],
};
zoneId: A shortened name for the zone to use these triggers in. The set of id names can be found in zone_id.ts. Prefer using this over zoneRegex. A trigger set must have one of zoneId or zoneRegex to specify the zone (but not both).
zoneLabel An optional name to use for this trigger set in the configuration interface. Overrides the zone name from zone_info.ts.
zoneRegex (deprecated): A regular expression that matches against the zone name (coming from ACT). If the regular expression matches, then the triggers will apply to that zone.
damageWarn and damageFail:
An object contains properties like 'trigger id': 'damage action id'
,
which provides an easy way to apply triggers via damage action id (in hex).
When a player was hit by these action,
a message (default to action name) would be shown.
damageWarn shows the message as warn
,
and damageFail shows it as fail
.
gainsEffectWarn and gainsEffectFail: Just like damageWarn and damageFail, but triggered when hit by an effect (id in hex).
shareWarn and shareFail: Just like damageWarn and damageFail, triggered when multiple players share damage which should only be on one player (e.g. spread AoE).
soloWarn and soloFail: The opposite of shareWarn and shareFail in that they are triggered when something that should be shared hits only one person (e.g. stack markers).
triggers: An array of triggers in the trigger set. See below for the format of each of individual triggers.
Each trigger is an object with the following fields. All fields are optional. This parallels the raidboss trigger structure.
id
: a string representing this trigger, for use in disabling triggers. See oopsyraidsy-example.js.condition
: function returning bool for whether or not to run this trigger.netRegex
: a regex matching a network line (such as from theNetRegexes
helper)delaySeconds
: float (or function returning float) for how long to wait before executing this trigger.suppressSeconds
: float (or function returning float) for how long to ignore future matches to this trigger (including additional collection).deathReason
: overrides the reason that a player died if the player dies without taking any more damage. This is for things that kill you without an obvious log line, e.g. forgetting to clear Beyond Death.mistake
: returns a single mistake or an array of mistakes to add to the live list. See below for themistake
format.run
: function that just runs, but does not return anything
type
is the icon: pull, warn, fail, potion, death, wipe (:arrow_forward::warning::no_entry_sign::cocktail::skull::toilet:).name
is an optional full player name to list as this mistake happening to. This will prepend their name in the live list.blame
is an optional full player name to blame for this mistake. Ifname
is not specified, then thename
will be theblame
player.reportId
is an optional player id. If set, it will include this mistake in that player's death report.text
is an optional reason for the mistake. It will be prepended by the blamed player's short name (if it exists). This will print ":no_entry_sign: Latke: Dynamo" in the live log.
mistake: (data, matches) => {
return {
type: 'fail',
blame: matches.target,
reportId: matches.targetId,
text: 'Dynamo'
};
},
id
is the player id to override the death reason for.name
is the full player name to override the next death reason for.reason
is the string to use.
If this following trigger is used, then if a player dies without taking any other damage, the log would show ":skull: Chippy: Doom Debuff" instead of assigning it to the last damage the player took before this trigger, which might incorrectly look more like ":skull: Chippy: Auto (3034/38471)".
deathReason: (data, matches) => {
return {
id: matches.targetId,
name: matches.targetName,
text: 'Doom Debuff',
},
},
Every function in an oopsy trigger gets two parameters: data
and matches
in that order.
Current hp/mp/tp values are not 100% precise. ACT polls these values periodically and so it may be out of date by one HoT/DoT tick. The most important consideration is that damage that does more than current hp may not actually be fatal, and vice versa that damage that does less than current hp may turn out to be fatal. There's no way to know until the 'was defeated' message shows up two seconds later.
{
// 26BB is the ability id for Nael's Iron Chariot.
netRegex: NetRegexes.ability({ id: '26BB' }),
mistake: (_data, matches) => {
// matches here is a single matches object
console.log(matches.target);
},
},
data
is an object that persists for an entire fight and is reset on wipe. It is passed to every function.
data
comes prepopulated with the following fields:
data.me
: string, the player's character name.data.job
: string, the player's job, e.g. WAR.data.role
: string, the role of the player's job: tank, healer, dps-melee, dps-ranged, dps-caster, crafting, gathering.data.inCombat
: bool, whether or not the game thinks the player is in combat. This is different than whether ACT thinks the player is in combat.data.IsPlayerId
: helper function to check if a target or attacker id represents a player (vs a pet or a mob).data.party
: the PartyTracker object, you can use this to check names and roles of players in the party, or to calldata.party.member(name).toString()
to get a shorter nickname or job name.
data
is something that triggers can and should store state on, if state is needed to be tracked across multiple triggers.
For example, if you want to store a map of which players have doom or not, that could be stored in data.hasDoom
. This could then be used across multiple triggers.
{
netRegex: NetRegexes.gainsEffect({ effect: 'Doom' }),
run: (data, matches) => {
data.hasDoom[matches.target] = true;
},
},
matches
is literally the regex match object returned from whatever regex this trigger matched. matches[0]
is always the full match, with other array entries being any other groups from the regex (if any). In the case of the single event above, matches[0] === 'Iron Chariot'
.
However, if matches
has any groups
(which all the Regexes
helper functions do),
then matches will be the groups field directly,
so that you can do things like matches.target
.
The full order of evaluation of functions in a trigger is:
regex
disabled
condition
delaySeconds
- (delay happens here)
suppressSeconds
mistake
deathReason
run
Oopsy has a playback viewer if you want to test it without running content.
It is hosted at https://overlayplugin.github.io/cactbot/ui/oopsyraidsy/oopsy_viewer.html. If you are running locally with the webpack dev server, you can also use it via https://localhost:8080/ui/oopsyraidsy/oopsy_viewer.html.
You can drag a network log file to the the viewer and it will process and show all the mistakes. The troubleshooting guide has information on where to find a network log.
Unfortunately, Oopsy is always a little bit less loved than Raidboss and so has fallen behind on features. There's plenty of work that could be done to make it better if you want to contribute.
Easier tasks:
- create an oopsy
Util
library to collect helper functions like these or these so that it isn't repeated- bonus: add support for a helper for "you took two of these", for cases where somebody is hit by two stacks
- bonus: add support for "this person missed the stack"
- make it possible to do
netRegex: { id: '1234', source: 'Mob' }
instead ofnetRegex: NetRegexes.ability({ etc })
- make it more clear in the log when somebody has died from multiple of the same damage, e.g.
Burst x2
instead of justBurst
Harder tasks:
- make early pulls more exact (they are very wrong, now)
- look into network data and try to figure out what happens when there is an instant death from a death wall so that an OverlayPlugin line can be added for it
- more exact hp tracking by using 0x37 lines (currently it is often very wrong)
- show which buffs are active and total mitigation for damage (this is a lot of work)