That sounds similar to how I've been doing things. Is there a playable version or source code somewhere?
My code is spawling, poorly documented and in Python. I do have a build but I'm not ready to post it just yet. I'll try to explain it better...
In essence each monster has an attack_list of a class I've called called 'attack tokens'. Each token has attributes of name, range, value and function. The value is the attack's weighting (higher values mean better attack), the function is a reference of the function to call if this attack is ever used. Each monster keeps track of the highest valued attack in it's list, which will also give it the range it needs to be. When I process monster AI, and determine something wants to be violent, I check to see if its target is within range of its highest valued attack token - if not move closer, if in range I execute the function referred to in the attack token.
The key is to write attack functions that can be used in this way. Abstracting the attacks from the monster AI gives me lots of flexibility. I can add any kind of attack to any monster, and I'm working on tying this to the items as well - picking up a wand of cold will add a high-valued medium-range cold-ray attack token to a creature's attack_list.
A problem I have with this method is to be interchangeable at run time, all attack functions must have the same arguments. So far I have standardised down to two -> passing reference to attacker and target. There are times I want to pass more, but haven't worked out how to abstract that into the attack_token yet.
I've also been musing on incorporating detection conditions as well (which will push this even closer towards state machine, which it was based on initially). So I think that the attack tokens could also have a list of conditions, each is actually a function reference. Those functions return a boolean, and when considering an attack token, you first process each conditional function in its list and if they are all true, execute the attack. This potentially entirely replaces my AI, because I could have a conditional function list containing [(am I standing next to an aggressor?)(am I less than 10% health?)(is my int>10)] with an 'attack' function of (run away!)