Author Topic: Calculating Monster Drops  (Read 14416 times)

Etinarg

  • Rogueliker
  • ***
  • Posts: 424
  • Karma: +1/-1
  • Idea archivist and game tinkerer.
    • View Profile
    • Gedankenweber Blog (German)
Calculating Monster Drops
« on: March 20, 2014, 01:51:00 PM »
There are many ways to handle treasure and item drops from slain monsters. I've set my corridor so that in my project I want both treasure and item drops, and that the items should fit to the monster but can still be quite random.

Now the problem is, I'm in the early stages of this project, and I want to design for expandability. So the question is, how to organize monster drop calculations/data so that is easy to maintain and still expandable - also expandable "backwards" so that newly added items will appear for old monsters without digging through 563 monster drop entries in some data sheet.

Diablo II (If I remember right) had treasure classes and monsters were assigned one or more treasure classes instead of drop lists. This way a new items could be added to a treasure class and all monster which could drop from that class now also could drop the new item. Problems were the balance between treasure classes with many items and classes with few items, in order to keep some items common and other fairly rare.

Are there better ideas? What do you suggest, how should I go at this?

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: Calculating Monster Drops
« Reply #1 on: March 20, 2014, 03:20:22 PM »
To make something extensible and configurable, I would assign one or more "treasure type" tags to each item type, and one or more "treasure type" tags to each monster type. Then a dying monster can compile a list of all items with a matching treasure type, and choose randomly from them.

To make rarity configurable, you can just assign a frequency value to each item. When compiling the drop list, the relative frequencies would be converted to probabilities.

If even a system like that is hard to balance, then the monster's list of drop types can include a frequency  for each treasure type. The item frequencies and the monster's treasure type frequencies are combined when building the list.

If this is too slow (for instance if you're writing Python and have 10,000 items and 100 treasure types, or if you have 100 monsters dying every frame), then you can precompute each monster's item list.

Etinarg

  • Rogueliker
  • ***
  • Posts: 424
  • Karma: +1/-1
  • Idea archivist and game tinkerer.
    • View Profile
    • Gedankenweber Blog (German)
Re: Calculating Monster Drops
« Reply #2 on: March 20, 2014, 03:37:15 PM »
Tags have the problem that typos often go unnoticed, and an item with a mistyped tag will never drop. But tags are easy and fit in my system fairly seamlessly, so I like the suggestion.

Would you make an easy monster in a hard region of the game give more valuable drops, i.e. base the drops on the region difficulty or on the monster difficulty?

There always was the complaint about "broken sashes" in D2 hell difficultly, but the sash was in the fallen's drop list, even in hell difficulty, because that drop list didn't depend on the difficulty setting. So once it's reasonable, since a fallen still is a very easy monster, but on the other hand, player expected more in the highest difficulty setting.

Endorya

  • Rogueliker
  • ***
  • Posts: 513
  • Karma: +0/-0
  • The non-purist roguelike lover
    • View Profile
    • Email
Re: Calculating Monster Drops
« Reply #3 on: March 20, 2014, 04:49:55 PM »
Is the loot in your game realistic? In the game I'm developing a dead bear will never drop a healing potion or a short sword, at beast you can remove any of its internal organs, its hide or chop off its limbs. As for characters all their possessions (including armor and clothing) will be available for looting. So, when I create a new character the loot is always consisting.

Then I have chests and special places where treasure linger about. For these, I have dedicated classes for each type of treasure. Before I can give you any advice I need to understand if your loot is realistic or not.
« Last Edit: March 20, 2014, 04:51:39 PM by Endorya »
"You are never alone. Death is always near watching you."

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: Calculating Monster Drops
« Reply #4 on: March 20, 2014, 06:14:34 PM »
Tags have the problem that typos often go unnoticed, and an item with a mistyped tag will never drop. But tags are easy and fit in my system fairly seamlessly, so I like the suggestion.

Would you make an easy monster in a hard region of the game give more valuable drops, i.e. base the drops on the region difficulty or on the monster difficulty?

There always was the complaint about "broken sashes" in D2 hell difficultly, but the sash was in the fallen's drop list, even in hell difficulty, because that drop list didn't depend on the difficulty setting. So once it's reasonable, since a fallen still is a very easy monster, but on the other hand, player expected more in the highest difficulty setting.
You can protect against mistypes. Just make a separate list of correct tags and a routine to verify that all items and monsters have tags from that list.

Whether drops change with dungeon level, monster level, both, or neither is a matter of preference. There are arguments to be made for both versions. If drops scale with monster level, then killing a difficult monster in the early game is likely to be very worthwhile. If drops scale with region difficulty, then it becomes profitable to go to difficult regions as soon as possible. If there are easy monsters in difficult regions and they drop valuable objects there, then that may encourage grinding. Some games have a constant item frequency across all regions. That can produce a wide variety of tactical situations if the items are sufficiently varied, but it can also encourage start-scumming.

Etinarg

  • Rogueliker
  • ***
  • Posts: 424
  • Karma: +1/-1
  • Idea archivist and game tinkerer.
    • View Profile
    • Gedankenweber Blog (German)
Re: Calculating Monster Drops
« Reply #5 on: March 20, 2014, 09:35:25 PM »
My first impression is that I like "scale with monster difficulty" better. If a new player manages to kill a difficult monster and gets great rewards for that it seems to be just fair. If they use exploits to do so, it's their decision, or may job as designer to reduce such exploits.

Thanks for all the thoughts and tips! This has been really helpful.

sokol815

  • Rogueliker
  • ***
  • Posts: 85
  • Karma: +0/-0
  • Web Developer by Day, still Web Developer by night
    • View Profile
    • Email
Re: Calculating Monster Drops
« Reply #6 on: March 23, 2014, 03:15:08 PM »
I was working on a drop system that would be both simple to implement and complex in functionality about 2 years ago. I am in the process of implementing this system on my current roguelike. This is what I came up with:
  • itemTypes - tiers of items found in a world.
  • dropClasses - monsters are attached dropclasses. This constructs the drop table they use when they die
  • itemDescriptors - these associate the description types defined in itemTypes with actual effects made in attributes
  • attributes - define what a named attribute does and how it applies to whatever item it is being added to.
if I killed a monster, upon his death, He would tell the itemDrop system that it is time to calculate a drop on his drop-set which happens to be '[rare:5|common:20|NO:100]'. (he could also just have a named dropclass of 'monster', which would just look up the named dropclass in the table below)
Let's say it rolled the rare drop.
It would find the rare itemTypes definition ('rare': '[prefix:2|NO:3] [rare-prefix] [item] [rare-suffix:3|NO:1]') and see that it spawns an item with a 2/5 chance of a prefix, a guaranteed rare-prefix and a 3/4 chance of a rare-suffix at the end. Note: this system also makes it easy to construct the item's name, as you just replace the item-descriptor with the actual chosen description.
it will then roll on the prefix and choose if it has one or not. If it finds it has one, it goes to the itemDescriptors table for prefix and randomly chooses one. It does the same thing for all other possible attributes of the item being spawned.

  • syntax:
  • '[]' a set of possibilities
  • '|' separates parameters in a list of options
  • ':' precedes an occurrence chance (if not present, & in a list, give default chance 1. If by itself, item has 100% chance of having this.)


note: I know this is the design section of the forum, but I think the included data structuring lends itself to understanding just how such a system could be constructed. Not included is all the parsing and connecting things together needed to make the system actually work. If the forum had a hide tag, I would totally use it.



Code: [Select]
jsRL.dropJSON =
{
   'itemTypes':{
      'unique':   '[unique-prefix] [item] [unique-suffix]',
      'artifact': '[artifact-prefix] [item] [artifact-suffix]',
      'rare':     '[prefix:2|NO:3] [rare-prefix] [item] [rare-suffix:3|NO:1]',
      'magic':    '[prefix:1|NO:4] [magic-prefix] [item] [magic-suffix:8|NO:1]',
      'superior': '[prefix] [item]',
      'common':   '[item]',
      'holy':     '[magic-prefix:holy] [item:mace]',
   },
   'dropClasses':{
      'treasure':'[common:55|superior:25|magic:15|rare:5]',
      'monster': '[common:60|superior:25|magic:12|rare:1]',
      'unique':  '[magic:50|rare:30|unique:10|artifact:1|NO:400|<monster>]',
      'holy':    '[holy]'
   },
   'itemDescriptors':{
      'prefix':'hard|swift|flexing|sharpend|sturdy',
      'magic-prefix':'whistling|thundrous|holy',
      'magic-suffix':'of the whale|of doom|of enlightenment|of travail|of striking',
      'rare-prefix':'powerful|king\'s|red dragon',
      'rare-suffix':'of Handheim',
      'unique-prefix':'Orcloth\'s',
      'unique-suffix':'',
      'artifact-prefix':'',
      'artifact-suffix':'',
   },
   'attributes':[
      {
         'name':'hard',
         'effectsWeapon':{'damage':'+2','durability':4},
         'effectsArmor': {'durability':4,'defense':3},
      },{
         'name':'swift',
         'effectsWeapon':{'useSpeedPercentage':.9},
         'effectsArmor': {'weightPercentage':.9},
      },{
         'name':'flexing',
         'effectsWeapon':{'durability':20},
         'effectsArmor': {'durability':10,'defense':-2},
      },{
         'name':'holy',
         'effectsWeapon':{'damageUndeadPercentage':2.5},
         'effectsArmor': {'arcaneResistance':5},
      },{
         'name':'of the whale',
         'effects':{'health':60},
      },
   ]
};

Quendus

  • Rogueliker
  • ***
  • Posts: 447
  • Karma: +0/-0
  • $@ \in \{1,W\} \times \{1,H\}$
    • View Profile
    • Klein Roguelikes
Re: Calculating Monster Drops
« Reply #7 on: March 23, 2014, 03:52:03 PM »
That's a pretty nice system!

Not included is all the parsing and connecting things together needed to make the system actually work. If the forum had a hide tag, I would totally use it.

Why do you need to define your own language (and write your own parser) to store drop chances when you're already using JSON to store the surrounding data? JSON already does all of the parsing - why not do the whole thing in JSON?

Code: [Select]
jsRL.dropJSON =
{
   "itemTypes":{
      "unique":   ["unique-prefix", "item","unique-suffix"],
      "artifact": ["artifact-prefix","item","artifact-suffix"],
      "rare":     [{"prefix":2,"NO":3},"rare-prefix", "item", {"rare-suffix":3,"NO":1}],
      "magic":    [{"prefix":1,"NO":4}, "magic-prefix","item", {"magic-suffix":8,"NO":1}],
      "superior": ["prefix", "item"],
      "common":   ["item"],
      // Does this work? It"s not accounted for in the semantics given
      "holy":     [{"magic-prefix":"holy"}, {"item":"mace"}]
   },
   "dropClasses":{
      "treasure":{"common":55,"superior":25,"magic":15,"rare":5},
      "monster": {"common":60,"superior":25,"magic":12,"rare":1},
      "unique":  {"magic":50,"rare":30,"unique":10,"artifact":1,"NO":400,"<monster>":1},
      "holy":    {"holy":1}
   },
   "itemDescriptors":{
      "prefix":["hard","swift","flexing","sharpened","sturdy"],
      "magic-prefix":["whistling","thunderous","holy"],
      "magic-suffix":["of the whale","of doom","of enlightenment","of travail","of striking"],
      "rare-prefix":["powerful","king's","red dragon"],
      "rare-suffix":"of Handheim",
      "unique-prefix":"Orcloth\"s",
      "unique-suffix":"",
      "artifact-prefix":"",
      "artifact-suffix":"",
      "NO":""
   },
};

sokol815

  • Rogueliker
  • ***
  • Posts: 85
  • Karma: +0/-0
  • Web Developer by Day, still Web Developer by night
    • View Profile
    • Email
Re: Calculating Monster Drops
« Reply #8 on: March 23, 2014, 10:03:48 PM »
That's a pretty nice system!

Not included is all the parsing and connecting things together needed to make the system actually work. If the forum had a hide tag, I would totally use it.

Why do you need to define your own language (and write your own parser) to store drop chances when you're already using JSON to store the surrounding data? JSON already does all of the parsing - why not do the whole thing in JSON?

Code: [Select]
jsRL.dropJSON =
{
   "itemTypes":{
      "unique":   ["unique-prefix", "item","unique-suffix"],
      "artifact": ["artifact-prefix","item","artifact-suffix"],
      "rare":     [{"prefix":2,"NO":3},"rare-prefix", "item", {"rare-suffix":3,"NO":1}],
      "magic":    [{"prefix":1,"NO":4}, "magic-prefix","item", {"magic-suffix":8,"NO":1}],
      "superior": ["prefix", "item"],
      "common":   ["item"],
      // Does this work? It"s not accounted for in the semantics given
      "holy":     [{"magic-prefix":"holy"}, {"item":"mace"}]
   },
   "dropClasses":{
      "treasure":{"common":55,"superior":25,"magic":15,"rare":5},
      "monster": {"common":60,"superior":25,"magic":12,"rare":1},
      "unique":  {"magic":50,"rare":30,"unique":10,"artifact":1,"NO":400,"<monster>":1},
      "holy":    {"holy":1}
   },
   "itemDescriptors":{
      "prefix":["hard","swift","flexing","sharpened","sturdy"],
      "magic-prefix":["whistling","thunderous","holy"],
      "magic-suffix":["of the whale","of doom","of enlightenment","of travail","of striking"],
      "rare-prefix":["powerful","king's","red dragon"],
      "rare-suffix":"of Handheim",
      "unique-prefix":"Orcloth\"s",
      "unique-suffix":"",
      "artifact-prefix":"",
      "artifact-suffix":"",
      "NO":""
   },
};

This definitely has the ability to have all the data encoded in JSON format. The basic way of using these items is that you have to construct some sort of state-machine parsing algorithm. You could have the item come first in an item definition, or it could be last depending on if it only has a suffix or prefix, or even both.

I think some of this data is actually a lot easier to work with if it is not yet in JSON format. See below:

Code: [Select]
'monster': '[common:60|superior:25|magic:12|rare:1]',
'unique':  '[magic:50|rare:30|unique:10|artifact:1|NO:400|<monster>]',

so the intention with this one was that 'unique' can actually reference the 'monster' drop class. It compiles at run-time to pull the existing monster drops and append them to the rest of the 'unique' drop class.

I realized just now that the syntax is not fully complete for what I want to do with the system. Here are the changes:
Code: [Select]
'items':{
'item': '[<cuttingWeapons1>|<bluntWeapons1>|<torsoArmors1>]',
'weapons': '[<cuttingWeapons1>|<bluntWeapons1>]',
'cuttingWeapons1': '[short sword|dagger|two handed sword|katana|handaxe|halbred|pike|crescent moon axe]',
'bluntWeapons1': '[tree branch|club|mace|mallet|hammer|war hammer|staff]',
'torsoArmors1': '[thick wool|leather armor|chain mail|woven armor|studded leather]',
},
'itemTypes':{
'holy':     '[holy] [mace]',
},

so the item associations are created inside the 'items' object. In order to generate a known weapon type (e.g. holy mace) You do something like the itemTypes definition has where holy and mace are both used. This makes the system need to be robust, as it could be searching 3 different sources for what information is actually being asked for. The operation of figuring out what a itemType is requires it to go through a few steps for each bracketed option present there:

  • items - point to only base item types in the actual game, and other items
  • itemTypes - these can point to items, itemDescriptors and attributes. Nothing else
  • dropClasses - these point to itemTypes first, then items second
  • itemDescriptors - point to itemDescriptors or attributes
  • attributes - actual data... these don't point to anything

When the game is initializing, it sets up the system by following the below steps for every [] found:
  • Does the bracket have chevrons inside? < > If so, go and find a matching field as defined by the above search order. replace the chevrons with the given contents. If they are inside a bracket together, take them out of the bracket. This can not be used on itemTypes, so a chevron there would foul the system and throw an error. If it has just run the <> search and it still has <> inside of it, throw an error.
  • turn the set into a probability array (e.g. an array with associated chances for all values). any particular value that doesn't match 'value:number' has a chance of 1

Then it is ready to go, because everything has been compiled into a probablity array, so when you go through generating an item, you basically just throw everything into a cart as it generates, then process it at the end and spit out a shiny new item afterwards.

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
Re: Calculating Monster Drops
« Reply #9 on: June 30, 2014, 04:28:58 AM »
As it happens, I've been implementing treasure (and monster) generation today (Yes, I'm back at work on Neohack, after a 2.5-year hiatus). 

The question of generating treasure for monsters to drop on death didn't come up in that form; the  question instead, as I understood it, is what the killed monsters were equipped with at the time of death.

When monsters are generated, intelligent ones can now be generated with weapons, armor, magic items, money, etc, depending on what they can use and how common I want each category of that stuff to be.  So if the player manages to fireball a Pixie Lancer, he'll probably get a wand (and a burnt Pixie corpse, and a burnt hummingbird corpse, and a slightly damaged 'saddle, size tiny'). 

I think that's the right answer; fold treasure generation for monster drops in with monster generation, and let monsters use their treasure (*ON* the player) until they get killed or use it up.   The plus side is it makes monsters less generic; Pixie lancers get one random wand each, and you probably need to worry more about the one with the Death wand than the one with the Dig wand.  The minus side is it means you need to make wand-using monsters pretty rare unless you want the player to have a huge supply of wands by the midgame. 

It's not implemented yet, but I won't be happy until I can allow at least the intelligent monsters  to pick up and use things off the floor, replacing the stuff they're carrying if that improves their kit.