I have an Attack
component that has a boolean for the active status and a timestamp when I did swing the last time, like a swing timer.
The AttackInputSystem
on the client will check that Attack
component and if the corresponding key is pressed, it either sends a StartAttackAction
or StopAttackAction
depending on the active status of the Attack
component.
The server has an AttackActionSystem
that listens for those action events, and if the target is valid, updates the Attack
component and confirms the action by sending an EntityUpdateEvent
with the updated Attack
component:
Entity player = operator.getPlayer();
Attack attack = player.getComponent(Attack.TYPE);
boolean wantsAttack = evt instanceof StartAttackAction;
if (attack.active() != wantsAttack) {
if (!wantsAttack || validTarget(operator).isPresent()) {
// update attack component
attack = new Attack(wantsAttack, attack.timestamp());
player.setComponent(attack);
EventQueue eventQueue = operator.getEventQueue();
eventQueue.publish(this, new EntityUpdateEvent(player.getId(), attack));
}
}
The server also has an AttackSystem
which will execute the attack by checking the conditions necessary to attack and sending an AttackEvent
:
long now = System.currentTimeMillis();
Entity player = operator.getPlayer();
Attack attack = player.getComponent(Attack.TYPE);
if (attack.active()) {
validTarget(operator).ifPresentOrElse(entity -> {
if (now - attack.timestamp() > WEAPON_SPEED && inRange(player, entity) && isFacing(player, entity)) {
eventQueue.publish(new AttackEvent(player.getId(), entity.getId()));
player.setComponent(new Attack(true, now));
}
}, () -> {
// toggle off auto attack
Entity player = operator.getPlayer();
eventQueue.publish(new EntityUpdateEvent(player.getId(), new Attack(false, now)));
});
}
The AnimationSystem
on the client takes the Attack
component state to animate an 'ATTACK_IDLE' animation and also plays the 'ATTACK' animation on AttackEvent
.
The DamageSystem
is responsible to react on the AttackEvent
by updating the Health
component, optionally creating the DeathEvent
.
I'm not seeing yet how to transfer this into a more generic AbilitySystem
. But it might give a good starting point.