Next to cards, runes, and objects, are the other major type of pocket item available in Isaac: Pills. They come in random colors with random pill effects each run, giving positive, negative, or neutral effects. This tutorial covers how to create a custom pill effect.
Custom Pill Colors
Custom pill colors are not naturally supported. If created through entities2.xml, they are unable to spawn naturally in a run and do not come assigned with a random pill effect, showing no text and not activating any effect when used. Other mods that add them force their own manual implementation.
Pill effects are created in the same way as cards; through the pocketitems.xml file inside your mod's content folder. Unlike cards, they are marginally easier to create thanks to just being effects, while the visuals are handled by the randomized pill colors. Creating pills requires the pill child tag. Below are all the variables available to build your pill effect entry:
pill tag variablesNote
name is the only required variable. All others are optional.
Variable-Name
Possible Values
Description
name
string
Name of the pill effect.
description
string
Description of the pill effect (optional, used in I found pills).
class
string
Possible values: [3-, 2-, 1-, 0, 1+, 2+, 3+]. Number indicates a Joke (0), Minor (1), Medium (2), or Major (3) effect. A + or - can be appended to note whether the pill is positive or negative, or excluded to denote neutral pills. Default = 0.
mimiccharge
int
Amount of charge the pill should take to mimic with Placebo. Default = 4
announcer
int
Sound ID to play when the pill is used.
announcer2
int
Sound ID to play when the pill is used as a horse pill.
announcerdelay
int
Delay in frames between pill use and the sound provided being played.
achievement
int
Ties the pill effect to a vanilla achievement.
greedmode
bool
Is the pocketitem available in greedmode. Default = true.
REPENTOGON expands pocketitems.xml with the following variables for pill effects:
REPENTOGON-exclusive pill tag variables
Variable Name
Possible Values
Description
achievement
int or string
Ties the pill-effect to be unlocked by an achievement. For modded ones, use the provided achievement name xml attribute(define one if it doesn't have one already).
With the pill effect created, only a short amount of Lua code is required to give it an effect. Create a main.lua file. Register your mod, use Isaac.GetPillEffectByName to fetch the ID of your pill, and create a single function attached to the MC_USE_PILL callback.
The following code will have the pill spawn an enemy fly:
1 2 3 4 5 6 7 8 9101112
localmod=RegisterMod("My Mod",1)localREGULAR_FLY=Isaac.GetPillEffectByName("Regular Fly")--MC_USE_PILL passes 3 arguments: The pill effect Id, the player using it, and UseFlags.functionmod:OnUsePill(pillEffect,player,useFlags)--Game:Spawn(EntityType, Variant, Position, Velocity, Spawner Entity, SubType, Seed)Game():Spawn(EntityType.ENTITY_FLY,0,player.Position,Vector.Zero,player,0,Random())end--MC_USE_PILL accepts an optional argument to only run for a specific pill.mod:AddCallback(ModCallbacks.MC_USE_PILL,mod.OnUsePill,REGULAR_FLY)
When using the pill, it does not play an animation by default. Positive pills are accompanied by EntityPlayer:AnimateHappy, negative pills accompanied by EntityPlayer:AnimateSad, while neutral pills hold the pill above the player's head using EntityPlayer:AnimatePill, which requires a pill color.
Unfortuantely, MC_USE_PILL does not provide any method of detecting what pill color was involved in the pill effect's activation, so you will need to manually track what pill the player is holding before the pill is used.
EntityPlayer:GetPill returns the currently held pill color in the primary pocket slot, but it will not return the desired pill color inside MC_USE_PILL as the pill is already used by then. Instead, use the function every player update using MC_POST_PEFFECT_UPDATE and store the result inside custom entity data attached to the player. This data can later be checked inside MC_USE_PILL.
REPENTOGON MC_USE_PILL PillColor Arg
With REPENTOGON, MC_USE_PILL passes a fourth argument that contains the pill color used in the pill effect use.
12345678
functionmod:OnUsePill(pillEffect,player,useFlags,pillColor)Game():Spawn(EntityType.ENTITY_FLY,0,player.Position,Vector.Zero,player,0,Random())--The second argument takes an animation found in the player's anm2 file.--The default animation is "Pickup", which is for collecting items and takes a long time. "UseItem" is more natural for using active items, cards, and pills.player:AnimatePill(pillColor,"UseItem")endmod:AddCallback(ModCallbacks.MC_USE_PILL,mod.OnUsePill,REGULAR_FLY)
localmod=RegisterMod("My Mod",1)localREGULAR_FLY=Isaac.GetPillEffectByName("Regular Fly")functionmod:OnUsePill(pillEffect,player,useFlags)localdata=player:GetData()--Get the data that tracks the pill color, or if the data doesn't exist yet, get the currently held pill as a failsafe.localpillColor=data.MYMOD_HeldPillColororplayer:GetPill(0)--A null pill color cannot be passed. Resort to another pill color by default.ifpillColor==PillColor.PILL_NULLthenpillColor=PillColor.PILL_BLUE_BLUEendGame():Spawn(EntityType.ENTITY_FLY,0,player.Position,Vector.Zero,player,0,Random())--The second argument takes an animation found in the player's anm2 file.--The default animation is "Pickup", which is for collecting items and takes a long time. "UseItem" is more natural for using active items, cards, and pills.player:AnimatePill(pillColor,"UseItem")end--MC_USE_PILL accepts an optional argument to only run for a specific pill.mod:AddCallback(ModCallbacks.MC_USE_PILL,mod.OnUsePill,REGULAR_FLY)functionmod:TrackPillColor(player)localdata=player:GetData()--Will check 30 frames a second what the pill color in the player's primary pocket slot is.--If no pill is in the primary slot, will be pill `0`.data.MYMOD_HeldPillColor=player:GetPill(0)endmod:AddCallback(ModCallbacks.MC_POST_PEFFECT_UPDATE,mod.TrackPillColor)
Horse Pills were introduced in Repentance, being a powered up version of all existing pill effects. These are not new pill effects on their own, and as such do not require their own separate entry, but instead classified as pill colors.
To enact different behaviour for horse pills on the custom pill effect, you need to know if the used pill was from a horse pill color. They can be identified by their large number, being the normal pill color id + PillColor.PILL_GIANT_FLAG, being 1 << 11 which equates to 2048. Referencing the previous section, get the player's current pill color and use it to determine if the pill is a horse pill.
REPENTOGON MC_USE_PILL PillColor Arg
As with the previous section, REPENTOGON has the callback pass the pill color.
localmod=RegisterMod("My Mod",1)localREGULAR_FLY=Isaac.GetPillEffectByName("Regular Fly")--Pass the player and the UseFlags from the pill activation.--Credit to this code goes to Xalum, who developed this for Fiend Folio.localfunctionisUsingHorsePill(player,useFlags)localdata=player:GetData()--Get the data that tracks the pill color, or if the data doesn't exist yet, get the currently held pill as a failsafe.localpillColor=data.MYMOD_HeldPillColororplayer:GetPill(0)localholdingHorsePill=pillColour&PillColor.PILL_GIANT_FLAG==PillColor.PILL_GIANT_FLAG--We cannot know the pill color if it was activated through Echo Chamber.--Echo Chamber is the only effect that uses the USE_NOHUD flag for pills, so disable usage if the flag is active.localproccedByEchoChamber=flags&UseFlag.USE_NOHUD==UseFlag.USE_NOHUDreturnholdingHorsePillandnotproccedByEchoChamberendfunctionmod:OnUsePill(pillEffect,player,useFlags)localflyType=EntityType.ENTITY_FLYlocalisHorse=isUsingHorsePill(player,useFlags)ifisHorsethenflyType=EntityType.ENTITY_ATTACKFLYendGame():Spawn(flyType,0,player.Position,Vector.Zero,player,0,Random())player:AnimatePill(pillColor)end--MC_USE_PILL accepts an optional argument to only run for a specific pill.mod:AddCallback(ModCallbacks.MC_USE_PILL,mod.OnUsePill,REGULAR_FLY)functionmod:TrackPillColor(player)localdata=player:GetData()--Will check 30 frames a second what the pill color in the player's primary pocket slot is.data.MYMOD_HeldPillColor=player:GetPill(0)endmod:AddCallback(ModCallbacks.MC_POST_PEFFECT_UPDATE,mod.TrackPillColor)
With items like PHD and False PHD, pill effects may have an assigned counterpart and be forced to turn into said counterparts. For example, False PHD will turn a Tears Up Pill into a Tears Down Pill, while PHD will do the opposite. To achieve this with custom pills, MC_GET_PILL_EFFECT can be used. The player will need to be checked if they meet the requirements for positive or negative pills. REPENTOGON adds a third argument to the callback that passes the player holding the pill effect. Without REPENTOGON, the callback does not pass the player, meaning the only option is to check all players to affect what to change the pill effect into.
For this example, two new pill effects will be created: Balls of Obsidian and Balls of Paper, which grant and remove black hearts respectively using EntityPlayer:AddBlackHearts. Define the new pill effects and new callbacks to give them functionality:
pocketitems.xml:
1234
<pocketitems><pillname="Balls of Obsidian"class="2+"/><pillname="Balls of Paper"class="2-"/></pocketitems>
main.lua:
1 2 3 4 5 6 7 8 9101112131415
localBALLS_OF_OBSIDIAN=Isaac.GetPillEffectByName("Balls of Obsidian")localBALLS_OF_PAPER=Isaac.GetPillEffectByName("Balls of Paper")functionmod:OnPillUse(pillEffect,player,useFlags)ifpillEffect==BALLS_OF_OBSIDIANthen--1 unit is 1/2 a heart, so 4 = 2 heartsplayer:AddBlackHearts(4)player:AnimateHappy()elseifpillEffect==BALLS_OF_PAPERthenplayer:AddBlackHearts(-4)player:AnimateSad()endendmod:AddCallback(ModCallbacks.MC_USE_PILL,mod.OnPillUse,BALLS_OF_OBSIDIAN)mod:AddCallback(ModCallbacks.MC_USE_PILL,mod.OnPillUse,BALLS_OF_PAPER)
Next, setup a function that will return if the player has positive or negative pill effects forced.
The Non-REPENTOGON method involves looping through all players with Isaac.FindByType:
--Define an enum for readabilitylocalPillEffectSubClass={NEUTRAL=0,POSITIVE=1,NEGATIVE=2}localfunctiongetPillEffectSubClass()localshouldBePositive=falselocalshouldBeNegative=false--Loop through all players in the room using Isaac.FindByType.for_,entinipairs(Isaac.FindByType(EntityType.ENTITY_PLAYER))do--Isaac.FindByType passes an Entity object by default. Cast to EntityPlayer to use player-related functions.localplayer=ent:ToPlayer()--All vanilla methods that enforce positive pills.ifplayer:HasCollectible(CollectibleType.COLLECTIBLE_LUCKY_FOOT)orplayer:HasCollectible(CollectibleType.COLLECTIBLE_VIRGO)orplayer:HasCollectible(CollectibleType.COLLECTIBLE_PHD)orplayer:GetZodiacEffect()==CollectibleType.COLLECTIBLE_VIRGOthenshouldBePositive=trueend--False PHD is the only item that enforces negative pills.ifplayer:HasCollectible(CollectibleType.COLLECTIBLE_FALSE_PHD)thenshouldBeNegative=trueend--Having both effects cancel each other out. End the loop early and return a value.ifshouldBePositiveandshouldBeNegativethenreturnPillEffectSubClass.NEUTRALendendifshouldBePositivethenreturnPillEffectSubClass.POSITIVEelseifshouldBeNegativethenreturnPillEffectSubClass.NEGATIVEelsereturnPillEffectSubClass.NEUTRALendend
The REPENTOGON method involves already having the player passed through the callback, so the function can be shortened:
--Define an enum for readabilitylocalPillEffectSubClass={NEUTRAL=0,POSITIVE=1,NEGATIVE=2}localfunctiongetPillEffectSubClass(player)localshouldBePositive=falselocalshouldBeNegative=false--All vanilla methods that enforce positive pills.ifplayer:HasCollectible(CollectibleType.COLLECTIBLE_LUCKY_FOOT)orplayer:HasCollectible(CollectibleType.COLLECTIBLE_VIRGO)orplayer:HasCollectible(CollectibleType.COLLECTIBLE_PHD)orplayer:GetZodiacEffect()==CollectibleType.COLLECTIBLE_VIRGOthenshouldBePositive=trueend--False PHD is the only item that enforces negative pills.ifplayer:HasCollectible(CollectibleType.COLLECTIBLE_FALSE_PHD)thenshouldBeNegative=trueend--Having both effects cancel each other out.ifshouldBePositiveandshouldBeNegativethenreturnPillEffectSubClass.NEUTRALelseifshouldBePositivethenreturnPillEffectSubClass.POSITIVEelseifshouldBeNegativethenreturnPillEffectSubClass.NEGATIVEelsereturnPillEffectSubClass.NEUTRALendend
Finally, setup the MC_GET_PILL_EFFECT function:
1 2 3 4 5 6 7 8 9101112
functionmod:AssignPillCounterparts(pillEffect,pillColor)--If using REPENTOGON, you would define the third argument above as "player" and pass it into the function below.localpillSubClass=getPillEffectSubClass()ifpillEffect==BALLS_OF_OBSIDIANandpillSubClass==PillEffectSubClass.NEGATIVEthenreturnBALLS_OF_PAPERelseifpillEffect==BALLS_OF_PAPERandpillSubClass==PillEffectSubClass.POSITIVEreturnBALLS_OF_OBSIDIANendendmod:AddCallback(ModCallbacks.MC_GET_PILL_EFFECT,mod.AssignPillCounterparts,BALLS_OF_OBSIDIAN)mod:AddCallback(ModCallbacks.MC_GET_PILL_EFFECT,mod.AssignPillCounterparts,BALLS_OF_PAPER)
As with cards, pill effects also have announcer voicelines. Please see the previous article on cards for the exact details on creating a system for announcer voicelines. The only notable difference is that horse pills have their own unique announcer voiceline, so a second sound will need to be created that will only play if you know the used pill is from a horse pill.