-
Notifications
You must be signed in to change notification settings - Fork 33
How it Works
This is intended as a proof of concept for how things could work. This is just to show that a better way exists.
The core idea here is to take events, starting with events at as low a level as possible, and emit new events based off those (hence the name of the 'reevent' module).
For example, in everyone's favorite mechanic, Titan Jails, here is how it might look:
- ACT log reader sees a log line
of
21|2021-09-30T19:43:43.1650000-07:00|40016AA2|Titan|2B6C|Rock Throw|10669D22|Some Dude|...
- It emits
a
ACTLogLineEvent("21|2021-09-30T19:43:43.1650000-07:00|40016AA2|Titan|2B6C|Rock Throw|10669D22|Some Dude|...")
- Another event handler will read the ACTLogLineEvent and parse it into a rich object, like:
AbilityUsedEvent(
time = 2021-09-30T19:43:43.1650000-07:00,
caster = Entity(name=Titan, id=40016AA2),
ability = Ability(name=Rock Throw, id=2B6C),
target = Entity(name=Some Dude, id=10669D22)
)
- Then, another event handler, subscribed to
AbilityUsedEvent
, would turn it into a more specific event:
TitanJailEvent(player = Entity(name=Some Dude, id=10669D22))
- Finally, yet another event handler would be subscribed to
TitanJailEvent
, and would put this player into a list. Nothing would happen yet. - Upon receiving two more
TitanJailEvent
s, this handler would then emit one final event:
UnsortedTitanJailsSolvedEvent(players = [Entity(name=some dude, ...), Entity(...), Entity(...)])
- However, we still need to sort the list by whatever priority system we want. We would make something to do that, and then it would emit another event:
FinalTitanJailsSolvedEvent(players = [Entity(name=some dude, ...), Entity(...), Entity(...)])
- Both an automarker plugin, and a personal callout plugin could subscribe to the
FinalTitanJailsSolvedEvent
. Perhaps others too, such as visual auras.
@Scope(Scopes.PULL) // One instance of this class per pull - this functionality doesn't exist yet
public class JailCollector implements EventHandler<AbilityUsedEvent> {
private final List<XivEntity> jailedPlayers = new ArrayList<>();
@Override
public void handle(EventContext context, AbilityUsedEvent event) {
// Check ability ID - we only care about these two
int id = event.getAbility().getId();
if (id != 0x2B6B && id != 0x2B6C) {
return;
}
jailedPlayers.add(event.getTarget());
// Fire off new event if we have exactly 3 events
if (jailedPlayers.size() == 3) {
context.accept(new UnsortedTitanJailsSolvedEvent(new ArrayList<>(jailedPlayers)));
}
}
}
Take a look at JailExampleTest to see how it all fits together.
The code is very readable and understandable. No regex parsing - that's already handled by the time we get
here. We just use event.getAbility().getId()
to check if it's one of the ability IDs we care about, and then we
extract the player out of it. The sorting/prioritization, as well as the actual callout/marking, are completely
de-coupled from the collection logic.
You also avoid a lot of nonsense. hex vs decimal conversion only needs to happen once, and then anything past that can specify IDs in hex or decimal natively. This also sidesteps weird issues of a few hex IDs in log lines being in lowercase while most are upper, as well as the ability to abstract away certain details that are useless 99% of the time (e.g. 21 NetworkAbility vs 22 NetworkAOEAbility).
Another advantage of abstracting away the log lines is that if log line format or fields change in the future, only a single update is needed, rather than potentially every trigger needing an update. Or, if SE changes how a particular ability shows up in the log lines (e.g. headmarker obfuscation), then once again the logic only needs to be updated in a single place.
On top of all that, there's the advantage that you get full IDE auto-completion and much better linting than what you'd get from either Cactbot or Trigg. Plus, I'm the kind of guy that considers "No JS involved" to be a job benefit, so there's that too.