Some functions, but you can also make things simpler by not using a sprite per substance, instead have one substance object, but multiple instances and use a an instance variable to distinguish the two. If you do that you can reduce the amount of events down to five even if you have hundreds of substances.
For multiple players I'd just add multiple instances of your player object and add a variable to distinguish between the two. The dictionaries relate to a single player so I'd put them in a container so each player has their own. Then adding a "for each player" to the last three events should get you most of the way there. The function will need to be modified so the player.uid is passed as well, then the first thing the function should do is pick by uid the passed value. You can do it without a container with variables but I don't feel like working that out.
OBJECTS:
Dictionaries: sub_dict, potion_dict
Sprite sub_sprite with text variable "name". Set the value of "name" to the name of the substance.
Spritevplayer with text variables "slotA" and "slotB". Both should start with a value of "".
EVENTS:
player: on collision with sub_sprite
[inverted] sub_dict: has key sub_sprite.name
system: compare: player.slotA+player.slotB <2
--- sub_dict: add key sub_sprite.name with value 1
on function "make potion?"
sub_dict: has key function.param(0)
sub_dict: has key function.param(1)
--- potion_dict: add key fuction.param(3) with value 1
--- sub_dict: delete key function.param(0)
--- sub_dict: delete key function.param(1)
every tick
--- call function "make potion?" ("substance1","substance2","potion1")
--- call function "make potion?" ("substance1","substance3","potion2")
--- call function "make potion?" ("substance2","substance3","potion3")
--- ... and so on
Potion_dict: for each key
player: slotA = ""
--- player: slotA set to Potions.CurrentKey
--- sub_dict: delete key Potions.CurrentKey
Potion_dict: for each key
player: slotB = ""
--- player: slotB set to Potions.CurrentKey
--- sub_dict: delete key Potions.CurrentKey