The usual answer is that the differences should be designed in data as far as possible. Then you get rid of functions like get_predator_target() and replace them with get_target(enum_predator) or whatever.
Yes, that's what I was thinking too, but then you just end up with a nasty bunch of "if this enum and that target type, check it" or the big switch: switch(enum) case 1 check this, case 2 check that, case 3 check the other one.
So it streamlines the API, but the guts of the code is no cleaner, just rearranged, and actually uglier.
About the cleanest way to do both would be an enum for the API, and then a switch that just calls the original dozen different select_target routines. That keeps the (public) API simple, and keeps each subroutine simple as well. You have to loop through 1 or 2 lists of targets (band members, and everyone else), checking for is_active, is_alive, is or is not some species, state (WILD, TAMED, WARRIOR, COMPANION, FRIENDLY, HOSTILE, etc), relations, range, etc. trying to combine 12 of those into one big loop is gong to be ugly....
Actually, the enum would determine if you even had to check bandmebers or not. And everything checks for active and alive (except predator target carcass for eating). So you could just do a big switch on enum in the middle of the loops to check for WILD, TAMED, predator ai, etc.
So it looks like the choices are a dozen routines with one or two loops and some hard coded checks in the loops. or a single routine with two loops, and a switch in the body of each loop that does checks based on enum, and execution of the band member loop based on the enum. if it wasn't already done 95% the first way, i might do it the second way instead. in the end it boils down to being able to say select_target(predator) instead of select_predator_target(), at the cost of having to refactor a dozen subroutines. If you think about it that way, it doesn't really seem to be worth it.
Similarly, instead of having "pet dogs, wild dogs, wild animals" you have a species value (dog, wolf, mammoth, whatever) and a feralness value (wild, tame) and each animal has a setting for each of those.
That's how it actually works. All entities have a species and state (wild, tamed, etc). Humans also have an NPC type (thief etc). So a wild dog is species 61, state=WILD, and a domesticated dog is species 61, state=TAMED. The 16 general entity types listed above are the result of the combo of species, state, is_avian, AI type, etc. There are actually 50 different species in the game. defending_location and is_quest_critter are additional variables that describe an animal's behavior. So species, state, AI type, is_avian, relations, defending_location, is_quest_critter, and NPC_type are all used to determine exactly what you're dealing with and what behaviors they follow.
That sounds a bit weird - choosing between hunting behavior and grazing is exactly the sort of thing that should be in the decision tree,
Hunt() IS a decision tree! <g>
I discovered you can write a decision tree for a given behavior, and if it triggers no move, you can then call another decision tree to see if it generates a move. So you have hunt AI, graze AI, and stand AI. You try hunting first, if there's nothing to hunt, you call graze. Graze transitions between wander, flock, and migrate at random, with stand as the default behavior when doing none of the above.
Hunt was the original AI for lone predators. Graze was the original AI for herd animals.
So the AI becomes prioritized "try this, then try that", where each behavior you try is a decision tree that may or may not yield a "move" as output, such as "attack", or "wander" or "stand".
It just so happens that for every critter that hunts, the next behavior to try is graze. So if it gets to the end of hunt without triggering an action, it calls graze. Which actually turns the "hunt" behavior into "hunt if you can, graze if you can't" behavior.
Each AI type in the game, of which there are about a dozen, is simply a list of: "if do some behavior, return, else if do some other behavior, return, etc.". With behaviors like "i'm cornered", "i'm taking fire", "threat nearby", "time to hunt", "attack badguys within 100 feet" etc. Actually, they are more of the form: check for some condition, if condition exists, react to condition, else check next highest priority condition to react to.