Declan Hopkins

software & game developer

[11] Drop Tables

11/03/2019, 04:55pm

This weekend I implemented a drop table system to handle monster loot. I wanted something that was easy to tweak and allowed a little more flexibility than a simple list of random drops would. I thought the item drop system I implemented for Asteroid Quest was okay, but it was hard to tune and did not feel very data-driven or scalable. So, I went back to the drawing board for this project.

The drop system now works based on something called "drop scripts". A drop script is defined inside an entity's .toml file, and then is parsed into a drop table component at runtime. I would say it's a sort of DSL, but toml is doing most of the parsing so I guess this is more of a "souped up" config entry. To make an NPC drop items upon death, all you have to do is tack a drop table onto the entity:

ents/chonkrat.toml ... [drop_table] drops = [ ["any", "0, 10.0, 2-6", "1, 100.0", "2, 100.0", "10, 20.0, 1-2", "11, 15.0" ] ]

"any" indicates that when a loot roll happens, multiple items from the list can be dropped. The entries themselves are in the form "item_id, drop_chance, min_quantity-max_quantity". The quantity fields are optional, and drop_chance must be between 0.0 and 100.0.

I got a lot of inspiration from Bob Nystrom's answer to this stackoverflow question from 9 years ago. The book he wrote, Game Programming Patterns, is awesome. When I was starting to learn more about game development and writing my own game engines, it was a invaluable resource. So, it's cool that here in the future I'm still getting educated by stuff he has written.

Anyway, similarly to the DSL proposed in that stackoverflow question, my drop scripts also have a macro system, but it is a bit simpler. It isn't powerful enough to do nested drop tables, as I originally wanted, but since this is just a first pass I may still implement that when I need it. For now, macros essentially just reference item drop lists that are written in their own .toml files. When an entry in the form of "!name" is present within a drop script, it is expanded, bringing in the drop list saved under name.toml.

drops/gems.toml id = 1 drops = [ "4, 3.0", "5, 1.5", "6, 0.5" ]

Now I can reference the gems drop list in an entity file:

ents/king-chonkrat.toml ... [drop_table] drops = [ ["any", "0, 50.0, 25-40", "1, 100.0", "2, 100.0", "10, 20.0, 1-2", "11, 15.0" ], ["one", "!gems" ] ]

"one" indicates that when a loot roll happens, only one item from the list can be dropped. So now, the King Chonkrat drops any of the items in the first list, but then will pick a maximum of one gem to drop.

Since I was working on item drop sets, I had to add some new (boring) items to the game:

  • Sapphire (id: 4)
  • Ruby (id: 5)
  • Emerald (id: 6)
  • Rusty Shortsword (id: 7)
  • Rusty Axe (id: 8)
  • Iron Longsword (id: 9)
  • Wooden Shield (id: 10)
  • Chonkrat Claws (id: 11)
  • Chonkrat Tail (id: 12)

The client didn't change much, so there isn't really anything new to see. Here's a screenshot of the battlefield after I had slain some chonkrats. Those purple cubes you are seeing are just items that I haven't had time to make 3D models for, yet.