Edit: I initially assumed both moving and attacking only happened N/S/E/W, and that your problem was other monsters were failing to approach the pile-up at all.
Now I understand that your attacking range is larger than your movement step, so enemies should prefer navigating to a tile diagonally-adjacent to the player over waiting behind an attacking monster when all 4 adjacent spots are blocked.
New Answer (get to a valid attack location, even if it's diagonal)
As mentioned in a comment, we can fix this with a small modification to A*.
We still invoke A* once, with the goal as the player position. Anytime we evaluate the heuristic, we do it with respect to the player position. That's all the same.
What we change is the acceptance criterion. Rather than returning a path only when currentNode == goal
, we accept a path if:
AttackDistance(currentNode, goal) < attackRange
So if we find a path that gets us to a spot diagonal to the player, within attacking range, job done! Return that path as though we'd reached the goal. Now an entity will get a valid path as long as any space in attacking range is reachable.
You can improve this further with the suggestion below about treating allies as high cost rather than total blockages, so that distant monsters will still approach a player who's completely surrounded.
Another possible improvement would be to cache this path, but not return it right away, to see if a closer option is available. Then we fall back on the initial find only if no closer option is found. This keeps a monster from stopping once it's in range and blocking everyone behind it, and will help for monsters that have ranged abilities but still prefer attacking in melee.
Original answer (solves approaching a blocked target)
Have you considered making the pathfinding treat cells occupied by the monster's allies, not as blocked, but as some exorbitantly high cost?
That way, whenever there is a path that doesn't require moving through a fellow monster, the agent will still prefer such a path.
But if there's absolutely no free path to the player, then the agent will settle for a path that takes it through another monster (on the assumption that this monster is also piling up on the same target, and will eventually die or move).
Since your pathfinding can now return paths with other entities in them, you'll need to make sure your movement logic checks the cell before moving into it, rather than trusting that the path is always free. If it's blocked, the monster can either idle there until a clear path is found, or play some other behaviour (like ranged attacks or circling the mob).
The other common solution to multi-monster swarming like this is to ditch A* and instead use something like Dijkstra's algorithm, working outward from the player and storing the path distance from the player to each cell, counting only terrain obstructions.
While this is more work, now every monster can share this one set of pathfinding data (which may pay off when you have many monsters). On its turn, a monster looks at the open cells around (and under) it, and moves to the one with the shortest stored distance to the player. (Gradient descent)