Personally, unless you plan to have lots of units, I would try using triggers instead of distance/facing calculations since it's a lot easier to visualize and debug. But I wouldn't put them on weapons etc. just as sensors around the characters. Each sensor could have a simple FSM that just sends an event to the AI FSM on the character (e.g. EnemyAhead, EnemyBehind, EnemyToLeft, EnemyInMeleeRange...). The AI FSM can then decide how to react (or not) to the events.
If performance becomes an issue you could always refactor these events to be sent by distance/facing calculations instead, without changing the AI FSM.
The AI FSM has states like Idle, Walking, Running, Attacking, Blocking, Recovering, Fleeing... state changes can be driven by PlayerController FSMs, sensor events, simple logic etc. State changes could also be driven by animation events, to introduce subtle timing to the combat. E.g., in the first moments of a heavy attack the state is Vulnerable, then an animation event switches the state to HeavyAttack.
The basic idea is that you can check the current state of any unit at any time. Then the outcome of any combat between 2 units is determined by each unit's state. HeavyAttack vs Idle = LotsOfDamage, HeavyAttack vs Dodging = FallOver, HeavyAttack vs HeavyAttack = BothFallDown etc.
The HeavyAttack state could use an FSM State Switch to test the state of the target and determine the outcome. The outcome is communicated by sending events to the target. Of course, you can have more fun with events, e.g., an attacked enemy sends a HelpMe event to all friendly units nearby...
Anyway that's one high level strategy, but the devil is always in the details!
I would start simple, get some basic action/reactions working reliably between units using trigger sensors, then build on that. Iterate and make it fun...
Hope that helps some!