[I don't know if this must be here or in General dev, so sorry if it does not belong here]
Hello, I have been working on a drop table solution for the last months and at moment I'm (probably) almost ready to have a beta version. I'm still working on documentation, so I don't have one to share right now, BUT I have made a sample scene with some cases that I think are common enough to cover most scenarios (that came to my mind) for an initial version. So I'm looking to get some feedback and critics about the whole thing, cases that I'm covering, the idiom that I had chosen, the code itself if you think is necessary, any kind of tip will be welcome. To be honest, I want to know if I have some public for this plugin before I go on, otherwise, I'll just finish some details and go on to the next project. I'll make a list of cases that I'm covering in the sample and if you want to know more you can watch the video in the end. This isn't a big plugin but I tried to make this with APIs that I would like to have if I got a similar system so I really appreciate any kind of help.
[TLDR] (The video will contain "almost" all this information) Video turned out to get longer than I expected, so if you intend to watch I suggest the topics N-attempts and Modifier, they're the better parts in my opinion.
Last but not least, if you can solve some problem in Known problems topic you'll automatically win an imaginary cup of coffee, so solve it and enjoy
>>>>>>>>>>>> Video <<<<<<<<<<<<<<
Brief about architecture
I have divided the system into 5 parts:
- Drop table: Main part, this is a scriptable object and it controls almost every aspect of your drops. It has your drops, callbacks, modifier, rules and it's from where you will (usually) request drops.
- Drops: This is a plain class that should exist only inside Drop Table
- Bag: When you request a drop you will get a bag as result.
- Loot: Is similar to Drop but it contains only the entry and the amount.
- Logic: This is the logic that will run in the background when you request a drop, you can inject this on each table or use the default one.
And we have some concepts to be aware of, by default both the modifiers and the rules are applied each time that you enumerate over the table in the following order - Global modifier -> Local modifier -> Temp modifier (when it exists) -> Rules.
- Modifier: Modifiers has been divided into layers, inner layers override outer layers, so Temporary modifiers override Local modifiers that override Global modifiers, modifiers are based on an Func<Drop, bool> as a filter, and an Action<Drop> as the proper action that will be applied to each drop.
- Rules: Can only be applied locally and it will work as a drop list filter.
Cases
In some cases where I don't think they're really necessary, some of them are really simple and sometimes more a guideline than a sample by itself, but as my girlfriend has said "nobody will complain about it being too documented", so I have kept it in.
- Simple: Illustrate the base case, it's a simple table with N items where each item have K chance to drop at each call
- Weighted: Also a base case, but now we get only one drop each time, the chance of each item is based on the total weight of all items in table, so increasing the chance of one will decrease the chance for other as the total sum of percentage must be 100%. Also, you can have some guaranteed drops set in the table which, in this case, means that you will have more than one drop (the one dropped by the table, and the ones you set as guaranteed).
- Tables containing tables: Here we demonstrate how to work with tables that can drop other tables with two examples, one showing to user their drop after each attempt and the other goes through each possible table and only getting final items in our result bag, it also shows how the system works with edge cases as an infinite loop between tables calling each other infinitely. EX 1: We drop items and other tables, every time the players drop a table they will be notified about it before the table was rerolled into another drop. EX 2: Tables are being used to simplify my design and I don't want to notify the player about this so the player will receive only final items
- Game Object: Here I'm only illustrating that the solution only works with scriptable objects, so if you want to instantiate something you will need to have a layer of scriptable objects where your game objects are contained. EX: I will randomize the goblin status based on a template before instantiate.
- N attempt: This will present us the concept of rules and use it to illustrate some custom approaches. EX: Drop a specific item after N drops OR guarantee at least one item for some quality in a pack with N drops.
- Instance Table: Sometimes we want to do something definitive to the table, eg. remove some drop or set some drop to null. Here we show how to create tables instances to work with. EX 1: We want to remove a drop after each attempt and refill the table based on a template when the table is empty.
- Repeatable: This is more a guideline of how to control your table to repeat some drops. EX: To avoid this scenario: the players didn't like the dropped items, so they reopen the game to try their luck again
- Modifiers: Here we have two examples, both show how to apply custom modifiers to some table, and how to add modifiers that CAN or CAN'T be removed. EX: This table will get a bonus percentage of drop based on player gathering skill OR this table will take in count the monster rage level when he dies to apply modifiers to drop.
- Custom logic: An example of how to create your own logic and make the table run based on it
- Global modifier: Similar to local modifier but is globally applied, so this rule will be applied to every table in the project when you call for a drop. eg. you want to double the chance of everything for some time.
Known problems:
- Modifiers will be applied when enumerate drops that should be validated verifying some conditions iterating over drops. This is recursive and will cause an stack overflow exception
In this case we can just iterate over original drops instead of custom drops, but and if I want to validate based on some custom value?
- Modifiers aren't being serialized what means that it need to be reinserted when some tables are loaded