Terrain generation is very complicated. Some people have spent years, possibly even most of their careers, working on terrain generation for various games.
For overall terrain generation, usually some sort of continuous 2D noise function is used as the starting point. One or more such noise functions with different seeds and scales form the base that then various transform functions work on to generate the final terrain output. This can not only handle terrain tile generation, but also object placement given appropriate transformation functions. The most recent popular example of this is Factorio, but I think RTS games have used it for random maps for decades now.
This sort of purely random terrain can make a good foundation for a large world, but often feels bland and generic. This is why games like the Diablo series prefer to go with bespoke elements that can have random variation and be combined randomly. With this approach a tile set is created, possibly by hand with explicit object positions, that is then used by a layout manager to place them in a logical way. Kind of like a big jigsaw puzzle except where many difference pieces could fit together perfectly in the same place. A good example of this is Diablo II, where after playing it enough you can start to understand the logic used to fit the pieces together, such as some exits always pointing a certain direction relative to the entrance direction.
Then there is parametric generation of an area. This is less a tool to generate massive random terrain, but rather to fill part of a terrain in a stylistic or purposeful way. For example, along a road you might want to place a pavement with trees every few tiles in a city, possibly spawning a bin, walkway or food stand if the stretch is long enough. This approach is used in games like Anno 1800 or City Skylines 2 to generate the clutter to the side of roads.
Although people like William have successfully used continuous 2D noise functions for tech demos in WC3, WC3 is not really well suited for it due to lack of native support/acceleration. As such I would recommend using a combination of the other approachs. It sounds like you might even be trying the parametric generation approach?
Parametric generation can be quite difficult to get setup. A very basic implementation might be a function for each type of generation/fill you want to use, and then either varying the input parameters randomly, or selecting a random function to fill a specific area. How the function is implemented really depends on what you want the fill to be, and each will likely need to be tackled as a separate problem with significant experimentation to get good results. For example, if you want to make a function to spawn a house for a village, you might want to start by filling the floor and making the walls using loops. Then, given the space available, start filling it with clutter. Maybe the house is a big space, so maybe it gets sub-divided into 2 or 4 rooms with doors connecting them. Each of these rooms might select another parametric function to generate the clutter for them, so a bedroom might have bets but a kitchen might have pots and stoves. When the function is called you could supply it with a wall style, such as for elven or human houses, a variable area to work with so it can make different sized houses, possibly a hint for the kind of clutter to use such as "poor" for peasant housing or "rich" for more expensive clutter items to appear. As you make more of such functions, some tasks might be able to be broken out into utility functions that are shared between many such functions.