Fix possible race condition for reaction roles
### Description
This MR aims to fix a possible race condition on the addition of exclusive reaction roles when the reactions are spammed by the user.
### Changes Proposed
- Add a `ConcurrentHashSet<(ulong, ulong)>` to keep track of exclusive reaction roles that are being processed.
- Added logic that takes the collection above in consideration.
- If entry is present, quit silently.
- Else, perform the reaction role stuff then remove the entry.
### Details
Exclusive reaction roles are meant to be exactly that - exclusive.
Normally, when a user selects an exclusive role they receive that role. If they select another role, their previous role is removed and the new one is added. There is a bug where if the user spams the reactions for a short period of time, Nadeko will eventually assign them multiple roles that are meant to be exclusive with each other. This happens because the events that handle the addition and removal work in a weird way - first they offload the removal of the roles to a `Task.Run()`, which also happens to have a `Task.Delay()` in it (possibly to avoid Discord ratelimits).
Concurrently, it proceeds to add the role that the user picked. The problem with this approach is that the Task that handles the role removal takes long enough for another reaction event to trigger and start the same work for a different reaction role. Then mayhem ensues, with different events concomitantly adding and removing the roles that previous events have removed or added. In the end, the user ends up with multiple exclusive roles they are not supposed to.
This MR fixes this by having a local field that keeps track of the reaction roles that are being currently processed (a `ConcurrentHashSet<T>` where T is a tuple `(ulong, ulong)` - (message ID, user ID)). When a reaction event runs, it adds itself to the concurrent hashset. If another event triggers and tries to add itself while the previous event still hasn't finished, it silently quits so it doesn't interfere with the current event. I was not entirely satisfied with the way this works, so I tried another system that cancels the old events (with a CancellationToken) instead of just making the new events quit, but that resulted in multiple exclusive roles being temporarily assigned to the user (just for a few seconds, but still).
If another approach is preferred, then please do let me know.
### Notes
- Methods in that entire file need to be broken down into smaller methods.
- The loop that removes old reactions is **very slow**. Using `Task.WhenAll()` instead of awaiting the removals could help improve performance (but could also trigger ratelimits).
See merge request Kwoth/nadekobot!183
- .gifts now have 3 new ✂️ Haircut 🧻 ToiletPaper and 🥀 WiltedRose which **reduce** waifu's value
- Added a new multiplier (waifu.multi.negative_gift_effect default 0.5, changeable via .config gambling or data/gambling.yml)