Architecture – Best way to program classes that can apply and override status effects

Imagine a game like League of Legends.

A player can choose a "class" to play. Your in-game character has equipped the class and defined the class which abilities can be used.

These skills can include things like:

  • damage a single opponent
  • Deal damage that disregards the enemy's defense
  • stun their opponents
  • Move their opponents

What is the best way to code a system like this that is adaptive enough to account for scenarios like this?

  • FOO stuns BAR.
  • BAR has an ability that prevents numbness.
  • FOO will get a backlash if the stun fails.
  • FOO stuns BAR for 20 seconds.
  • BAR activates an ability that halves the duration of a status effect (including this stun).
  • The ability of FOO causes an additional explosion when the stun ends. Which is not just twenty seconds away.
  • FOO is temporarily invincible.
  • BAR has a skill that deals real damage to the enemy and bypasses invincibility.
  • However, FOO has a skill that reduces the total damage done by half.
  • The ability of BAR has the effect of barping half the true damage the opponent has received.
  • FOO stuns BAR.
  • The ability of BAR prevents stunning from taking effect until 2 seconds later.
  • FOO deprives BAR of health while BAR is stunned.

And so on.

My design goal is that new classes can be easily added and removed. I do not want to have to hard-code all these scenarios.

I have considered a messaging / event approach, e.g. for scene 1:

FOO.stun() Calls BAR.onStunned() Calls FOO.onStunAvoided(),

Is that really the best system?

Then I need event classes for every status effect, with properties like.

StunEvent
stunner: Player
stunnee: Player
start: Time
stop: Time

Passing parameters back and forth as follows.

Is there an established solution?