Author Topic: implementing a tweakable power curve + randomized monster races  (Read 7396 times)

sokol815

  • Rogueliker
  • ***
  • Posts: 85
  • Karma: +0/-0
  • Web Developer by Day, still Web Developer by night
    • View Profile
    • Email
implementing a tweakable power curve + randomized monster races
« on: December 17, 2013, 07:27:26 AM »
I apologize for the wall of text (130 lines) that here-follows. This is a very information-packed post.

I am going to present my approach to monster generation/balancing for my current roguelike. I am curious to know how others have approached this same subject, and would love to hear suggestions others have.

my latest project is a javascript roguelike. I have been working on it for about a month now.

I had a couple of very interesting ideas to help me implement the denizens of the fateful dungeons that you would encounter in my game.

I decided I would like to use mathematical progressions like cosine and Fibonacci to create the power curves of monsters on each level.

I am using cosine as a base to calculate how many monster types and if any bosses will be on each level.
I am using Fibonacci as the basis for the power curve.

Monster Strength/Power Curve

I arbitrarily decided that I would have 20 levels in my game.

the cosine function (with some interference) generates a decent curve which allows for easier runs of levels with less varied monsters to be followed by similarly sized runs of levels with larger variety of monsters.
in my 20 levels, the equation I put together creates anywhere from 3 to 7 monster types per level with an added parameter increasing the number of monster types on later levels. any level with more than 5 monster types uses the types beyond 5 as bosses.
My current game is giving me a progression that looks like this:

level numbernumber of monster typesstrengths of monstersAverage strength*boss(es) strength*
157, 8, 9, 11, 149.8
248, 9, 10, 129.75
3311, 12, 1412.33
4412, 13, 15, 1814.5
5515, 17, 20, 25, 3322
6516, 18, 21, 26, 3423
7621, 24, 29, 37, 5032.271
8522, 25, 30, 38, 5133.2
9429, 34, 42, 5540
10430, 35, 43, 5641
11641, 49, 62, 83, 11770.4172
12658, 71, 92, 126, 181105.6270
13659, 72, 93, 127, 182106.6271
14786, 107, 141, 196, 285163429, 662
15687, 108, 142, 197, 286164430
165130, 164, 219, 308, 452254.6
176131, 165, 220, 309, 453255.6686
187200, 255, 344, 488, 721401.61098, 1708
197201, 256, 345, 489, 722402.61099, 1709
207312, 401, 545, 778, 1155638.21765, 2752

* - average strength is off because I am using a probability distribution that results in lower powered enemies being more likely to spawn than higher leveled enemies. e.g. if there were 5 enemies at strengths [7,8,9,11,14] the spawn chances would arbitrarily be [50,40,30,20,10]. This results in 5x as many level 7 enemies vs level 14 enemies

strength is generally about equal to total hitpoints for a given monster. Most other stats scale with strength as well. A strength value can generate a very generic monster, but then monster race and class are applied on-top of that generic monster to create the great variety that will exist.

I use Fibonacci as the basis for strength calculation... every couple of levels or so, I increment to the next Fibonacci as the level's base monster strength. This is visible in the nearly-always pattern of 2 levels with very similarly leveled monsters.

when a level is first created, it is populated with about 20 monsters from the table row. After a player has spent a significant amount of time on a floor, more monsters will begin to generate. These monsters follow different rules. They will instead be spawned from the pool of monsters between that level's average  * .3 to average * 1.3. as a player spends more time on a level (barring death by hunger) the additionally generated monsters creep towards a bias for stronger monsters.


Effects on gameplay & Basic Player mechanics

It becomes necessary for the player to spend some additional time on some levels or return to previous levels in order to keep up with the difficulty curve. I have always thought having to work your way towards more difficulty enemies is a fun part of roguelikes, as you see what the game can throw at you. The PC will have to be particularly powerful to advance through the later stages of the game.

A starting PC may begin with stats equivalent to a monster with strength of around 15 or 25. By the end, they will slay a boss 100 - 150x their original strength.

My intention is to make the PC rely heavily upon their gear to bring them through the final levels. A degree of crafting will be implemented in the game to achieve this balance. Crafting should eventually yield items as-powerful-as generated artifacts. Crafting is utterly useless if crafted items are of no use at the end-game.

Part of keeping the PC on their toes is making it very very difficult for them to max out their resistances. I have 6 damage types in the game (physical,cold,fire,poison,arcane,divine). A resistance of 10 means you take no damage from an attack of that type, 5 = 50%. going above 10 or below 0 makes the player recover from attack types or take extra damage from attack types.

The player shall never be able to become fully immune to physical damage, nor at any time shall the players total resistances cap 45. This will help the player to recognize the importance of tracking damage types from enemies and prepare their gear to counteract that damage.

A player can get a resistance above 10, but it would require a decent amount of gear pushing resistances for that damage type, sacrificing general resistances in the process. e.g. a rainbow or dual resistance item may offer 5 or 6 resistance by itself, but if an item offers 2 or 3 resistance to a single damage type, it is unlikely to have additional resistance modifiers.

Weapons will not be able to offer resistance bonuses, this leaves only 8 pieces of equipment from which to gain resistances (unless double-shielding, which would be 9). Therefore, no item shall ever give more than 1 physical resistance (shields and weapons 0), or more than 6 total resistance. (6 is going to be extremely difficult to achieve, even if it is distributed across all the damage types.)


Monster Races

I have also defined a set of races with accompanying monster types for those races. Each race is actually a base race, then 2 or 3 advanced or upgraded versions of that race.

here is one example:
Code: [Select]
{
'name':['dragon','aeldur dragon','ancient dragon'],
'character':['d','D','D'],
'description':'the {0} is a beast of magic and terrible strength',
'mods':'physRes2,absorb1,life4',
'classes':
[
{
'name':'red {0}',
'mods':'fireRes5,fireAttack4',
'color':'#FF1E32',
},
{
'name':'blue {0}',
'mods':'coldRes5',coldAttack4,
'color':'#539FEE',
},
{
'name':'green {0}',
'mods':'poisonRes5,poisonAttack4',
'color':'#277F2A',
},
{
'name':'white {0}',
'mods':'divineRes5,divineAttack4',
'color':'#CCCCCC',
},
{
'name':'black {0}',
'mods':'arcaneRes5,arcaneAttack4',
'color':'#333333',
}
]
},
{
'name':['goblin','hobgoblin','greater goblin'],
'character':['g','h','G'],
'mods':'',
'description':'{0}s are creatures of the deep, always seeking for riches',
},

NOTE: the '{0}' seen in some of the strings above is a placeholder, to be replaced with the basic name of the creature via string formatting. This allows for flexible enemy naming, while maintaining information about the monster in it's name. e.g. 'red {0}'.format('dragon') will return 'red dragon'

NOTE 2: the goblin family above does not yet have it's classes implemented, but I think you get the picture.

Races are chosen and ordered randomly for each game. Note the race definition at no-point defines a strength for any of the monsters. A different seed will create a different order/advancement of monster races. One game could start with goblins as the weakest enemies and dragons as the strongest, another could place them in completely opposite order.

the method of reusing a race later on as an 'upgraded race' makes for consistency in the game. you will see enemies of similar types, but much greater power.

It is possible that I may add additional parameters that make a race more likely to appear later in the game or earlier in the game based upon my notions of their strength compared to other races.

I have implemented 14 such race-definitions like the one in the code-box above. this results in about 45 different total races (some races have 4 tiers) and 225 total different enemies. Any given game currently uses about 20 different races only, so there will be great variety in which monsters you fight in each play-through. The higher leveled races of each race-definition can only appear if their lower version(s) appeared earlier in the game.

All races have different intrinsic stats that change their relative strengths if they were to be compared to another race at the same monster strength level. Dragons, for instance, tend to have more health, be resistant to damage and heal quickly. goblins, however don't have much of anything good going on for them. so a strength 50 goblin will be weaker than a strength 50 dragon because of intrinsic attributes.

When the game is choosing which monsters belong to which race, it first takes all the generated monster strengths (seen in the table above) and orders them lowest-to-highest into an array of unique values. It will then chop that array into smaller arrays of no more than 5 values where the largest value in the new array is no larger than the smaller * 1.3. This then becomes a single race. The resulting structure is power bands, where a race will always be more powerful than the race before it, but weaker than the race after it.

This organization also works well with the generated monster strengths above, as the levels tend to have mixed races of monsters. (because the values for minimum and maximum strength on a single level usually differ by much more than 30%)

Thanks for reading!

Rickton

  • Rogueliker
  • ***
  • Posts: 215
  • Karma: +0/-0
    • View Profile
    • Weirdfellows
Re: implementing a tweakable power curve + randomized monster races
« Reply #1 on: December 17, 2013, 01:10:54 PM »
Seems interesting and well thought out, I look forward to seeing how it works in play.
One question though, it seems like you're planning on having optimal play involve swapping armor around to get the best resistances for what you're facing. Are you going to have pretty high carry limits to let players carry around different armors? If you can't carry much and would have to backtrack to pick up your other armor just to face one fire-aligned creature, people might not do it.
Creator of the 7DRL Possession: Escape from the Nether Regions
And its sequel, simply titled Possession

IBOL

  • Rogueliker
  • ***
  • Posts: 102
  • Karma: +0/-0
    • View Profile
    • IBOLOGY LLC
    • Email
Re: implementing a tweakable power curve + randomized monster races
« Reply #2 on: January 11, 2014, 08:24:33 PM »
I read through a lot of this, and I love it.
this sounds exactly like the beginning of my development process
find interesting ways to represent systems to provide maximum randomness within a manageable framework (for gameplay's sake)
hope you're still pursuing it.
Randomly Approaching The Infinite Realms.
http://ibology.org/

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: implementing a tweakable power curve + randomized monster races
« Reply #3 on: January 12, 2014, 03:03:14 AM »
I implemented randomly generated monsters in a single-level roguelike with a custom difficulty setting in Mutant Aliens! (Download link in signature). Three sizes of alien were defined and stats like health, damage, speed, running speed, and stamina were randomly chosen from ranges affected by size and difficulty.

The result was that actual difficulty scaled very well with numerical difficulty, but from the perspective of gameplay there were only three or four enemy types: easily dispatched cannon fodder, slow monsters that can be ignored but limit travel options, average speed monsters that can be killed easily by kiting (costing time) or by using big guns (costing ammo), and fast dangerous monsters that have to be killed quickly.

In combination with strict resource limits this made for a good game, but it wouldn't have filled 20 levels. I think procedural monster systems need to consider randomising monster abilities and behaviours too, and factor that into the power assessment function. Your six damage types may be suitable for this, if they have interesting side-effects.

I tried to randomise behaviour by making different monsters rely on sight, sound, smell, or telepathy to find the player, but this wasn't drastic or visible enough to change gameplay much. Mutant Aristocrats!, a deterministic version with fixed monsters and no PCG, improved this by making smell-reliant monsters sniff around in the wrong places sometimes, and by making monsters with much more disparate stats.

Including ranged enemies would also have helped the variety, but would have been very unbalanced because of my decision not to include any means of healing in the game.

guest509

  • Guest
Re: implementing a tweakable power curve + randomized monster races
« Reply #4 on: January 12, 2014, 03:24:51 AM »
Agree Quendus, just randomizing stats isn't enough. You need to randomize specials abilities and have the behavior controller (I use a generic one) be able to use those abilities.

It is incomplete but I'm rating my monsters by: Moves, Hits, Bumps, Shoots, Aggression (behavior modifyer) and Ability (1-5, with behavior modified by existence of the ability). No idea how to rate them by difficulty yet but it will end up being a point system or a list by level.

Not nearly as complex or open to fine tuning as you have above, but I can see where you are going.

I like it.

miki151

  • Rogueliker
  • ***
  • Posts: 264
  • Karma: +0/-0
    • View Profile
Re: implementing a tweakable power curve + randomized monster races
« Reply #5 on: January 13, 2014, 07:52:02 AM »
To estimate monster difficulty if they have many stats and/or equipment you can run a series of duels before the game. For example assume some stats and equipment the player will acquire for each dungeon level. Then the % of wins a monster had with this enemy defines their chance to spawn on that level. If you randomize the fake PC a bit each time, then the distribution will be different every game, which could be interesting.

I also have procedural monsters in KeeperRL, and I randomly generate their names, physiognomy (body size, limbs, wings, if they're humanoid), and stats and some abilities of course. I generate a description of them when the player sees one for the first time. They are kind of like mini-bosses. But somehow they end up being pretty boring anyway. Either the combat system is too simple or they need a lot more randomization, like special attacks and resistances. The goal would be to make the player think up different strategies each time they encounter one.
KeeperRL, Dungeon Keeper in roguelike style:
http://keeperrl.com

TheCreator

  • Rogueliker
  • ***
  • Posts: 370
  • Karma: +0/-0
    • View Profile
    • Fame
    • Email
Re: implementing a tweakable power curve + randomized monster races
« Reply #6 on: January 13, 2014, 11:16:22 AM »
To estimate monster difficulty if they have many stats and/or equipment you can run a series of duels before the game. For example assume some stats and equipment the player will acquire for each dungeon level. Then the % of wins a monster had with this enemy defines their chance to spawn on that level. If you randomize the fake PC a bit each time, then the distribution will be different every game, which could be interesting.

Very interesting idea indeed, although it might be quite a lot of work to implement that (I mean faking PC's actions). I'd rather arrange a duel between two monsters, one of the kind being estimated and one armed with the most advanced AI profile available in the game. But that's just because I don't have a good abstraction of a PC's in my game yet, meaning that I can't really fake it. Never mind. I wonder how the results of such an estimation would compare to a simple estimation based just on monster's attributes (HP, damage, resistances and so on).
Fame (Untitled) - my game. Everything is a roguelike.

miki151

  • Rogueliker
  • ***
  • Posts: 264
  • Karma: +0/-0
    • View Profile
Re: implementing a tweakable power curve + randomized monster races
« Reply #7 on: January 13, 2014, 11:54:21 AM »
I'm sure you can create a monster that's almost identical to the PC and use that. There are still some problems though, like monsters that are powerful, but require a certain (maybe easy) strategy to kill, for example if they are slow.

The best way to approach this would be to take a large number of real played games and just see what killed the PC, and adjust. I guess it's easier to do in a browser game.
KeeperRL, Dungeon Keeper in roguelike style:
http://keeperrl.com

sokol815

  • Rogueliker
  • ***
  • Posts: 85
  • Karma: +0/-0
  • Web Developer by Day, still Web Developer by night
    • View Profile
    • Email
Re: implementing a tweakable power curve + randomized monster races
« Reply #8 on: March 05, 2014, 02:35:42 AM »
it's awesome to see all the ideas you guys have had. I'm still working on this project, it actually has a github home, now:

https://github.com/gregbillings/jsRogue

happy coding!