From 9703626b7148a01ed80a95e93c1272c9713128bb Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sun, 14 Jul 2024 16:07:10 -0400 Subject: [PATCH] Misc item reminder updates (#695) * Change wisp checking * Change selected item var * Trinket and wisp checks * add a simple room counter * various changes * update my todo list * Gulped trinket support * Reset selected items better * update todo list * add poop nuggets to pickup names * add glyph of balance * add a helper function for xml names * overhaul special description modifiers * Pandora's Box greed * Pandora's Box tweak * Pandora's Box overview desc * let Card Reading Boss portal get modified * move Strange Key to conditionals * Update eid_conditionals.lua * misc helper functions * whitelists * small text pos modifier tweaks * wild card + glowing hourglass + void ab+ * move vurp, add wild card * move SimpleRound * add wild card, move simpleround, fix pill icon stuff * Echo Chamber color * update todo list * starting work on The Stars? preview * remove collectible count check; bad for tainted eden * Tainted Laz overhaul * fix 4.5 volt with timed pocket actives * fix pocket active check functions * Tainted Laz overhaul + The Stars? * more conditionals * Battery and bean synergies --- descriptions/ab+/en_us.lua | 18 +- descriptions/rep/en_us.lua | 29 +- features/eid_api.lua | 275 +++++++++++++------ features/eid_conditionals.lua | 79 ++++-- features/eid_conditionals_funcs.lua | 98 +++++-- features/eid_data.lua | 30 ++- features/eid_holdmapdesc.lua | 403 ++++++++++++++++++++-------- features/eid_itemprediction.lua | 71 ++++- features/eid_mcm.lua | 2 +- features/eid_modifiers.lua | 139 ++++++---- features/eid_tmtrainer.lua | 17 +- main.lua | 207 +++++++++----- 12 files changed, 985 insertions(+), 383 deletions(-) diff --git a/descriptions/ab+/en_us.lua b/descriptions/ab+/en_us.lua index 4a14b76c..fd3533b0 100644 --- a/descriptions/ab+/en_us.lua +++ b/descriptions/ab+/en_us.lua @@ -319,7 +319,7 @@ EID.descriptions[languageCode].collectibles={ {"296", "Converter", "{{Heart}} Converts 2 Soul/Black Hearts into 1 heart container"}, -- Converter -- NOTE FOR LOCALIZERS: There is code to highlight the text of your current floor -- For it to work, only use line breaks or semicolons to separate floor details, and use the same order as English - {"297", "Pandora's Box", "{{Warning}} SINGLE USE {{Warning}} Spawns rewards based on floor:#B1: 2{{SoulHeart}}; B2: 2{{Bomb}} + 2{{Key}}#C1: Boss item; C2: B1+C1#D1: 4{{SoulHeart}}; D2: 20{{Coin}}#W1: 2 Boss items#W2: {{Collectible33}} The Bible#???/Void: Nothing#Sheol: Devil item + 1{{BlackHeart}}#Cathe: Angel item + 1{{EternalHeart}}#{{NoLB}}Dark Room: Unlocks {{Collectible523}} Moving Box#Chest: 1{{Coin}}"}, -- Pandora's Box + {"297", "Pandora's Box", "{{Warning}} SINGLE USE {{Warning}} Spawns rewards based on floor:#B1: 2{{SoulHeart}}; B2: 2{{Bomb}} + 2{{Key}}#{{NoLB}}C1: Boss item; C2: C1 + 2{{SoulHeart}}#D1: 4{{SoulHeart}}; D2: 20{{Coin}}#W1: 2 Boss items#W2: {{Collectible33}} The Bible#???/Void: Nothing#Sheol: Devil item + 1{{BlackHeart}}#Cathe: Angel item + 1{{EternalHeart}}#{{NoLB}}Dark Room: Unlocks {{Collectible523}} Moving Box#Chest: 1{{Coin}}"}, -- Pandora's Box {"298", "Unicorn Stump", "{{Timer}} Receive for 6 seconds:#↑ {{Speed}} +0.28 Speed#Invincibility#Isaac can't shoot (No contact damage)"}, -- Unicorn Stump {"299", "Taurus", "↓ {{Speed}} -0.3 Speed#↑ {{Speed}} Slowly gain speed while in hostile rooms#At 2 speed, Isaac becomes invincible and deals contact damage#Afterwards, lose the Taurus speed boost for the room"}, -- Taurus {"300", "Aries", "↑ {{Speed}} +0.25 Speed#Touching enemies deals contact damage"}, -- Aries @@ -633,7 +633,7 @@ EID.descriptions[languageCode].carBattery = { [485] = "25% chance to quadruple, 25% chance to double and remove the originals, 50% chance to remove", -- Crooked Penny [486] = {"effects", "{{CR}}effects {{BlinkYellowGreen}}twice"}, -- Dull Razor [488] = "Activates twice, but only the second effect stays", -- Metronome - [489] = {" a ", " 2 ", "effect", "{{CR}}effects"}, -- D Infinity (TODO: d infinity's desc addon is not visible in item reminder) + [489] = {" a ", " 2 ", "effect", "{{CR}}effects"}, -- D Infinity [504] = {" a ", " 2 ", "turret", "{{CR}}turrets"}, -- Brown Nugget [507] = "Triggers twice", -- Sharp Straw [510] = {" a ", " 2 ", "version", "{{CR}}versions"}, -- Delirious @@ -731,6 +731,7 @@ EID.descriptions[languageCode].BFFSSynergies = { ["5.350.54"] = {3.5, 7}, --- Isaac's Head ["5.350.57"] = {3.5, 7}, --- ???'s Soul + ["Lilith"] = "Incubus damage is doubled", -- Incubus (for BFFS pedestal) } ---------- Trinkets ---------- @@ -1129,7 +1130,7 @@ EID.descriptions[languageCode].RoomTypeNames = { "Normal Room", "Shop", "I AM ER -- Not technically room types but still potentially useful to have localized [666] = "Devil/Angel Room", [1024] = "Red Room", [1025] = "Special Red Room" } --- currently used for D1 +-- currently used for D1 and Glyph of Balance; contains Rep pickups for ease of localization EID.descriptions[languageCode].PickupNames = { ["5.0"] = "None", @@ -1144,6 +1145,8 @@ EID.descriptions[languageCode].PickupNames = { ["5.40"] = "{{Bomb}} Bomb", ["5.40.1"] = "{{Crafting15}} Bomb", ["5.40.2"] = "{{Crafting15}} Double Bomb", ["5.40.3"] = "{{Crafting15}} Troll Bomb", ["5.40.4"] = "{{Crafting16}} Golden Bomb", ["5.40.5"] = "{{Crafting15}} Mega Troll Bomb", ["5.40.7"] = "{{Crafting17}} Giga Bomb", + + ["5.42"] = "{{Crafting29}} Poop Nugget", ["5.42.1"] = "{{Crafting29}} Big Poop Nugget", ["5.90"] = "{{Battery}} Battery", ["5.90.0"] = "{{Crafting19}} Lil' Battery", --AB+ didn't have subtypes for batteries @@ -1171,6 +1174,7 @@ EID.descriptions[languageCode].ConditionalDescs = { ["Overrides"] = "Overrides {1}", ["Almost No Effect"] = "Little to no effect for {1}", ["No Effect"] = "No effect for {1}", + ["No Effect From"] = "No effect from {1}", ["Can't Charge"] = "Can't charge {1}", ["Can't Be Charged"] = "Can't be charged by {1}", ["Can't Be Duplicated"] = "Can't be duplicated", @@ -1198,6 +1202,7 @@ EID.descriptions[languageCode].ConditionalDescs = { ["5.350.44"] = {"{{Pill}} Chance for a bonus pill from chests, tinted rocks and destroyed machines"}, -- Safety Cap ["5.350.45"] = {"{{Card}} Chance for a bonus card from chests, tinted rocks and destroyed machines"}, -- Ace of Spades ["5.350.72"] = {"{{Battery}} +10% chance for random pickups to be a battery#{{Battery}} 5% chance to add 1 charge to held active item when clearing a wave"}, -- Watch Battery + ["5.100.297 (Greed)"] = {"{{Warning}} SINGLE USE {{Warning}} Spawns rewards based on floor:#Basement: 2{{Bomb}} + 2{{Key}}#Caves: Boss item + 2{{SoulHeart}}#Depths: 20{{Coin}}#Womb: 2 Boss items#Sheol: Devil item + 1{{BlackHeart}}#The Shop/Ultra Greed: 1{{Coin}}"}, -- Pandora's Box ------ ACHIEVEMENT CHECKS ------ @@ -1272,6 +1277,13 @@ EID.descriptions[languageCode].ConditionalDescs = { ["Technology Ipecac"] = "The laser gets +4 damage and poisons targets", ["Chocolate Milk Overrides"] = "↑ {{Tears}} x1.25 Tears multiplier", ["Chocolate Milk Marked"] = "Isaac automatically charges based on distance from the crosshair", + ["Ghost Pepper Fart"] = "Farting causes a flame to shoot out behind Isaac", + + ["5.300.5"] = {"{{BossRoom}} Teleports Isaac to a random Boss Room"}, -- IV - The Emperor (in The Void) + ["5.300.18"] = "Teleports Isaac to a random room if there's no Treasure Room", -- XVII - The Stars (Womb and below) + ["5.300.18 (Greed)"] = {"{{TreasureRoom}} Teleports Isaac to a random Treasure Room"}, -- XVII - The Stars (Greed) + ["5.300.18 (Late Greed)"] = {"Teleports Isaac to the first room of the floor"}, -- XVII - The Stars (Greed Last Floors) + ["5.300.10"] = "Teleports Isaac to a random room if there's no Shop", -- IX - The Hermit (Womb and below) } diff --git a/descriptions/rep/en_us.lua b/descriptions/rep/en_us.lua index c4f5ecf5..bc5acf9d 100644 --- a/descriptions/rep/en_us.lua +++ b/descriptions/rep/en_us.lua @@ -135,7 +135,7 @@ local repCollectibles={ [296] = {"296", "Converter", "{{Heart}} Converts 1 Soul or Black Heart into 1 heart container"}, -- Converter -- NOTE FOR LOCALIZERS: There is code to highlight the text of your current floor -- For it to work, only use line breaks or semicolons to separate floor details, and use the same order as English - [297] = {"297", "Pandora's Box", "{{Warning}} SINGLE USE {{Warning}} Spawns rewards based on floor:#B1: 2{{SoulHeart}}; B2: 2{{Bomb}} + 2{{Key}}#C1: Boss item; C2: B1+C1#D1: 4{{SoulHeart}}; D2: 20{{Coin}}#W1: 2 Boss items#W2: {{Collectible33}} The Bible#???/Void: Nothing#Sheol: Devil item + 1{{BlackHeart}}#Cathe: Angel item + 1{{EternalHeart}}#{{NoLB}}Dark Room: Unlocks {{Collectible523}} Moving Box#Chest: 1{{Coin}}#Home: {{Collectible580}} Red Key"}, -- Pandora's Box + [297] = {"297", "Pandora's Box", "{{Warning}} SINGLE USE {{Warning}} Spawns rewards based on floor:#B1: 2{{SoulHeart}}; B2: 2{{Bomb}} + 2{{Key}}#{{NoLB}}C1: Boss item; C2: C1 + 2{{SoulHeart}}#D1: 4{{SoulHeart}}; D2: 20{{Coin}}#W1: 2 Boss items#W2: {{Collectible33}} The Bible#???/Void: Nothing#Sheol: Devil item + 1{{BlackHeart}}#Cathe: Angel item + 1{{EternalHeart}}#{{NoLB}}Dark Room: Unlocks {{Collectible523}} Moving Box#Chest: 1{{Coin}}#Home: {{Collectible580}} Red Key"}, -- Pandora's Box [300] = {"300", "Aries", "↑ {{Speed}} +0.25 Speed#Moving above 0.85 Speed makes Isaac immune to contact damage and deals 25 damage to enemies"}, -- Aries [307] = {"307", "Capricorn", "↑ {{Heart}} +1 Health#↑ {{Speed}} +0.1 Speed#↑ {{Tears}} +0.5 Fire rate#↑ {{Damage}} +0.5 Damage#↑ {{Range}} +0.75 Range#+1 {{Coin}} coin, {{Bomb}} bomb and {{Key}} key"}, -- Capricorn [308] = {"308", "Aquarius", "Isaac leaves a trail of creep#{{Damage}} The creep deals 66% of Isaac's damage per second and inherits his tear effects"}, -- Aquarius @@ -221,7 +221,7 @@ local repCollectibles={ [489] = {"489", "D Infinity", "Can be made to act as any die item (except {{Collectible723}} Spindown Dice) with the drop button ({{ButtonRT}})#Charge time varies based on the last die used and updates with every use"}, -- D Infinity [491] = {"491", "Acid Baby", "{{Pill}} Spawns a random pill every 7 rooms#{{Poison}} Using a pill poisons all enemies in the room"}, -- Acid Baby [493] = {"493", "Adrenaline", "↑ {{Damage}} Damage up for every empty heart container#The more empty heart containers, the bigger the bonus for each new one"}, -- Adrenaline - [494] = {"494", "Jacob's Ladder", "Tears spawn a spark of electricity on impact#Sparks deal half of Isaac's damage#Sparks can spread to other enemies"}, -- Jacob's Ladder + [494] = {"494", "Jacob's Ladder", "Tears spawn a spark of electricity on impact#Sparks deal half of Isaac's damage#Sparks can arc to up to 4 other enemies"}, -- Jacob's Ladder [495] = {"495", "Ghost Pepper", "8% chance to shoot a blue fire that blocks enemy shots and deals contact damage#{{Luck}} 50% chance at 10 luck#Fires shrink and disappear after 2 seconds"}, -- Ghost Pepper [496] = {"496", "Euthanasia", "3.33% chance to shoot a needle#{{Luck}} 25% chance at 13 luck#Needles kill normal enemies instantly, bursting them into 10 tears#{{Damage}} Needles deal 3x Isaac's damage against bosses"}, -- Euthanasia [497] = {"497", "Camo Undies", "{{Confusion}} Entering a room camouflages Isaac and confuses all enemies until a tear is shot#↑ {{Speed}} +0.5 Speed while cloaked#Uncloaking deals damage around Isaac and grants a very brief fire rate and damage up"}, -- Camo Undies @@ -248,7 +248,7 @@ local repCollectibles={ [556] = {"556", "Sulfur", "{{Timer}} {{Collectible118}} Brimstone for the room#Using it multiple times in one room grants increased damage and a larger beam"}, -- Sulfur [557] = {"557", "Fortune Cookie", "Grants one of the following rewards:#A fortune#{{SoulHeart}} A Soul Heart#{{Rune}} A Rune or Soul Stone#{{Card}} A Tarot card#{{Trinket}} A Trinket"}, -- Fortune Cookie [558] = {"558", "Eye Sore", "Chance to shoot 1-3 extra tears in random directions#{{Luck}} Not affected by luck"}, -- Eye Sore - [559] = {"559", "120 Volt", "Repeatedly zaps nearby enemies#{{Damage}} Electricity deals up to 3.75x Isaac's damage per second"}, -- 120 Volt + [559] = {"559", "120 Volt", "Repeatedly zaps nearby enemies#{{Damage}} Electricity deals up to 3.75x Isaac's damage per second#Sparks can arc to up to 4 other enemies"}, -- 120 Volt [560] = {"560", "It Hurts", "{{Timer}} When taking damage, receive for the room:#↑ {{Tears}} +1.2 Fire rate on the first hit#↑ {{Tears}} +0.4 Fire rate for each additional hit#Releases a ring of 10 tears around Isaac"}, -- It Hurts [561] = {"561", "Almond Milk", "↑ {{Tears}} x4 Fire rate multiplier#↓ {{Damage}} x0.3 Damage multiplier#↓ {{Tearsize}} -0.16 Tear size#Tears gain random worm trinket effects and some item effects"}, -- Almond Milk [562] = {"562", "Rock Bottom", "↑ Prevents stats from being lowered for the rest of the run"}, -- Rock Bottom @@ -669,11 +669,11 @@ EID.descriptions[languageCode].abyssSynergies = { -- Effect of Car battery on Active Items local repCarBattery = { - [34] = {2, 4}, -- The Book of Belial -- TODO: Is this +1 damage or another +2 from additional uses? - [59] = {2, 4}, -- The Book of Belial (Birthright) + [34] = {2, 3}, -- The Book of Belial + [59] = {2, 3}, -- The Book of Belial (Birthright) [83] = {" Half", "1"}, -- The Nail [263] = "Triggers it twice", -- Clear Rune - [283] = {1, 2, "pickup", "{{CR}}pickups"}, -- D100 TODO: TEST THIS is this really the only change + [283] = {1, 2, "pickup", "{{CR}}pickups"}, -- D100 [285] = "Devolves enemies twice", -- D10 [288] = {"4-8", "8-16"}, -- Box of Spiders [293] = {200, 400}, -- Head of Krampus @@ -703,7 +703,7 @@ local repCarBattery = { [713] = {"half a heart", "a heart", "a clot", "2 {{CR}}clots"}, -- Sumptorium [719] = {" a ", " 2 ", "item/pickup", "{{CR}}items/pickups"}, -- Keeper's Box [720] = "Spawns a poop alongside the pickup", -- Everything Jar - [722] = "Chains 2 enemies down#If there's only one enemy, chains it for 10 seconds", -- Anima Sola + [722] = "Chains 2 enemies down#If there's only one enemy, chains it for 10 seconds#Using Anima Sola again unchains the closest enemy", -- Anima Sola [723] = {"one", "two"}, -- Spindown Dice } EID:updateDescriptionsViaTable(repCarBattery, EID.descriptions[languageCode].carBattery) @@ -764,13 +764,14 @@ local repBFFSSynergies = { ["5.100.706"] = "Locust damage is increased by 25%", -- Abyss ["5.100.712"] = "Doubles orbital contact damage", -- Lemegeton ["5.100.713"] = "Clot damage is doubled", -- Sumptorium - ["5.100.728"] = {75, 150, 100, 200}, -- Incubus (+ Lilith exception) + ["5.100.728"] = {75, 150, 100, 200}, -- Gello (+ Lilith exception) ["5.300.96"] = "Doubles wisp tear and contact damage", -- Soul of Bethany ["5.350.142"] = "Doubles wisp tear and contact damage", -- Beth's Faith ["5.350.182"] = "Doubles wisp tear and contact damage", -- Beth's Essence ["5.350.176"] = "Clot damage is doubled", -- Lil Clot ["5.350.186"] = "Locust damage is increased by 25%", -- Apollyon's Best Friend + ["Tainted Lilith"] = "Gello's tear damage is doubled", -- Gello (for BFFS pedestal) } EID:updateDescriptionsViaTable(repBFFSSynergies, EID.descriptions[languageCode].BFFSSynergies) @@ -817,7 +818,7 @@ local repTrinkets={ [138] = {"138", "'M", "Using an active item rerolls it"}, -- 'M [139] = {"139", "Teardrop Charm", "{{Luck}} +4 Luck towards luck-based tear effects"}, -- Teardrop Charm [140] = {"140", "Apple of Sodom", "Picking up Red Hearts can convert them into blue spiders#Works even while at full health#Effect may consume hearts needed for healing"}, -- Apple of Sodom - [141] = {"141", "Forgotten Lullaby", "Increases the fire rate of familiars"}, -- Forgotten Lullaby + [141] = {"141", "Forgotten Lullaby", "Doubles the fire rate of familiars"}, -- Forgotten Lullaby [142] = {"142", "Beth's Faith", "{{Collectible584}} Entering a new floor spawns 4 Book of Virtues wisps"}, -- Beth's Faith [143] = {"143", "Old Capacitor", "{{Battery}} Prevents active item from charging by clearing a room#{{Battery}} Clearing a room has a 20% chance to spawn a battery#{{Luck}} 33% chance at 5 luck"}, -- Old Capacitor [144] = {"144", "Brain Worm", "Tears turn 90 degrees to target enemies that they may have missed"}, -- Brain Worm @@ -1404,6 +1405,7 @@ local repConditions = { ["Black Feather"] = "↑ {{Damage}} +0.5 Damage", -- Black Feather items ["Hemoptysis"] = "Works with {1}", -- Hemoptysis + Brimstone items ["5.100.600"] = "x1.2 Fire rate multiplier for {1}", -- Eye Drops (items and some players) + ["5.100.482"] = "Tainted characters change into other tainted characters", -- Clicker ["4.5 Volt Timed"] = "No effect on timed recharges", -- 4.5 Volt + Timed Recharges ["4.5 Volt Multiple"] = "Secondary active items only gain charge if the primary active is fully charged", -- 4.5 Volt + Schoolbag/Pocket Actives @@ -1421,6 +1423,15 @@ local repConditions = { ["Technology Ipecac"] = "The laser gets +2 damage and explodes on targets", ["Eye of the Occult Beam"] = "Isaac automatically shoots with a crosshair that alters the beam's path", + ["Lullaby Tainted Eve"] = "Clot fire rate is doubled", + ["Lullaby Lilith"] = "Incubus fire rate is doubled", + + -- Jacob's Ladder / 120 Volt battery synergies + ["Sparks Damage"] = "Sparks deal 50% more damage", + ["Sparks Arc Length"] = "Sparks have longer arc distance", + ["Sparks Arc Count"] = "Sparks can arc to 2 more enemies", + ["Sparks Arc Back"] = "Sparks can arc back to the same enemy", + } EID:updateDescriptionsViaTable(repConditions, EID.descriptions[languageCode].ConditionalDescs) diff --git a/features/eid_api.lua b/features/eid_api.lua index 837be95e..ec6d2966 100644 --- a/features/eid_api.lua +++ b/features/eid_api.lua @@ -756,7 +756,8 @@ function EID:replaceNameMarkupStrings(text) if showIcon then iconText = "{{" .. EID:GetIconNameByVariant(entityID[2]) .. entityID[3] .. "}} " end colorText = VariantToColorText[entityID[2]] or "" end - name = iconText .. colorText .. EID:getObjectName(entityID[1], entityID[2], entityID[3]) .. "{{CR}}" + if entityID[2] == 70 then name = iconText .. colorText .. EID:getPillName(entityID[3], false) .. "{{CR}}" + else name = iconText .. colorText .. EID:getObjectName(entityID[1], entityID[2], entityID[3]) .. "{{CR}}" end elseif indicator == "C" then -- Collectible name = (showIcon and "{{Collectible"..id.."}} " or "") .. "{{ColorYellow}}" .. EID:getObjectName(5, 100, id) .. "{{CR}}" elseif indicator == "T" then -- Trinket @@ -837,8 +838,8 @@ function EID:createItemIconObject(str) end local pillID,numReplace4 = string.gsub(str, "Pill", "") if numReplace4 > 0 and pillID ~= "" and tonumber(pillID) ~= nil then - if tonumber(pillID) > maxPillID then return EID.InlineIcons[str] or EID.InlineIcons["Pill"] end - return {"Pills", tonumber(pillID)-1, 9, 8, 0, 1, EID.CardPillSprite} + if tonumber(pillID % 2048) > maxPillID then return EID.InlineIcons[str] or EID.InlineIcons["Pill"] end + return {"Pills", tonumber(pillID % 2048)-1, 9, 8, 0, 1, EID.CardPillSprite} end if item == nil then return nil @@ -1266,14 +1267,15 @@ end function EID:PlayersVoidedCollectible(collectibleID) for i = 0, game:GetNumPlayers() - 1 do local player = Isaac.GetPlayer(i) - if player:HasCollectible(477) and EID.absorbedItems[tostring(i)] and EID.absorbedItems[tostring(i)][tostring(collectibleID)] then + local playerNum = EID:getPlayerID(player, true) + if player:HasCollectible(477) and EID.absorbedItems[tostring(playerNum)] and EID.absorbedItems[tostring(playerNum)][tostring(collectibleID)] then return true, player, i end end return false end function EID:PlayerVoidedCollectible(player, collectibleID) - local i = EID:getPlayerID(player) + local i = EID:getPlayerID(player, true) if player:HasCollectible(477) and EID.absorbedItems[tostring(i)] and EID.absorbedItems[tostring(i)][tostring(collectibleID)] then return true end @@ -1375,6 +1377,24 @@ function EID:GetMaxCollectibleID() return id end +local maxTrinketID = nil +function EID:GetMaxTrinketID() + if maxTrinketID then + return maxTrinketID + end + local id = TrinketType.NUM_TRINKETS-1 + local step = 16 + while step > 0 do + if EID.itemConfig:GetTrinket(id+step) ~= nil then + id = id + step + else + step = step // 2 + end + end + maxTrinketID = id + return id +end + function EID:DetectModdedItems() if EID:GetMaxCollectibleID() > EID.XMLMaxItemID then return true @@ -1722,11 +1742,22 @@ function EID:checkPlayersForMissingItems() end end -function EID:getPlayerID(entityPlayer) +function EID:getPlayerID(entityPlayer, lazarusAdjust) if not entityPlayer then return 0 end for i = 0, game:GetNumPlayers() - 1 do local player = Isaac.GetPlayer(i) if GetPtrHash(player) == GetPtrHash(entityPlayer) then + -- Dead Tainted Lazarus exceptions + if lazarusAdjust and EID.isRepentance then + -- Check for Birthright to make the ghostly form use the same base ID as the main form + if player:GetMainTwin() then + if GetPtrHash(player:GetMainTwin()) ~= GetPtrHash(player) then + i = EID:getPlayerID(player:GetMainTwin()) + end + end + -- Increase the ID by 666 if Dead Tainted Lazarus + if player:GetPlayerType() == 38 then i = i + 666 end + end return i end end @@ -1842,24 +1873,21 @@ EID.TransformationProgress = {} function EID:evaluateTransformationProgress(transformation) for i = 0, game:GetNumPlayers() - 1 do local player = Isaac.GetPlayer(i) - EID.TransformationProgress[i] = {} - EID.TransformationProgress[i][transformation] = 0 + local id = EID:getPlayerID(player, true) -- Dead Tainted Lazarus exception + EID.TransformationProgress[id] = {} + EID.TransformationProgress[id][transformation] = 0 local transformData = EID.TransformationData[transformation] if not EID.TransformationLookup[transformation] then return end if REPENTOGON and transformData and transformData.VanillaForm then -- REPENTOGON lets us ignore everything else for vanilla transformation progress - EID.TransformationProgress[i][transformation] = player:GetPlayerFormCounter(transformData.VanillaForm) + EID.TransformationProgress[id][transformation] = player:GetPlayerFormCounter(transformData.VanillaForm) elseif transformData and transformData.VanillaForm and player:HasPlayerForm(transformData.VanillaForm) then - EID.TransformationProgress[i][transformation] = transformData.NumNeeded or 3 + EID.TransformationProgress[id][transformation] = transformData.NumNeeded or 3 else - local pickupHistory = EID.PlayerItemInteractions[i].pickupHistory + local pickupHistory = EID.PlayerItemInteractions[id].pickupHistory local pillsTable = {} - -- Dead Tainted Lazarus exception - if player:GetPlayerType() == 38 then - pickupHistory = EID.PlayerItemInteractions[i].altPickupHistory or pickupHistory - end if pickupHistory then for j = 1, #pickupHistory do if pickupHistory[j][1] == "pill" then @@ -1872,38 +1900,35 @@ function EID:evaluateTransformationProgress(transformation) end end - local activesTable = EID.PlayerItemInteractions[i].actives - if player:GetPlayerType() == 38 then - activesTable = EID.PlayerItemInteractions[i].altActives or activesTable - end + local activesTable = EID.PlayerItemInteractions[id].actives for entityString, _ in pairs(EID.TransformationLookup[transformation]) do local eType, eVariant, eSubType = entityString:match("([^.]+).([^.]+).([^.]+)") if tonumber(eType) == EntityType.ENTITY_PICKUP then if tonumber(eVariant) == PickupVariant.PICKUP_COLLECTIBLE then - local currentCount = EID.TransformationProgress[i][transformation] + local currentCount = EID.TransformationProgress[id][transformation] if activesTable[tostring(eSubType)] then - EID.TransformationProgress[i][transformation] = EID.TransformationProgress[i][transformation] + activesTable[tostring(eSubType)] + EID.TransformationProgress[id][transformation] = EID.TransformationProgress[id][transformation] + activesTable[tostring(eSubType)] else local collCount = player:GetCollectibleNum(eSubType, true) - if EID.PlayerItemInteractions[i].rerollItems then - collCount = collCount - (EID.PlayerItemInteractions[i].rerollItems[tostring(eSubType)] or 0) + if EID.PlayerItemInteractions[id].rerollItems then + collCount = collCount - (EID.PlayerItemInteractions[id].rerollItems[tostring(eSubType)] or 0) end - EID.TransformationProgress[i][transformation] = EID.TransformationProgress[i][transformation] + collCount + EID.TransformationProgress[id][transformation] = EID.TransformationProgress[id][transformation] + collCount -- Undo the Book of Virtues active item getting counted here if tonumber(eSubType) == 584 and player:GetActiveItem() == 584 then - EID.TransformationProgress[i][transformation] = EID.TransformationProgress[i][transformation] - 1 + EID.TransformationProgress[id][transformation] = EID.TransformationProgress[id][transformation] - 1 end end -- In AB+, only one copy of a given collectible is counted for trans - if not EID.isRepentance and EID.TransformationProgress[i][transformation] > currentCount + 1 then - EID.TransformationProgress[i][transformation] = currentCount + 1 + if not EID.isRepentance and EID.TransformationProgress[id][transformation] > currentCount + 1 then + EID.TransformationProgress[id][transformation] = currentCount + 1 end elseif tonumber(eVariant) == PickupVariant.PICKUP_TRINKET and player:HasTrinket(eSubType) then - EID.TransformationProgress[i][transformation] = EID.TransformationProgress[i][transformation] + player:GetTrinketMultiplier(eSubType) + EID.TransformationProgress[id][transformation] = EID.TransformationProgress[id][transformation] + player:GetTrinketMultiplier(eSubType) elseif tonumber(eVariant) == PickupVariant.PICKUP_PILL then if pillsTable[tostring(eSubType)] then - EID.TransformationProgress[i][transformation] = EID.TransformationProgress[i][transformation] + pillsTable[tostring(eSubType)] + EID.TransformationProgress[id][transformation] = EID.TransformationProgress[id][transformation] + pillsTable[tostring(eSubType)] end end end @@ -1941,23 +1966,25 @@ EID.RecentlyTouchedItems = {} local hadQueuedItem = {} function EID:evaluateQueuedItems() for i = 0, game:GetNumPlayers() - 1 do - EID:InitItemInteractionIfAbsent(i) local player = Isaac.GetPlayer(i) + local id = EID:getPlayerID(player, true) + EID:InitItemInteractionIfAbsent(id) + if player.QueuedItem then -- Refresh our descriptions and grid entity list upon a queued passive item being added to a player - if not player.QueuedItem.Item and hadQueuedItem[i] then + if not player.QueuedItem.Item and hadQueuedItem[id] then EID.ForceRefreshCache = true EID:CheckCurrentRoomGridEntities() end - hadQueuedItem[i] = player.QueuedItem.Item ~= nil - if EID.PlayerItemInteractions[i].LastTouch + 45 >= game:GetFrameCount() and player.QueuedItem.Item then + hadQueuedItem[id] = player.QueuedItem.Item ~= nil + if EID.PlayerItemInteractions[id].LastTouch + 45 >= game:GetFrameCount() and player.QueuedItem.Item then return else - EID.PlayerItemInteractions[i].LastTouch = 0 + EID.PlayerItemInteractions[id].LastTouch = 0 end if not player.QueuedItem.Touched and player.QueuedItem.Item then - EID.PlayerItemInteractions[i].LastTouch = game:GetFrameCount() + EID.PlayerItemInteractions[id].LastTouch = game:GetFrameCount() local itemIDStr = tostring(player.QueuedItem.Item.ID) -- Add touched active items to our transformation progress table if player.QueuedItem.Item.Type == ItemType.ITEM_ACTIVE then @@ -1965,24 +1992,23 @@ function EID:evaluateQueuedItems() -- (Fixes co-op bugs, compared to only initiating it for the toucher) for j = 0, game:GetNumPlayers() - 1 do EID.PlayerItemInteractions[j].actives[itemIDStr] = EID.PlayerItemInteractions[j].actives[itemIDStr] or 0 - EID.PlayerItemInteractions[j].altActives[itemIDStr] = EID.PlayerItemInteractions[j].altActives[itemIDStr] or 0 - end - - -- Dead Tainted Lazarus exceptions - local activesTable = EID.PlayerItemInteractions[i].actives - if player:GetPlayerType() == 38 then - activesTable = EID.PlayerItemInteractions[i].altActives or activesTable + EID.PlayerItemInteractions[j+666].actives[itemIDStr] = EID.PlayerItemInteractions[j+666].actives[itemIDStr] or 0 end + + local activesTable = EID.PlayerItemInteractions[id].actives EID.ForceRefreshCache = true activesTable[itemIDStr] = activesTable[itemIDStr] + 1 elseif player.QueuedItem.Item.Type ~= ItemType.ITEM_TRINKET then -- In AB+, Halo of Flies is counted as an active item due to inaccuracy with GetCollectibleNum if (not EID.isRepentance and itemIDStr == "10") then - EID.PlayerItemInteractions[i].actives[itemIDStr] = EID.PlayerItemInteractions[i].actives[itemIDStr] + 1 + EID.PlayerItemInteractions[id].actives[itemIDStr] = EID.PlayerItemInteractions[id].actives[itemIDStr] + 1 end -- Put non-active item pickups into the recent item list, for printing in the Item Reminder - table.insert(EID.RecentlyTouchedItems[i], player.QueuedItem.Item.ID) - EID.ItemReminderSelectedItem = 0 + local playerID = i + if EID.isRepentance and player:GetPlayerType() == 38 then playerID = playerID + 666 end + table.insert(EID.RecentlyTouchedItems[playerID], player.QueuedItem.Item.ID) + EID:ResetItemReminderSelectedItems("Passives") + EID.ItemReminderSelectedItems[#EID.ItemReminderCategories - 1] = 0 end end end @@ -1991,15 +2017,17 @@ end -- if the player ItemInteraction table doesnt exist, create it with its init values function EID:InitItemInteractionIfAbsent(playerID) + playerID = playerID % 666 -- dead tainted lazarus exception if not EID.PlayerItemInteractions[playerID] then - EID.PlayerItemInteractions[playerID] = { LastTouch = 0, actives = {}, altActives = {}, - pickupHistory = {}, altPickupHistory = {}, rerollItems = {} } + EID.PlayerItemInteractions[playerID] = { LastTouch = 0, actives = {}, pickupHistory = {}, rerollItems = {} } + EID.PlayerItemInteractions[playerID+666] = { LastTouch = 0, actives = {}, pickupHistory = {}, rerollItems = {} } -- in AB+, initiate Halo of Flies as an active item (collectible ID 10 is used for ALL Pretty Flies) if not EID.isRepentance then EID.PlayerItemInteractions[playerID].actives["10"] = 0 end end EID.RecentlyTouchedItems[playerID] = EID.RecentlyTouchedItems[playerID] or {} + EID.RecentlyTouchedItems[playerID+666] = EID.RecentlyTouchedItems[playerID+666] or {} end @@ -2051,19 +2079,33 @@ function EID:AddPickupToHistory(pickupType, effectID, player, useFlags, pillColo if EID.isRepentance and (useFlags & UseFlag.USE_MIMIC == UseFlag.USE_MIMIC or useFlags & UseFlag.USE_NOANNOUNCER == UseFlag.USE_NOANNOUNCER) then allowEchoChamber = false end - local playerID = EID:getPlayerID(player) + local playerID = EID:getPlayerID(player, true) EID:InitItemInteractionIfAbsent(playerID) - local historyTable = EID.PlayerItemInteractions[playerID].pickupHistory - -- Dead Tainted Lazarus exception - if player:GetPlayerType() == 38 then - historyTable = EID.PlayerItemInteractions[playerID].altPickupHistory or historyTable - end -- pickupType = ["pill","card"], pillColorID, effectID, hadEchoChamberWhenUsed table.insert(historyTable, 1, {pickupType, pillColorID, effectID, EID.isRepentance and player:HasCollectible(700) and allowEchoChamber}) end +EID.WildCardEffects = {} +EID.WildCardPillColor = {} +EID.TemporaryWildCardEffects = {} -- resets every frame +function EID:TrackWildCardEffects(effectID, player, pillColor) + local playerID = EID:getPlayerID(player, true) + -- ? Card exception; both it and the active it used are ignored by Wild Card + if effectID == "5.300.48" then + -- Blank Card using ? Card exception; Blank Card being used is still seen + if EID.TemporaryWildCardEffects[playerID] ~= "5.100.286" then EID.TemporaryWildCardEffects[playerID] = nil end + else + EID.TemporaryWildCardEffects[playerID] = effectID + EID.WildCardPillColor[playerID] = pillColor -- doesn't matter that much so just set it here + end +end +function EID:UpdateWildCardEffects() + for k,v in pairs(EID.TemporaryWildCardEffects) do EID.WildCardEffects[k] = v end + EID.TemporaryWildCardEffects = {} +end + -- Render a sprite of an entity function EID:RenderEntity(entity, sprite, position) if entity.Type == 5 and entity.Variant == 100 then @@ -2087,7 +2129,7 @@ end -- Collects items that the player got after using D4 item function EID:CollectRerolledItemsOfPlayer(player) if maxCollectibleID == nil then maxCollectibleID = EID:GetMaxCollectibleID() end - local playerID = EID:getPlayerID(player) + local playerID = EID:getPlayerID(player, true) EID.PlayerItemInteractions[playerID].rerollItems = {} for i = 1, maxCollectibleID do local count = player:GetCollectibleNum(i, true) @@ -2118,6 +2160,10 @@ end function EID:GetEntityXMLName(Type, Variant, SubType) return EID.XMLEntityNames[Type.."."..Variant] or EID.XMLEntityNames[Type.."."..Variant.."."..SubType] end +function EID:GetEntityXMLNameByString(tvsString) + local Type, Var, Sub = EID:SplitTVS(tvsString) + return EID:GetEntityXMLName(Type, Var, Sub) +end -- Get an item's RNG seed. We have no use for the RNG object itself because every other function it can do will advance the item's RNG, altering the game state function EID:GetItemSeed(player, id, variant) @@ -2177,44 +2223,90 @@ function EID:UpdateAllPlayerPassiveItems() local listUpdatedForPlayers = {} for i = 1, #EID.coopAllPlayers do local player = EID.coopAllPlayers[i] - local playerNum = EID:getPlayerID(player) - if player == nil then return listUpdatedForPlayers -- dont evaluate when bad data is present end - - -- only update if collectible number changed - local currentCollectibleCount = player:GetCollectibleCount() - if #EID.RecentlyTouchedItems[playerNum] ~= currentCollectibleCount then - - -- remove items the player no longer has. reverse itteration to make deletion easier - for index = #EID.RecentlyTouchedItems[playerNum], 1, -1 do - if not player:HasCollectible(EID.RecentlyTouchedItems[playerNum][index], true) then - table.remove(EID.RecentlyTouchedItems[playerNum], index) - listUpdatedForPlayers[i] = true - end + + local playerNum = EID:getPlayerID(player, true) + + -- remove items the player no longer has. reverse iteration to make deletion easier + for index = #EID.RecentlyTouchedItems[playerNum], 1, -1 do + if not player:HasCollectible(EID.RecentlyTouchedItems[playerNum][index], true) then + table.remove(EID.RecentlyTouchedItems[playerNum], index) + listUpdatedForPlayers[i] = true + -- If an item earlier than our oldest item is removed (e.g. Eve sacrificial altaring her Dead Bird), reduce it + if index < EID.OldestItemIndex[playerNum] then EID.OldestItemIndex[playerNum] = EID.OldestItemIndex[playerNum] - 1 end end + end - -- add items the player did get with non-standard methods (console command, item effects, etc...) - for _, itemID in ipairs(passives) do - if player:HasCollectible(itemID, true) then - local alreadyInList = false - for _, heldItemID in ipairs(EID.RecentlyTouchedItems[playerNum]) do - if itemID == heldItemID then - alreadyInList = true - break - end - end - if not alreadyInList then - table.insert(EID.RecentlyTouchedItems[playerNum], itemID) - listUpdatedForPlayers[i] = true + -- add items the player did get with non-standard methods (console command, item effects, etc...) + for _, itemID in ipairs(passives) do + if player:HasCollectible(itemID, true) then + local alreadyInList = false + for _, heldItemID in ipairs(EID.RecentlyTouchedItems[playerNum]) do + if itemID == heldItemID then + alreadyInList = true + break end end + if not alreadyInList then + table.insert(EID.RecentlyTouchedItems[playerNum], itemID) + listUpdatedForPlayers[i] = true + end end end end return listUpdatedForPlayers end +EID.OldestItemIndex = {} +function EID:SetOldestItemIndex() + for i = 0, game:GetNumPlayers() - 1 do + EID.RecentlyTouchedItems[i] = EID.RecentlyTouchedItems[i] or {} + if EID.OldestItemIndex[i] == nil then EID.OldestItemIndex[i] = #EID.RecentlyTouchedItems[i] + 1 end + -- set up Dead Tainted Lazarus to be oldest slot 1 + if EID.OldestItemIndex[i+666] == nil then EID.OldestItemIndex[i+666] = 1 end + end +end + +EID.GulpedTrinkets = {} +-- Check for gulped trinkets that have been removed (e.g. perfection, walnut) +function EID:UpdateAllPlayerTrinkets() + for i = 1, #EID.coopAllPlayers do + local player = EID.coopAllPlayers[i] + if player == nil then return end + + local playerNum = EID:getPlayerID(player, true) + if EID.GulpedTrinkets[playerNum] == nil then return end + + -- remove items the player no longer has. reverse iteration to make deletion easier + for index = #EID.GulpedTrinkets[playerNum], 1, -1 do + if not player:HasTrinket(EID.GulpedTrinkets[playerNum][index]) then + table.remove(EID.GulpedTrinkets[playerNum], index) + end + end + end +end + +EID.WispsPerPlayer = {} +-- This is automatically called shortly after main.lua sees a Lemegeton get used, and when the Item Reminder is opened +function EID:UpdateAllPlayerLemegetonWisps() + EID.WispsPerPlayer = {} + + for _, wisp in ipairs(Isaac.FindByType(3, 237, -1, true, false)) do + local wispPlayer = wisp:ToFamiliar() and wisp:ToFamiliar().Player + if wispPlayer then + local playerNum = EID:getPlayerID(wispPlayer, true) + EID.WispsPerPlayer[playerNum] = EID.WispsPerPlayer[playerNum] or {} + table.insert(EID.WispsPerPlayer[playerNum], wisp) + end + end + -- Sort wisps by age (newest first), and leave just the IDs + for playerNum,wisps in pairs(EID.WispsPerPlayer) do + table.sort(wisps, function(a, b) return a.FrameCount < b.FrameCount end) + for i,v in ipairs(wisps) do wisps[i] = v.SubType end + end + +end -- Replaces Variable placeholders in string with a given value -- Example: "My {1} message" --> "My test message" @@ -2222,3 +2314,26 @@ function EID:ReplaceVariableStr(str, varID, newString) if type(str) ~= "string" or newString == nil then return str end return str:gsub("{"..varID.."}", newString) end + +-- deep table copy, copied from http://lua-users.org/wiki/CopyTable +function EID:CopyTable(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[EID:CopyTable(orig_key)] = EID:CopyTable(orig_value) + end + setmetatable(copy, EID:CopyTable(getmetatable(orig))) + else -- number, string, boolean, etc + copy = orig + end + return copy +end + +-- simple decimal rounding, instead of just floor or ceil +function EID:SimpleRound(num, dp) + dp = dp or 2 + local mult = 10^dp + return math.floor(num * mult + 0.5)/mult +end diff --git a/features/eid_conditionals.lua b/features/eid_conditionals.lua index e84daffc..919118b4 100644 --- a/features/eid_conditionals.lua +++ b/features/eid_conditionals.lua @@ -24,15 +24,26 @@ else EID:AddItemConditional("5.300", 451, nil, {locTable = "tarotClothBuffs", re -- Car Battery EID:AddItemConditional("5.100", 356, EID.CheckForCarBattery, {locTable = "carBattery", replaceColor = "BlinkYellowGreen", noFallback = false}) -EID:AddConditional(356, EID.CheckActivesForCarBattery, "No Effect") -- "No effect" text for Car Battery pedestal +EID:AddConditional(356, EID.CheckActivesForCarBattery, nil, {locTable = "carBattery", useResult = true, layer = 2}) -- Effect text for Car Battery pedestal +EID:AddConditional(356, EID.CheckActivesForNoCarBattery, "No Effect", {layer = 1}) -- "No effect" text for Car Battery pedestal --- BFFS! / Hive Mind + +-- BFFS! / Hive Mind / Forgotten Lullaby EID:AddItemConditional({"5.100","5.350.54","5.350.57"}, 247, EID.CheckForBFFS, {locTable = "BFFSSynergies", replaceColor = "BlinkPink", noFallback = false, uniqueID = "BFFS"}) -EID:AddConditional(247, EID.CheckFamiliarsForBFFS, "No Effect") -- "No effect" text for BFFS pedestal +EID:AddConditional(247, EID.CheckFamiliarsForBFFS, nil, {locTable = "BFFSSynergies", useResult = true, layer = 2}) -- Effect text for BFFS pedestal +EID:AddPlayerConditional(247, 13, "Lilith", {locTable = "BFFSSynergies", layer = 3}, false) -- Lilith's Incubus +EID:AddConditional(247, EID.CheckFamiliarsForNoBFFS, "No Effect", {layer = 1}) -- No effect text for BFFS pedestal if EID.isRepentance then EID:AddItemConditional({"5.300.96", "5.350.142", "5.350.176", "5.350.182", "5.350.186"}, 247, EID.CheckForBFFS, {locTable = "BFFSSynergies", replaceColor = "BlinkPink", noFallback = false}) -- BFFS! Repentance soulstone/trinkets + EID:AddPlayerConditional(247, 32, "Tainted Lilith", {locTable = "BFFSSynergies", layer = 3}) -- Tainted Lilith's Gello EID:AddItemConditional("5.100", 248, EID.CheckForHiveMind, {locTable = "BFFSSynergies", replaceColor = "BlinkBlue", noFallback = false, uniqueID = "BFFS"}) -- Hive Mind - EID:AddSynergyConditional(247, 248, "No Effect (Familiars)") -- Already having Hive Mind / BFFS! + EID:AddSynergyConditional(247, 248, "No Effect (Familiars)", {layer = 5}) -- Already having Hive Mind / BFFS! + EID:AddConditional(248, EID.CheckFamiliarsForHiveMind, nil, {locTable = "BFFSSynergies", useResult = true, layer = 2}) -- Effect text for Hive Mind pedestal + + EID:AddSynergyConditional({584, 685, 702, 728}, "5.350.141", "No Effect From", "No Effect") -- Forgotten Lullaby no effect familiars (wisps, Gello) + EID:AddPlayerConditional("5.350.141", 32, "No Effect", {bulletpoint = "Collectible728", variableText = "{{NameOnlyC728}}"}) -- Forgotten Lullaby no effect on Tainted Lilith's Gello + EID:AddPlayerConditional("5.350.141", 13, "Lullaby Lilith", nil, false) + EID:AddPlayerConditional("5.350.141", 26, "Lullaby Tainted Eve") end -- Abyss, Birthright Book of Belial, Binge Eater @@ -53,7 +64,8 @@ EID:AddConditional({266, "5.350.46", 385}, EID.IsGreedMode, "Room to Wave") -- append/overwrite some descriptions EID:AddConditional({483, 535, "5.300.15", "5.300.19", "5.300.20", 246, 333, "5.350.120", "5.350.34", "5.350.36", "5.350.41", "5.350.44", "5.350.45", "5.350.72", 514 }, EID.IsGreedMode) -- Mama Mega, Blanket, Temperance, The Moon, The Sun, Blue Map, The Mind, Hairpin, some nogreed trinkets, Broken Modem -EID:AddConditional({"5.350.5", 208}, EID.IsGreedMode, "No Champion Drops") +EID:AddConditional({"5.350.5", 208}, EID.IsGreedMode, "No Champion Drops") -- Champion Belt, Purple Heart +EID:AddConditional(297, EID.IsGreedMode, "Greed") -- Pandora's Box if EID.isRepentance then EID:AddConditional({567, 693, "5.350.163", "5.350.167", }, EID.IsGreedMode, "Room to Wave") @@ -85,6 +97,7 @@ EID:AddPlayerConditional(227, 14, "Keeper 0-1") -- Keeper + Piggy Bank EID:AddPlayerConditional(501, 14) -- Keeper + Greed's Gullet EID:AddPlayerConditional(230, 14, "Keeper") -- Keeper + Abaddon EID:AddPlayerConditional(152, 2, "Technology 2 One Eye") -- Cain + Technology 2 +EID:AddPlayerConditional(122, 5, nil, nil, false) -- Eve + Whore of Babylon if EID.isRepentance then -- Tainted characters reviving as themselves EID:AddPlayerConditional({ 161, "5.350.28" }, 25, "Tainted Revive") -- Ankh, Broken Ankh @@ -112,12 +125,12 @@ end ------ DUPLICATE COPIES OF ITEMS ------ --- Items that have no effect from multiple copies (AddSelfConditional adds it when holding Diplopia automatically) +-- Items that have no effect from multiple copies (AddSelfConditional adds it when holding Diplopia / Crooked Penny automatically) -- This list is mostly taken from Diplopia's wiki page, there's a lot more with no effect out there I'm sure EID:AddSelfConditional({7, 55, 63, 69, 87, 104, 106, 108, 109, 116, 122, 132, 139, 149, 152, 156, 162, 169, 191, 200, 203, 214, 221, 225, 231, 233, 238, 239, 243, 244, 247, 248, 249, 261, 276, 299, 300, 313, 316, 330, 350, 356, 366, 368, 371, 374, 379, 400, 401, 408, 411, 412, 414, 416, 423, 429, 444, 446, 447, 450, 451, 458, 494, 495, 496, 501, 524, 525, 529, 532, 533, 534, 540, 546, }, "No Effect (Copies)") -- Items that have an interesting effect from multiple copies EID:AddSelfConditional({2, 64, 118, 153, 245, 358}, "Copies") --- Items that can't be duplicated +-- Items that can't be duplicated by Diplopia EID:AddItemConditional(347, 347, "Can't Be Duplicated", nil, false) -- Diplopia if not EID.isRepentance then EID:AddSelfConditional({115, 222, 224, 228, 531}, "No Effect (Copies)") @@ -173,6 +186,7 @@ if EID.isRepentance then EID:AddSynergyConditional({52, 69, 118, 168, 229, 316, 329, 379, 394, 395, 397, 440, 556, 597, }, 579, "Overridden", "Overrides", {layer = 1000, checkLayers = true}) -- Spirit Sword EID:AddOneSidedSynergyConditional(579, 114, "Spirit Sword Mom's Knife", {layer = 1000, checkLayers = true}) -- Spirit Sword + Mom's Knife EID:AddOneSidedSynergyConditional(579, 68, "Spirit Sword Technology", {layer = 1000, checkLayers = true}) -- Spirit Sword + Technology + EID:AddOneSidedSynergyConditional(579, 149, "Spirit Sword Ipecac", {layer = 1000, checkLayers = true}) -- Spirit Sword + Ipecac EID:AddSynergyConditional({553, 572, 678, "5.350.144"}, 168, "Overridden", "Overrides", {layer = 900, checkLayers = true}) -- Epic Fetus EID:AddOneSidedSynergyConditional(561, 168, "Epic Fetus Soy Milk", {layer = 900, checkLayers = true}) -- Epic Fetus + Almond Milk @@ -192,9 +206,9 @@ end ----- MISC. ITEM CONDITIONS ------ -- IV Bag conditions (need to be applied in a specific order) -EID:AddItemConditional(135, 75, "PHD") -- PHD improves IV Bag -EID:AddPlayerConditional(135, 14, "Keeper 0-1") -- Keeper gets 0-1 coins -if EID.isRepentance then EID:AddConditional(135, EID.IsHardMode, "Hard Mode") end -- Hard Mode pays out less +EID:AddItemConditional(135, 75, "PHD", {layer = 3}) -- PHD improves IV Bag +EID:AddPlayerConditional(135, 14, "Keeper 0-1", {layer = 2}) -- Keeper gets 0-1 coins +if EID.isRepentance then EID:AddConditional(135, EID.IsHardMode, "Hard Mode", {layer = 1}) end -- Hard Mode pays out less -- Suicide item interactions EID:AddSynergyConditional({475, "5.300.46"}, {210, 276, 313}, "Suicide 1", "Suicide 2") -- Plan C, Suicide King + Isaac's Heart, Holy Mantle, Gnawed Leaf @@ -228,7 +242,7 @@ if EID.isRepentance then EID:AddConditional(647, function() return EID:CheckPlayersForActiveChargeType(nil, 1) end, "4.5 Volt Timed") -- 4.5 Volt + Having a timed active EID:AddConditional("5.100", function(EID, descObj) return EID:CheckActiveChargeType(descObj.ObjSubType, nil, 1, 647) end, "4.5 Volt Timed", { bulletpoint = "Collectible647"}) EID:AddItemConditional(534, 647, "4.5 Volt Multiple") -- Schoolbag + 4.5 Volt - EID:AddConditional(647, EID.CheckForMultipleActives, "4.5 Volt Multiple")-- 4.5 Volt + Schoolbag/Pocket Actives + EID:AddConditional(647, EID.CheckForMultipleChargeableActives, "4.5 Volt Multiple")-- 4.5 Volt + Schoolbag/Pocket Actives end -- Sacrificial Altar interactions @@ -265,6 +279,11 @@ EID:AddItemConditional({8, 113, 163, 167, 99, 100, 174, 95, 268, 67}, 322, "Mong if EID.isRepentance then EID:AddItemConditional(608, 322, "Mongo Babies") end EID:AddSynergyConditional(261, 222, "Proptosis Anti-Gravity") EID:AddSynergyConditional(394, 69, "Chocolate Milk Marked") +EID:AddConditional("5.300.5", EID.InStageVoid) -- The Emperor random boss room +EID:AddConditional("5.300.10", EID.InStageNoTreasureRoom) -- The Hermit possibly no shop +EID:AddConditional("5.300.18", EID.InStageNoTreasureRoom, nil, {layer = 3}) -- The Stars possibly no treasure room +EID:AddConditional("5.300.18", EID.IsGreedMode, "Greed", {layer = 2}) -- The Stars random treasure room +EID:AddConditional("5.300.18", EID.InStageTheShop, "Late Greed", {layer = 1}) -- The Stars no treasure room (I probably went too high detail on this) -- AB+ only misc conditionals if not EID.isRepentance then @@ -291,39 +310,49 @@ if EID.isRepentance then -- eye drops + chargeable passives like brimstone could go here but there's a lot of them EID:AddPlayerConditional(600, {2, 7, 13, 16}) -- Eye Drops + Cain, Azazel, Lilith, Forgotten EID:AddSynergyConditional(152, {708, 444}, "Technology 2 One Eye") -- Technology 2 + Stapler, Lead Pencil + EID:AddConditional(482, EID.CheckForTaintedPlayer) + EID:AddItemConditional(297, "5.350.175", "PandorasBoxStrangeKeyEffect", {noTable = true}) + + -- Jacob's Ladder / 120 Volt battery synergies + EID:AddSynergyConditional({494, 559}, {205, 356}, "Sparks Damage") + EID:AddSynergyConditional({494, 559}, {116, "5.350.72", "5.350.120"}, "Sparks Arc Length") + EID:AddSynergyConditional({494, 559}, {63, 520, "5.350.3"}, "Sparks Arc Count") + EID:AddSynergyConditional({494, 559}, {"5.350.143"}, "Sparks Arc Back") + + -- Ghost Pepper / Bird's Eye + farting (The Poop, Kidney Bean, The Bean, The Black Bean, Butter Bean, Wait What?, Mega Bean, No. 2, IBS) + EID:AddSynergyConditional(495, {36, 421, 111, 180, 294, 484, 351, 378}, "Ghost Pepper Fart", nil, {uniqueID = "pepperfart"}) + if EID.isRepentance then + EID:AddSynergyConditional(495, 725, "Ghost Pepper Fart", nil, {uniqueID = "pepperfart"}) + EID:AddSynergyConditional(616, {36, 421, 111, 180, 294, 484, 351, 378, 725}, "Ghost Pepper Fart", nil, {uniqueID = "pepperfart"}) + end end --[[ BUURAZU'S TODO STUFF: -Overridden / Overrides (Brimstone, Mom's Knife, Epic Fetus stuff) -Co-op benefit items (not sure what they are tbh) +Overridden / Overrides (Brimstone, Mom's Knife, Epic Fetus stuff) (still need some probably) Spoiler achievement descriptions (plus a config setting possibly, to always show the spoilers) (Chaos Card and Plan C?) -BFFs / Hive Mind + Tainted Apollyon, Tainted Eve, Tainted Lilith; melody trinket + Tainted Eve/Lilith; these are insane synergies that deserve attention Maybe a find/replace pair for some Keeper descriptions that talk about Red hearts but work for him, Red -> Coin or Red/Coin; Could be good for Dead Cat. or just leave it be -Crystal Key + Pandora's Box should be moved here? Or leave it in modifiers -Are Blank Card + Placebo + Clear Rune ready to get moved to here now that I have good ways to return values? -Jacob's Ladder synergies with other battery items (I didn't even know about this) -Bean synergies with ghost pepper/bird's eye -Car Battery / BFFS whitelist for showing certain lines on the car/bffs pedestal, not just "No effect" -Implement a system where similar texts don't get shown multiple times in one apply conditionals function (hive minds/bffs) hallowed ground / midas touch + the poop / card against humanity / everything jar synergies Add Void synergy information for some active items (which?) (warn on things like mom's box you don't get double trinket power?) Damage multipliers that don't stack -Clicker only changes Tainteds into other Tainteds? TODOS I'VE DECIDED NOT TODO RIGHT NOW: Incubus effects; a lot are AB+ only; annoying to test; plus Lilith has to be added for all of them too TODOs unrelated to conditionals: Luck modifier that prints out a percentage for the effect based on your current luck stat -Glyph of Balance prediction in Item Reminder? -Purity's current stat boost, if possible +What item is Liberty Cap, Broken Syringe, etc. currently giving? +RNG cheat for Crooked Penny Implement the standardized colors for characters, items, map names, etc -Character info tab in item reminder Make Glitched Crown type pedestals work better in EID (pause on one item and press tab to switch to the next item) Don't show Void stat ups depending on what Void has absorbed (abyss absorbed = no void stat ups etc, reroll item = might become actives) -Wild Card vurp-like desc modifier + +REPENTOGON TODOS: +Glyph of balance (test with bethany but repentogon's is probably always right) +Item reminder previews of immaculate/cambion progress (cambion's increases after two familiars; vardata like glowing hourglass?) +Purity's current stat boost +Attack formula simulator? (yeah right) MAYBE SOME DAYS: Bag of Crafting "sort by recipe nearest completion in bag" option diff --git a/features/eid_conditionals_funcs.lua b/features/eid_conditionals_funcs.lua index 83ae3200..2c4e4332 100644 --- a/features/eid_conditionals_funcs.lua +++ b/features/eid_conditionals_funcs.lua @@ -123,12 +123,13 @@ end Argument 4 (optional): Table with any additional options, usually not needed: variableText: If there's a {1} in the desc, it will be replaced with this; can be a function for more dynamic text results, it will get descObj passed into it locTable: Specify a different table to look for the localization string in, rather than ConditionalDescs - noTable: Set to true to look in the base localizations only (requires modText) (not used yet...) + noTable: Set to true to look in the base localizations only (requires modText) lineColor: Appended lines will be highlighted with this color replaceColor: Find/replace text will be highlighted with this color bulletpoint: Appended lines will begin with this bulletpoint noFallback: Don't fallback to English if this isn't localized; by default, conditionals don't fallback, to avoid printing English text in unupdated languages usePedestalName: If true, display the pedestal item's name in place of {1} + useResult: If true, use the result from the conditional function to find the localization string layer: A number used in determining the order that conditionals should be checked in, default -1 checkLayers: If true, don't print this condition if a higher layer condition was applied already uniqueID: Only one conditional with the given unique ID will be printed @@ -260,6 +261,19 @@ function EID:IsHardMode() return game.Difficulty == 1 or game.Difficulty == 3 end +function EID:InStageNum(stageNum) + return game:GetLevel():GetAbsoluteStage() == stageNum +end +function EID:InStageVoid() + return EID:InStageNum(12) +end +function EID:InStageNoTreasureRoom() + return game:GetLevel():GetAbsoluteStage() > 6 +end +function EID:InStageTheShop() + return EID:IsGreedMode() and EID:InStageNum(0) +end + -- Check if we have any characters that can't have Red Health, to print additions to descs like Dead Cat function EID:CheckForNoRedHealthPlayer() if EID.InsideItemReminder then @@ -276,26 +290,54 @@ function EID:CheckForNoRedHealthPlayer() return false end --- Check if we have characters with Schoolbag or a pocket active item -function EID:CheckForMultipleActives(onlyChargeablePockets) +function EID:CheckForTaintedPlayer() if EID.InsideItemReminder then local player = EID.ItemReminderPlayerEntity - if player:HasCollectible(534) then return true end - local id = player:GetPlayerType() - if EID.isRepentance and (EID.PocketActivePlayerIDs[id] == 0 or (not onlyChargeablePockets and EID.PocketActivePlayerIDs[id])) then return true end + return EID.TaintedIDs[player:GetPlayerType()] else for i = 0, game:GetNumPlayers() - 1 do local player = Isaac.GetPlayer(i) - if player:HasCollectible(534) then return true end + if EID.TaintedIDs[player:GetPlayerType()] then + return true + end + end + end + return false +end + +-- Check if we have characters with Schoolbag or a pocket active item +function EID:CheckForMultipleActives(descObj, onlyChargeablePockets) + if EID.InsideItemReminder then + local player = EID.ItemReminderPlayerEntity + if player:HasCollectible(534) then return 534 end + local id = player:GetPlayerType() + if EID.isRepentance and (EID.PocketActivePlayerIDs[id] == 0 or (not onlyChargeablePockets and EID.PocketActivePlayerIDs[id])) then return player:GetActiveItem(2) end + else + for i = 0, game:GetNumPlayers() - 1 do + local player = Isaac.GetPlayer(i) + if player:HasCollectible(534) then return 534 end local id = player:GetPlayerType() - if EID.isRepentance and (EID.PocketActivePlayerIDs[id] == 0 or (not onlyChargeablePockets and EID.PocketActivePlayerIDs[id])) then return true end + if EID.isRepentance and (EID.PocketActivePlayerIDs[id] == 0 or (not onlyChargeablePockets and EID.PocketActivePlayerIDs[id])) then return player:GetActiveItem(2) end end end return false end -- Check if we have characters with Schoolbag or a chargeable pocket active item (for 4.5 Volt) -function EID:CheckForMultipleChargeableActives() - return EID:CheckForMultipleActives(true) +function EID:CheckForMultipleChargeableActives(descObj) + return EID:CheckForMultipleActives(descObj, true) +end +-- Check if we have a character with a pocket active +function EID:CheckForPocketActives() + if EID.InsideItemReminder then + local player = EID.ItemReminderPlayerEntity + return EID.PocketActivePlayerIDs[player:GetPlayerType()] + else + for i = 0, game:GetNumPlayers() - 1 do + local player = Isaac.GetPlayer(i) + if EID.PocketActivePlayerIDs[player:GetPlayerType()] then return true end + end + end + return false end -- When we have Car Battery, change active pedestal descriptions @@ -303,8 +345,15 @@ function EID:CheckForCarBattery(descObj) if EID.CarBatteryNoSynergy[descObj.ObjSubType] then return "No Effect" end return descObj.ObjSubType end --- When we're looking at a Car Battery pedestal, check our actives for having no effect +-- When we're looking at a Car Battery pedestal, check our actives for having an effect or no effect function EID:CheckActivesForCarBattery(descObj) + if EID.InsideItemReminder then return false end + for k,v in pairs(EID.CarBatteryPedestalWhitelist) do + if v and EID:PlayersHaveCollectible(k) then return k end + end + return false +end +function EID:CheckActivesForNoCarBattery(descObj) if EID.InsideItemReminder then return false end for k,v in pairs(EID.CarBatteryNoSynergy) do if v and EID:PlayersHaveCollectible(k) then return k end @@ -324,8 +373,22 @@ function EID:CheckForHiveMind(descObj) return "N/A" end --- When we're looking at a BFFS pedestal, check our familiars for having no effect (only finds the earliest one, but whatever) +-- When we're looking at a BFFS pedestal, check our familiars for having an effect (only finds the earliest one) function EID:CheckFamiliarsForBFFS(descObj) + if EID.InsideItemReminder then return false end + for k,v in pairs(EID.BFFSPedestalWhitelist) do + if v and EID:PlayersHaveCollectible(k) then return "5.100." .. k end + end + return false +end +function EID:CheckFamiliarsForHiveMind(descObj) + if EID.InsideItemReminder then return false end + for k,v in pairs(EID.BFFSPedestalWhitelist) do + if EID.HiveMindFamiliars[k] and v and EID:PlayersHaveCollectible(k) then return "5.100." .. k end + end + return false +end +function EID:CheckFamiliarsForNoBFFS(descObj) if EID.InsideItemReminder then return false end for k,v in pairs(EID.BFFSNoSynergy) do if v and EID:PlayersHaveCollectible(k) then return k end @@ -336,7 +399,7 @@ end -- Check for a player having an active item with a specific quantity of charges, or charge type -- 0 = normal, 1 = timed, 2 = special function EID:CheckPlayersForActiveChargeType(maxCharge, chargeType, checkPockets) - checkPockets = checkPockets or true + if checkPockets == nil then checkPockets = true end local players = {} if EID.InsideItemReminder then players[1] = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player @@ -440,6 +503,7 @@ function EID:applyConditionals(descObj) local locTable = cond.locTable or "ConditionalDescs" local text = nil local modifierText = type(cond.modifierText) == "function" and cond.modifierText(EID, descObj) or cond.modifierText + if cond.useResult then modifierText = result end -- Find our string in the base localization table if cond.noTable then @@ -459,11 +523,11 @@ function EID:applyConditionals(descObj) if cond.layer > highestLayer then highestLayer = cond.layer end if cond.uniqueID then printedDescs[cond.uniqueID] = true end local variableText, bulletpoint - -- If the condition returned a value, use that value + -- If the condition returned a value, use that value as an item ID for the bulletpoint/{1} replacement if result ~= true then - -- For the time being, we assume the result was a collectible number (will support more soon) - variableText = "{{NameOnlyC"..result.."}}" - bulletpoint = "Collectible"..result + if type(result) == "number" then result = "5.100." .. result end + variableText = "{{NameOnly"..result.."}}" + bulletpoint = "Item"..result else variableText = type(cond.variableText) == "function" and cond.variableText(EID, descObj) or cond.variableText bulletpoint = cond.bulletpoint diff --git a/features/eid_data.lua b/features/eid_data.lua index 85e4bf1c..fface589 100644 --- a/features/eid_data.lua +++ b/features/eid_data.lua @@ -952,6 +952,18 @@ if EID.isRepentance then EID.CarBatteryNoSynergy[709] = true; EID.CarBatteryNoSynergy[710] = true; EID.CarBatteryNoSynergy[711] = true; EID.CarBatteryNoSynergy[714] = true; EID.CarBatteryNoSynergy[715] = true; EID.CarBatteryNoSynergy[728] = true; EID.CarBatteryNoSynergy[729] = true; end +-- Items that should show their Car Battery synergy while looking at a Car Battery pedestal +-- Void, Crooked Penny, Metronome, Moving Box, Broken Shovel +EID.CarBatteryPedestalWhitelist = { [477] = true, [485] = true, [488] = true, [523] = true, [550] = true } +if EID.isRepentance then + EID.CarBatteryPedestalWhitelist[523] = nil -- Moving Box + EID.CarBatteryPedestalWhitelist[386] = true -- D12 + EID.CarBatteryPedestalWhitelist[611] = true -- Larynx + EID.CarBatteryPedestalWhitelist[635] = true -- Stitches + EID.CarBatteryPedestalWhitelist[685] = true -- Jar of Wisps + EID.CarBatteryPedestalWhitelist[720] = true -- Everything Jar + EID.CarBatteryPedestalWhitelist[722] = true -- Anima Sola +end -- Familiars that have no effect from BFFS! EID.BFFSNoSynergy = { [10] = true, [11] = true, [81] = true, [178] = true, [238] = true, [239] = true, [243] = true, [265] = true, [268] = true, [269] = true, [276] = true, [278] = true, [280] = true, [281] = true, [387] = true, [404] = true, [431] = true, [433] = true, [436] = true, [467] = true, [469] = true, [472] = true, [492] = true, [504] = true, [516] = true, [528] = true, [542] = true, [543] = true } @@ -967,17 +979,33 @@ if EID.isRepentance then EID.BFFSNoSynergy[651] = true -- Star of Bethlehem EID.BFFSNoSynergy[697] = true -- Vanishing Twin end +-- Items that should show their BFFS / Hive Mind synergy while looking at a BFFS / Hive Mind pedestal +-- Charged Baby, Key Bum, Spider Mod, Succubus, Lil Spewer, Mystery Egg +EID.BFFSPedestalWhitelist = { [372] = true, [388] = true, [403] = true, [417] = true, [537] = true, [539] = true } +if EID.isRepentance then + EID.BFFSPedestalWhitelist[276] = true -- Isaac's Heart + EID.BFFSPedestalWhitelist[569] = true -- Blood Oath + EID.BFFSPedestalWhitelist[584] = true -- Book of Virtues + EID.BFFSPedestalWhitelist[612] = true -- Lost Soul + EID.BFFSPedestalWhitelist[635] = true -- Stitches + EID.BFFSPedestalWhitelist[685] = true -- Jar of Wisps + EID.BFFSPedestalWhitelist[702] = true -- Vengeful Spirit + EID.BFFSPedestalWhitelist[706] = true -- Abyss + EID.BFFSPedestalWhitelist[712] = true -- Lemegeton + EID.BFFSPedestalWhitelist[713] = true -- Sumptorium +end -- Familiars that count for Hive Mind in Repentance (although it could give them No Effect if it just increases size) EID.HiveMindFamiliars = { [10] = true, [57] = true, [128] = true, [170] = true, [264] = true, [272] = true, [274] = true, [279] = true, [320] = true, [364] = true, [365] = true, [403] = true, [426] = true, [430] = true, [504] = true, [511] = true, [575] = true, [581] = true, [629] = true, [649] = true, [650] = true, [706] = true, } -- Familiars that count for Hive Mind but should be ignored by BFFS (not used yet, maybe used by modded item conditionals) EID.BFFSIgnore = {} + -- Tainted character's respective normal version ID, for conditionals that apply to both versions of the character -- To help with other character pairs, Esau = Jacob, Dead Tainted Lazarus = Tainted Lazarus, Tainted Soul = Tainted Forgotten EID.TaintedToRegularID = { [20] = 19, [21] = 0, [22] = 1, [23] = 2, [24] = 3, [25] = 4, [26] = 5, [27] = 6, [28] = 7, [29] = 8, [30] = 9, [31] = 10, [32] = 13, [33] = 14, [34] = 15, [35] = 16, [36] = 18, [37] = 19, [38] = 29, [39] = 37, [40] = 35 } --- Player IDs of Tainted characters, might be useful for something +-- Player IDs of Tainted characters EID.TaintedIDs = {}; for i = 21, 40 do EID.TaintedIDs[i] = true end -- Character IDs that are Soul/Black Hearts only: ???, The Lost, The Soul diff --git a/features/eid_holdmapdesc.lua b/features/eid_holdmapdesc.lua index 5fafda53..885372af 100644 --- a/features/eid_holdmapdesc.lua +++ b/features/eid_holdmapdesc.lua @@ -10,14 +10,11 @@ EID.ItemReminderBlacklist = { ["5.100.714"] = true, ["5.100.715"] = true } -- Do EID.ItemReminderTempDescriptions = {} -- Temporary stores descriptions that will be displayed after everythng got evaluated EID.ItemReminderSelectedCategory = 0 -- Currently selected category EID.ItemReminderSelectedPlayer = 0 -- Currently selected player -EID.ItemReminderSelectedItem = 0 -- Currently selected Item +EID.ItemReminderSelectedItems = {} -- Currently selected Item per category +EID.ItemReminderDisplayingScrollbar = false -- For if the scrollbar is currently being drawn EID.InsideItemReminder = false -- Disable some modifiers, when building the Item Reminder description EID.ItemReminderPlayerEntity = nil -- for description modifiers to reference --- TODO: --- crooked penny cheats. 404/liberty cap/broken syringe/etc. "what item is it" --- pandora's box? it shows the whole desc which is kinda useful but too big - -- Data Tables -- @@ -25,50 +22,61 @@ EID.ItemReminderPlayerEntity = nil -- for description modifiers to reference -- Execution order is handles by the order of the table entries. -- Category name is interpreted as a lookup for the "EID.descriptions.ItemReminder.CategoryNames" table. If no translation is found, use english or the lookup value EID.ItemReminderCategories = { - { id = "Overview", entryGenerators = {} }, -- special handling for Overview category - { id = "Special", entryGenerators = { function(player) EID:ItemReminderHandlePoopSpells(player) end, } }, + { id = "Overview", entryGenerators = { } }, -- special handling for Overview category + { id = "Character", entryGenerators = { function(player) EID:ItemReminderHandleCharacterInfo(player) end, } }, + { id = "Special", entryGenerators = { function(player) EID:ItemReminderHandlePoopSpells(player) end, } }, + { id = "Wisps", + isScrollable = true, + entryGenerators = { function(player) EID:ItemReminderHandleLemegetonWisps(player) end, }, + scrollbarGenerator = function(player) + local playerNum = EID:getPlayerID(player, true) + return EID:ItemReminderHandleItemScrollbarFeature(EID.WispsPerPlayer[playerNum], 100, false) + end + }, { id = "Actives", entryGenerators = { function(player) EID:ItemReminderHandleActiveItems(player) end, } }, - { - id = "Pockets", + { id = "Pockets", entryGenerators = { function(player) EID:ItemReminderHandleDiceBag(player) end, function(player) EID:ItemReminderHandlePocketActive(player) end, function(player) EID:ItemReminderHandlePocketItems(player) end } }, - { - id = "Trinkets", - entryGenerators = { - function(player) EID:ItemReminderHandleTrinkets(player) end, - function(player) EID:ItemReminderHandleGulpedModelingClay(player) end } - }, - { - id = "Passives", + { id = "Trinkets", isScrollable = true, - entryGenerators = { - function(player) -- Passive item: Zodiac - if EID.isRepentance and player:HasCollectible(392) and not EID:IsCategorySelected("Passives") then - EID:ItemReminderAddDescription(player, 5, 100, 392) - end - end, - function(player) -- Passive item: Echo Chamber - if EID.isRepentance and player:HasCollectible(700) and not EID:IsCategorySelected("Passives") then - EID:ItemReminderAddDescription(player, 5, 100, 700) - end - end, - function(player) EID:ItemReminderHandleSelectedPassiveItem(player) end }, + entryGenerators = { function(player) EID:ItemReminderHandleTrinkets(player) end }, scrollbarGenerator = function(player) - local playerNum = EID:getPlayerID(player) - local numPassives = EID.RecentlyTouchedItems[playerNum] and #EID.RecentlyTouchedItems[playerNum] or 0 - if numPassives < 2 then return nil end -- dont display scrollbar if less than 2 items are in the table + return EID:ItemReminderHandleItemScrollbarFeature(EID:ItemReminderHeldPlusGulped(player), 350, true) + end + }, + { id = "Passives", + isScrollable = true, + entryGenerators = { function(player) EID:ItemReminderHandleSelectedPassiveItem(player) end }, + scrollbarGenerator = function(player) + local playerNum = EID:getPlayerID(player, true) return EID:ItemReminderHandleItemScrollbarFeature(EID.RecentlyTouchedItems[playerNum], 100, true) end }, } + +-- simple function to reset the selected items in the scrollbars. can specify a category name like "Passives" +function EID:ResetItemReminderSelectedItems(categoryName) + if not categoryName then + for i=0,#EID.ItemReminderCategories-1 do EID.ItemReminderSelectedItems[i] = 0 end + else + for i,category in ipairs(EID.ItemReminderCategories) do + if category.id == categoryName then EID.ItemReminderSelectedItems[i-1] = 0 return end + end + end +end +EID:ResetItemReminderSelectedItems() + +-- This table is for items that should have a special modifier applied to them when viewed in the item reminder -- Format: ItemID = table -- modifierFunction = function that modifies the original description object of the item -- isCheat = Only evaluate, if the "ItemReminderShowRNGCheats" config option is enabled -- isHiddenInfo = Only evaluate, if the "ItemReminderShowHiddenInfo" config option is enabled -- isRepentance = Only evaluate, if Repentance is installed +-- noOverview = If true, this modifier doesn't have special priority in the Overview category (most do) +-- Make sure to return true if the description was modified, so the overview knows EID.ItemReminderDescriptionModifier = { ---------------- Passive Items ---------------- ["5.100.392"] = { -- Zodiac @@ -80,18 +88,15 @@ EID.ItemReminderDescriptionModifier = { EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) descObj.Icon = demoDescObj.Icon descObj.Description = demoDescObj.Description + return true end end }, ["5.100.700"] = { -- Echo Chamber isRepentance = true, - modifierFunction = function(descObj, player) - local playerID = EID:getPlayerID(player) + modifierFunction = function(descObj, player, inOverview) + local playerID = EID:getPlayerID(player, true) local pickupHistory = EID.PlayerItemInteractions[playerID].pickupHistory - -- Dead Tainted Lazarus exception - if player:GetPlayerType() == 38 then - pickupHistory = EID.PlayerItemInteractions[playerID].altPickupHistory or pickupHistory - end if not pickupHistory then return end local pickupNames = "" local pickupsToPrint = 3 @@ -99,53 +104,131 @@ EID.ItemReminderDescriptionModifier = { if pickupHistory[i][4] then -- Echo chamber was owned before this card/pill was used and it's not a mimic usage if pickupHistory[i][1] == "pill" then local name = EID:getPillName(pickupHistory[i][3], false) - if (pickupHistory[i][2] == 14) then name = EID:getPillName(9999, false) end - pickupNames = pickupNames .. "{{Pill" .. pickupHistory[i][2] .. "}} " .. name .. "#" - pickupsToPrint = pickupsToPrint - 1 + if (pickupHistory[i][2] % 2048 == 14) then name = EID:getPillName(9999, false) end + pickupNames = pickupNames .. "{{Pill" .. pickupHistory[i][2] .. "}} {{ColorPill}}" .. name .. "#" else local name = EID:getObjectName(5, 300, pickupHistory[i][3]) - pickupNames = pickupNames .. "{{Card" .. pickupHistory[i][3] .. "}} " .. name .. "#" - pickupsToPrint = pickupsToPrint - 1 + pickupNames = pickupNames .. "{{Card" .. pickupHistory[i][3] .. "}} {{ColorCard}}" .. name .. "#" end + pickupsToPrint = pickupsToPrint - 1 if (pickupsToPrint == 0) then break end end end if pickupNames ~= "" then - EID:ItemReminderAddResultHeaderSuffix(descObj) - descObj.Description = pickupNames + EID:ItemReminderAddResult(descObj, pickupNames, inOverview) + return true end end }, + ["5.100.464"] = { -- Glyph of Balance + modifierFunction = function(descObj, player, inOverview) + local result = EID:GlyphOfBalancePrediction(player) + EID:ItemReminderAddResult(descObj, result, inOverview) + return true + end + }, ---------------- Active Items ---------------- ["5.100.44"] = { -- Teleport! location isCheat = true, - modifierFunction = function(descObj, player) + modifierFunction = function(descObj, player, inOverview) -- The result preview changes as soon as we activate Teleport, which looks awkward, so try to not display the result while mid-teleport. -- Doesn't work perfectly and only in Rep if not EID.isRepentance or player:GetSprite():GetAnimation() ~= "TeleportUp" then - EID:ItemReminderAddResultHeaderSuffix(descObj) - descObj.Description = EID:Teleport1Prediction(EID:GetItemSeed(player, 44)) + EID:ItemReminderAddResult(descObj, EID:Teleport1Prediction(EID:GetItemSeed(player, 44)), inOverview) + return true end end, }, + ["5.100.297"] = { -- Pandora's Box shortened description for overview + modifierFunction = function(descObj, player, inOverview) + if inOverview then + -- Crystal Key interaction takes priority + if EID.isRepentance and player:HasTrinket(175) then + local result = "{{Trinket175}} " .. EID:getDescriptionEntry("PandorasBoxStrangeKeyEffect") + EID:ItemReminderAddResult(descObj, result, inOverview) + return true + end + -- remove spaces after semicolons + descObj.Description = string.gsub(descObj.Description, "; ", ";") + descObj.Description = string.gsub(descObj.Description, "{{ColorBagComplete}} ", "{{ColorBagComplete}}") + -- find the highlighted text + local startLoc = string.find(descObj.Description, "{{ColorBagComplete}}") + if startLoc then + -- display the current result and the next floor's result; this is a bit complicated if you want to be truly accurate + local count = 2 + local skip = 0 + + local level = game:GetLevel() + local stageNum = level:GetAbsoluteStage() + local inAscent = EID.isRepentance and (level:IsAscent() or level:IsPreAscent()) + local final = not level:IsNextStageAvailable() + + if EID:IsGreedMode() and not EID.PandorasGreedConditional then + skip = 1 + if stageNum == 5 then skip = 0 -- Depths 2 -> Womb 1 + elseif stageNum == 7 or stageNum == 10 then skip = 2 + elseif stageNum == 0 then count = 1 end -- Womb 1 -> Sheol, Sheol -> Chest + else + if final or stageNum == 12 then count = 1 -- Dark Room/Chest/The Void/Home/Corpse 2 doesn't display any more + elseif EID:IsGreedMode() then count = 2 -- prevent Greed Mode Sheol from behaving weird + elseif stageNum == 8 then count = 3; skip = 1 -- Womb 2 displays Sheol and Cathe + elseif stageNum == 9 then count = 3 -- ??? displays Sheol and Cathe + elseif stageNum == 10 then skip = 1 end -- Sheol/Cathe skip one to display Dark Room/Chest + end + + local newDesc = "" + if inAscent then + -- Go through the description, copying the current line, until we find the highlighted line, appending the previous line to it + -- Exception: Basement 1 wants to append the final line (Home) so just keep iterating + local highlightLine = "" + local secondLine = "" + for w in string.gmatch(descObj.Description, "([^#;]+)") do + if string.find(w, "{{ColorBagComplete}}") then + highlightLine = w + if stageNum ~= 1 then break end + else + secondLine = w + end + end + newDesc = highlightLine .. "#" .. secondLine + else + local truncated = string.sub(descObj.Description, startLoc) + for w in string.gmatch(truncated, "([^#;]+)") do + -- skip lines after adding the highlighted line + if newDesc ~= "" and skip > 0 then + skip = skip - 1 + else + newDesc = newDesc .. w + count = count - 1 + if count == 0 then break + else newDesc = newDesc .. "#" end + end + end + end + EID:ItemReminderAddResult(descObj, newDesc, inOverview) + return true + end + end + end + }, ["5.100.419"] = { -- Teleport 2.0 location - modifierFunction = function(descObj, _) - EID:ItemReminderAddResultHeaderSuffix(descObj) - descObj.Description = EID:Teleport2Prediction() + modifierFunction = function(descObj, _, inOverview) + EID:ItemReminderAddResult(descObj, EID:Teleport2Prediction(), inOverview) + return true end }, ["5.100.476"] = { -- D1 isCheat = true, - modifierFunction = function(descObj, player) - EID:ItemReminderAddResultHeaderSuffix(descObj) - descObj.Description = EID:D1Prediction(EID:GetItemSeed(player, 476)) + modifierFunction = function(descObj, player, inOverview) + EID:ItemReminderAddResult(descObj, EID:D1Prediction(EID:GetItemSeed(player, 476)), inOverview) + return true end }, ["5.100.477"] = { -- Void - modifierFunction = function(descObj, player) - local absorbedItems = EID.absorbedItems[tostring(EID:getPlayerID(player))] + modifierFunction = function(descObj, player, inOverview) + local absorbedItems = EID.absorbedItems[tostring(EID:getPlayerID(player, true))] if absorbedItems then local descriptionText = "" local countItems = 0 @@ -162,8 +245,8 @@ EID.ItemReminderDescriptionModifier = { descriptionText = descriptionText .. EID:getObjectName(5, 100, tonumber(k)) .. "#" end end - EID:ItemReminderAddResultHeaderSuffix(descObj) - descObj.Description = descriptionText + EID:ItemReminderAddResult(descObj, descriptionText, inOverview) + return true end end end @@ -176,6 +259,7 @@ EID.ItemReminderDescriptionModifier = { EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) descObj.Icon = demoDescObj.Icon descObj.Description = demoDescObj.Description + return true end }, ["5.100.489"] = { -- D Infinity @@ -185,11 +269,12 @@ EID.ItemReminderDescriptionModifier = { EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) descObj.Icon = demoDescObj.Icon descObj.Description = demoDescObj.Description + return true end }, ["5.100.710"] = { -- Bag of Crafting isRepentance = true, - modifierFunction = function(descObj, _) + modifierFunction = function(descObj, _, inOverview) local floorQuery = EID.BoC.FloorOverride or EID.BoC.FloorQuery local inventoryQuery = EID.BoC.InventoryOverride or EID.BoC.InventoryQuery local bagItems = EID.BoC.BagItemsOverride or EID.BoC.BagItems @@ -203,27 +288,34 @@ EID.ItemReminderDescriptionModifier = { local hideButton = controllerEnabled and EID.ButtonToIconMap[EID.Config["CraftingHideButton"]] text = text .. "#!!! ".. EID:ReplaceVariableStr(EID:getDescriptionEntry("CraftingIsHidden"), 1, (hideKey or hideButton)) end - descObj.Description = text + if inOverview then + descObj.Description = text + else + descObj.Description = descObj.Description .. "#" .. text + end + return true end }, ---------------- TRINKETS ---------------- ["5.350.4"] = { -- Broken Remote has two possible effects depending on if its doubled - modifierFunction = function(descObj, player) + modifierFunction = function(descObj, player, inOverview) local originalName = descObj.Name if player:GetTrinketMultiplier(4) > 1 then -- Teleport 2.0 local demoDescObj = EID:getDescriptionObj(5, 100, 419) - EID.ItemReminderDescriptionModifier["5.100.419"].modifierFunction(descObj, player) + EID.ItemReminderDescriptionModifier["5.100.419"].modifierFunction(descObj, player, inOverview) descObj.Name = originalName - EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) + if inOverview then EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) end + return true elseif EID.Config["ItemReminderShowRNGCheats"] then -- Teleport local demoDescObj = EID:getDescriptionObj(5, 100, 44) - EID.ItemReminderDescriptionModifier["5.100.44"].modifierFunction(descObj, player) + EID.ItemReminderDescriptionModifier["5.100.44"].modifierFunction(descObj, player, inOverview) descObj.Name = originalName - EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) + if inOverview then EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) end + return true end end }, @@ -237,6 +329,7 @@ EID.ItemReminderDescriptionModifier = { EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) descObj.Icon = demoDescObj.Icon descObj.Description = demoDescObj.Description + return true end }, ["5.350.75"] = { -- 404 Error @@ -249,17 +342,22 @@ EID.ItemReminderDescriptionModifier = { EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) descObj.Icon = demoDescObj.Icon descObj.Description = demoDescObj.Description + return true end }, ["5.350.166"] = { -- Modeling Clay isRepentance = true, modifierFunction = function(descObj, player) - local modelingClayItem = player:GetModelingClayEffect() - if modelingClayItem > 0 then - local demoDescObj = EID:getDescriptionObj(5, 100, modelingClayItem) - EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) - descObj.Icon = "{{Collectible" .. modelingClayItem .. "}}" - descObj.Description = demoDescObj.Description + -- When gulped, Modeling Clay's current item becomes Hidden Information + if EID.Config["ItemReminderShowHiddenInfo"] or player:GetTrinket(0) == 166 or player:GetTrinket(1) == 166 then + local modelingClayItem = player:GetModelingClayEffect() + if modelingClayItem > 0 then + local demoDescObj = EID:getDescriptionObj(5, 100, modelingClayItem) + EID:ItemReminderAddResultHeaderSuffix(descObj, demoDescObj.Name) + descObj.Icon = "{{Collectible" .. modelingClayItem .. "}}" + descObj.Description = demoDescObj.Description + return true + end end end }, @@ -290,9 +388,25 @@ function EID:ItemReminderAddResultHeaderSuffix(descObj, newName) descObj.Name = descObj.Name .. EID:getDescriptionEntry("ItemReminder", "ResultHeader") end end +-- Adds a formatted "Result" text appended to the item description +function EID:ItemReminderAddResultAppend(descObj, newText) + local iconString = EID:GetIconStringByDescriptionObject(descObj) + descObj.Description = descObj.Description .. "#" .. iconString .. " {{ColorObjName}}" .. descObj.Name .. EID:getDescriptionEntry("ItemReminder", "ResultHeader") .. "#" .. newText +end +-- Helper function for modifiers that want to replace the description in the overview, but append to the description otherwise +function EID:ItemReminderAddResult(descObj, newText, inOverview) + if inOverview then + EID:ItemReminderAddResultHeaderSuffix(descObj) + descObj.Description = newText + else + EID:ItemReminderAddResultAppend(descObj, newText) + end +end -- Add item description for a given entity to the reminder. Also tries to apply special modifiers if present function EID:ItemReminderAddDescription(player, entityType, variant, subType, extraIcon) + -- If given an item ID string, split it into type/variant/subtype + if type(entityType) == "string" then entityType, variant, subType = EID:SplitTVS(entityType) end local objectID = entityType .. "." .. variant .. "." .. subType if currentBlacklist[objectID] then return true end @@ -315,13 +429,41 @@ function EID:ItemReminderAddDescription(player, entityType, variant, subType, ex return EID:ItemReminderAddTempDescriptionEntry(iconString, descObj.Name, descObj.Description) end --- Recently Acquired Item Descriptions +-- In the overview category, check for special descriptions +function EID:ItemReminderAddSpecialDescriptions(player) + for objectID,specialDesc in pairs(EID.ItemReminderDescriptionModifier) do + local evaluateFunction = true + if (not EID:PlayerHasItem(player, objectID)) then evaluateFunction = false end + if (specialDesc.isCheat and not EID.Config["ItemReminderShowRNGCheats"]) then evaluateFunction = false end + if (specialDesc.isHiddenInfo and not EID.Config["ItemReminderShowHiddenInfo"]) then evaluateFunction = false end + if (specialDesc.isRepentance and not EID.isRepentance) then evaluateFunction = false end + if evaluateFunction then + local Type, Var, Sub = EID:SplitTVS(objectID) + local descObj = EID:getDescriptionObj(Type, Var, Sub) + if specialDesc.modifierFunction(descObj, player, true) then + currentBlacklist[objectID] = true + local iconString = EID:GetIconStringByDescriptionObject(descObj) + EID:ItemReminderAddTempDescriptionEntry(iconString, descObj.Name, descObj.Description) + end + end + if not EID:ItemReminderCanAddMoreToView() then return end + end +end + +-- Information about the player character's traits +function EID:ItemReminderHandleCharacterInfo(player) + local playerType = player:GetPlayerType() + local playerDesc = EID:getDescriptionEntry("CharacterInfo")[playerType] + if playerDesc[2] ~= "" then + EID:ItemReminderAddTempDescriptionEntry("{{Player" .. playerType .. "}}", playerDesc[1], playerDesc[2]) + end +end + +-- Passive Item Descriptions function EID:ItemReminderHandleSelectedPassiveItem(player) - local playerNum = EID:getPlayerID(player) + local playerNum = EID:getPlayerID(player, true) if EID.RecentlyTouchedItems[playerNum] and #EID.RecentlyTouchedItems[playerNum] > 0 then - local index = EID:IsCategorySelected("Passives") and EID.ItemReminderSelectedItem % #EID.RecentlyTouchedItems[playerNum] or 0 - local recentID = EID.RecentlyTouchedItems[playerNum][#EID.RecentlyTouchedItems[playerNum] - index] % GLITCH_ITEM_FLAG - EID:ItemReminderAddDescription(player, 5, 100, recentID) + EID:ItemReminderHandleItemPrinting(player, EID.RecentlyTouchedItems[playerNum], 100, true) end end @@ -378,33 +520,27 @@ function EID:ItemReminderHandlePocketItems(player) end end +-- Held trinkets are added at the end of the table; use this table in descending order +function EID:ItemReminderHeldPlusGulped(player) + local playerNum = EID:getPlayerID(player, true) + local newTable = {} + if EID.GulpedTrinkets[playerNum] then newTable = {table.unpack(EID.GulpedTrinkets[playerNum])} end + for i=0, 1 do + local trinket = player:GetTrinket(i) + if trinket > 0 then table.insert(newTable, trinket) end + end + return newTable +end -- Trinket Descriptions function EID:ItemReminderHandleTrinkets(player) - for t = 0, 1 do - if not EID:ItemReminderCanAddMoreToView() then return end - -- account for Golden Trinket IDs - local heldTrinketTrue = player:GetTrinket(t) - local heldTrinket = heldTrinketTrue - if EID.isRepentance then heldTrinket = heldTrinketTrue & TrinketType.TRINKET_ID_MASK end - if heldTrinket > 0 then - EID:ItemReminderAddDescription(player, 5, 350, heldTrinketTrue) - end - end -end - --- Gulped/unslotted Modeling Clay --- (Hidden information, because Modeling Clay does not visually show its item when gulped) -function EID:ItemReminderHandleGulpedModelingClay(player) - if EID.isRepentance and EID.Config["ItemReminderShowHiddenInfo"] and player:GetModelingClayEffect() > 0 and EID:ItemReminderCanAddMoreToView() then - EID:ItemReminderAddDescription(player, 5, 350, 166) - end + EID:ItemReminderHandleItemPrinting(player, EID:ItemReminderHeldPlusGulped(player), 350, true) end -- Tainted ??? Poop Descriptions function EID:ItemReminderHandlePoopSpells(player) if EID.isRepentance and player:GetPlayerType() == 25 and EID:ItemReminderCanAddMoreToView() then local poopInfo = EID:getDescriptionEntry("poopSpells") - for i = 0, 3 do + for i = 0, numAvailableDescriptionSlots - 1 do local nextPoop = player:GetPoopSpell(i) EID:ItemReminderAddTempDescriptionEntry("{{PoopSpell" .. nextPoop .. "}}", poopInfo[nextPoop][1], poopInfo[nextPoop][2]) @@ -412,6 +548,12 @@ function EID:ItemReminderHandlePoopSpells(player) end end +-- Lemegeton wisp descriptions +function EID:ItemReminderHandleLemegetonWisps(player) + local playerNum = EID:getPlayerID(player, true) + EID:ItemReminderHandleItemPrinting(player, EID.WispsPerPlayer[playerNum], 100, false) +end + -- Handle scroll inputs function EID:ItemReminderHandleInputs() if EID.Config["ItemReminderEnabled"] and EID.Config["ItemReminderDisplayMode"] ~= "Classic" and EID.holdTabCounter >= 30 and EID.TabDescThisFrame == false and EID.holdTabPlayer ~= nil then @@ -434,19 +576,19 @@ function EID:ItemReminderHandleInputs() lastInputTime = Isaac.GetTime() lastScrollDirection = 1 elseif Input.IsActionTriggered(EID.Config["ItemReminderNavigateUpButton"], EID.holdTabPlayer.ControllerIndex) and Isaac.GetTime() - lastInputTime > 50 then - if not EID:IsScrollableCategorySelected() then + if not EID.ItemReminderDisplayingScrollbar then EID.ItemReminderSelectedPlayer = (EID.ItemReminderSelectedPlayer - 1) % #EID:ItemReminderGetAllPlayers() else - EID.ItemReminderSelectedItem = EID.ItemReminderSelectedItem - 1 -- clamp later + EID.ItemReminderSelectedItems[EID.ItemReminderSelectedCategory] = EID.ItemReminderSelectedItems[EID.ItemReminderSelectedCategory] - 1 -- clamp later end EID.ForceRefreshCache = true lastInputTime = Isaac.GetTime() elseif Input.IsActionTriggered(EID.Config["ItemReminderNavigateDownButton"], EID.holdTabPlayer.ControllerIndex) and Isaac.GetTime() - lastInputTime > 50 then - if not EID:IsScrollableCategorySelected() then + if not EID.ItemReminderDisplayingScrollbar then EID.ItemReminderSelectedPlayer = (EID.ItemReminderSelectedPlayer + 1) % #EID:ItemReminderGetAllPlayers() else - EID.ItemReminderSelectedItem = EID.ItemReminderSelectedItem + 1 -- clamp later + EID.ItemReminderSelectedItems[EID.ItemReminderSelectedCategory] = EID.ItemReminderSelectedItems[EID.ItemReminderSelectedCategory] + 1 -- clamp later end EID.ForceRefreshCache = true @@ -485,10 +627,12 @@ function EID:ItemReminderHandleInitHoldTab() break end end - + + EID:UpdateAllPlayerLemegetonWisps() + EID:UpdateAllPlayerTrinkets() local updatedPlayers = EID:UpdateAllPlayerPassiveItems() if updatedPlayers[EID.ItemReminderSelectedPlayer + 1] or oldDisplayPlayer ~= EID.ItemReminderSelectedPlayer then - EID.ItemReminderSelectedItem = 0 + EID:ResetItemReminderSelectedItems("Passives") end end @@ -516,7 +660,7 @@ function EID:ItemReminderGetTitle() local playerIcon = EID:GetPlayerIcon(currentPlayer:GetPlayerType(), "P" .. curPlayerID ) local playerSelectWidget = playerIcon .. " " - if not EID:IsScrollableCategorySelected() and EID.Config["ItemReminderDisplayMode"] ~= "Classic" then + if not EID.ItemReminderDisplayingScrollbar and EID.Config["ItemReminderDisplayMode"] ~= "Classic" then playerSelectWidget = EID.ButtonToIconMap[EID.Config["ItemReminderNavigateUpButton"]] .. playerIcon .. EID.ButtonToIconMap[EID.Config["ItemReminderNavigateDownButton"]] .. "|" end @@ -542,15 +686,18 @@ end -- takes a list of item ids and an item variant to automatically generate a scrollable list of item icons. function EID:ItemReminderHandleItemScrollbarFeature(entryTable, itemVariant, descending) + if entryTable == nil or #entryTable <= EID.Config["ItemReminderMaxEntriesCount"] then return nil end + -- handle Scrollable passives list - local newDescription = "{{Blank}} " .. EID.ButtonToIconMap[EID.Config["ItemReminderNavigateUpButton"]] + local newDescription = "{{Blank}} {{NoLB}}" .. EID.ButtonToIconMap[EID.Config["ItemReminderNavigateUpButton"]] + local selectedItem = EID.ItemReminderSelectedItems[EID.ItemReminderSelectedCategory] - EID.ItemReminderSelectedItem = EID.ItemReminderSelectedItem % #entryTable -- clamp selection + selectedItem = selectedItem % #entryTable -- clamp selection -- render icons if descending then - local startIndex = #entryTable - EID.ItemReminderSelectedItem -- start from end of list - local stopIndex = startIndex - 3 + local startIndex = #entryTable - selectedItem -- start from end of list + local stopIndex = startIndex - (EID.Config["ItemReminderMaxEntriesCount"] + 1) for i = startIndex, stopIndex, -1 do local index = ((i - 1) % #entryTable) + 1 @@ -563,8 +710,8 @@ function EID:ItemReminderHandleItemScrollbarFeature(entryTable, itemVariant, des newDescription = newDescription .. "{{" .. EID:GetIconNameByVariant(itemVariant) .. recentID .. "}} " end else - local startIndex = EID.ItemReminderSelectedItem -- start from end of list - local stopIndex = startIndex + 3 + local startIndex = selectedItem -- start from end of list + local stopIndex = startIndex + (EID.Config["ItemReminderMaxEntriesCount"] + 1) for i = startIndex, stopIndex do local index = (i % #entryTable) + 1 local recentID = entryTable[index] % GLITCH_ITEM_FLAG @@ -577,11 +724,33 @@ function EID:ItemReminderHandleItemScrollbarFeature(entryTable, itemVariant, des end end -- add counter - newDescription = newDescription .. "(" .. (EID.ItemReminderSelectedItem + 1) .. "/" .. #entryTable .. ") " + newDescription = newDescription .. "(" .. (selectedItem + 1) .. "/" .. #entryTable .. ")" return newDescription .. EID.ButtonToIconMap[EID.Config["ItemReminderNavigateDownButton"]] .. "#" end +-- takes a list of item ids and an item variant to automatically print the correct amount of descriptions +function EID:ItemReminderHandleItemPrinting(player, entryTable, itemVariant, descending) + if not EID:ItemReminderCanAddMoreToView() or entryTable == nil or #entryTable == 0 then return end + + local descsToDisplay = numAvailableDescriptionSlots + if #entryTable < descsToDisplay then descsToDisplay = #entryTable end + + if descending then + local startIndex = (#entryTable - EID.ItemReminderSelectedItems[EID.ItemReminderSelectedCategory] - 1) % #entryTable + for i = 1, descsToDisplay do + EID:ItemReminderAddDescription(player, 5, itemVariant, entryTable[startIndex+1] % GLITCH_ITEM_FLAG) + startIndex = (startIndex - 1) % #entryTable + end + else + local startIndex = EID.ItemReminderSelectedItems[EID.ItemReminderSelectedCategory] % #entryTable + for i = 1, descsToDisplay do + EID:ItemReminderAddDescription(player, 5, itemVariant, entryTable[startIndex+1] % GLITCH_ITEM_FLAG) + startIndex = (startIndex + 1) % #entryTable + end + end +end + function EID:ItemReminderGetDescription() EID.InsideItemReminder = true EID.ItemReminderTempDescriptions = {} @@ -593,11 +762,17 @@ function EID:ItemReminderGetDescription() EID.ItemReminderPlayerEntity = player -- for description modifiers to reference if EID.ItemReminderSelectedCategory == 0 or EID.Config["ItemReminderDisplayMode"] == "Classic" then + -- execute special previews / item results while in the overview + numAvailableDescriptionSlots = EID.Config["ItemReminderMaxEntriesCount"] + EID:ItemReminderAddSpecialDescriptions(player) -- execute all functions defined per category for _, category in ipairs(EID.ItemReminderCategories) do - numAvailableDescriptionSlots = 1 -- limit to one description per category in overview mode - for _, func in ipairs(category.entryGenerators) do - func(player) + -- don't display character info in the overview after a few rooms + if category.id ~= "Character" or EID.roomCount < 5 then + numAvailableDescriptionSlots = 1 -- limit to one description per category in overview mode + for _, func in ipairs(category.entryGenerators) do + func(player) + end end end else @@ -638,13 +813,15 @@ function EID:ItemReminderGetDescription() end autoScrollTriesLeft = -999 - local finalHoldMapDesc = "{{Blank}}#" + local finalHoldMapDesc = "" + EID.ItemReminderDisplayingScrollbar = false local category = EID.ItemReminderCategories[EID.ItemReminderSelectedCategory + 1] if EID:IsScrollableCategorySelected() then local scrollbar = category.scrollbarGenerator(player) if scrollbar then finalHoldMapDesc = scrollbar + EID.ItemReminderDisplayingScrollbar = true end end -- roughly estimate space the descriptions would occupy. trim descriptions if nessesary diff --git a/features/eid_itemprediction.lua b/features/eid_itemprediction.lua index c1cc7ac3..8285ae9b 100644 --- a/features/eid_itemprediction.lua +++ b/features/eid_itemprediction.lua @@ -28,7 +28,7 @@ function EID:CurrentDInfinity(rng, player) rng = EID:RNGNext(rng, 0x1, 0x9, 0x1D) -- magic disassembled numbers! return dinfinityList[rng % 7] else - local playerID = EID:getPlayerID(player) + local playerID = EID:getPlayerID(player, true) return dinfinityList[EID.DInfinityState[playerID]] or 476 end end @@ -42,7 +42,7 @@ if EID.isRepentance then function EID:WatchForDice(collectibleType, _, player) if not justDidD or collectibleType == 489 then return end justDidD = false - local playerID = EID:getPlayerID(player) + local playerID = EID:getPlayerID(player, true) for i,v in ipairs(dinfinityList) do if v == collectibleType then EID.DInfinityState[playerID] = i end end @@ -329,3 +329,70 @@ function EID:D1Prediction(rng) local pickupNames = EID:getDescriptionEntry("PickupNames") or {} return pickupNames[fullID] or EID:GetEntityXMLNameByString(fullID) or fullID end + + +-- Glyph of Balance -- +function EID:GlyphOfBalancePrediction(player) + local fullID = "5.0" + + local playerID = player:GetPlayerType() + local skipHearts = EID.isRepentance and EID.NoRedHeartsPlayerIDs[playerID] -- Soul Heart characters can't get hearts in Repentance + local skipSoulHearts = EID.isRepentance and (playerID == 18 or playerID == 14 or playerID == 33) -- Bethany and Keepers can't get Soul Hearts with 6 or less containers in Repentance + local skipBombs = EID.isRepentance and playerID == 25 -- Tainted ??? (the only character that can't have Bombs) + + -- Soul Hearts: When Isaac has no Red Heart containers and less than 2 Soul Hearts. + if not skipHearts and player:GetEffectiveMaxHearts() == 0 and player:GetSoulHearts() < 4 then + fullID = "5.10.3" + -- Full Red Hearts: While at 0.5 Red Hearts. + -- In AB+, you seem to have to have at least one non-Bone container to trigger this condition + elseif not skipHearts and player:CanPickRedHearts() and player:GetHearts() <= 1 and (EID.isRepentance or player:GetMaxHearts() > 0) then + fullID = "5.10.1" + -- Keys: When Isaac has no keys. + elseif player:GetNumKeys() == 0 then + fullID = "5.30.1" + -- Bombs: When Isaac has no bombs. + elseif not skipBombs and player:GetNumBombs() == 0 then + fullID = "5.40.1" + -- Big Poop Nugget: When Tainted ??? has no poops + elseif skipBombs and player:GetPoopMana() == 0 then + fullID = "5.42.1" + -- Soul Hearts: When Bethany has no soul charges + elseif EID.isRepentance and playerID == 18 and player:GetSoulCharge() == 0 then + fullID = "5.10.3" + -- Red Hearts: When Tainted Bethany has no blood charges + elseif EID.isRepentance and playerID == 36 and player:GetBloodCharge() == 0 then + fullID = "5.10.1" + -- Full Red Hearts: When Isaac has at least half a Red Heart container empty. + elseif not skipHearts and player:CanPickRedHearts() then + fullID = "5.10.1" + -- Pennies: While Isaac has less than 15 pennies. + elseif player:GetNumCoins() < 15 then + fullID = "5.20.1" + -- Keys: When Isaac has less than 5 keys. + elseif player:GetNumKeys() < 5 then + fullID = "5.30.1" + -- Bombs: When Isaac has less than 5 bombs. + elseif not skipBombs and player:GetNumBombs() < 5 then + fullID = "5.40.1" + -- Trinkets: If Isaac has no trinkets and there are no trinkets on the ground in the room. + elseif player:GetTrinket(0) == 0 and #Isaac.FindByType(5, 350) == 0 then + fullID = "5.350" + -- Big Poop Nugget: When Tainted ??? has less than 5 poops + elseif skipBombs and player:GetPoopMana() < 5 then + fullID = "5.42.1" + -- Soul Hearts: When Bethany has less than 12 soul charges + elseif EID.isRepentance and playerID == 18 and player:GetSoulCharge() < 12 then + fullID = "5.10.3" + -- Red Hearts: When Tainted Bethany has less than 12 blood charges + elseif EID.isRepentance and playerID == 36 and player:GetBloodCharge() < 12 then + fullID = "5.10.1" + -- Soul Hearts: When Isaac has less than 6 total Heart containers of any kind. + elseif not skipHearts and not skipSoulHearts and player:GetHearts() + player:GetSoulHearts() < 12 then + fullID = "5.10.3" + end + + --todo: in REPENTOGON, replace this whole function (in the eid_repentogon lua style) + local pickupNames = EID:getDescriptionEntry("PickupNames") or {} + return pickupNames[fullID] or EID:GetEntityXMLNameByString(fullID) or fullID +end + diff --git a/features/eid_mcm.lua b/features/eid_mcm.lua index b7249970..14a80133 100644 --- a/features/eid_mcm.lua +++ b/features/eid_mcm.lua @@ -583,7 +583,7 @@ if MCMLoaded then EID:AddNumberSetting("Reminder", "ItemReminderDisplayMode", "Display Mode", 1, #itemReminderModes, {indexOf = itemReminderModes, infoText = "Changes the display mode. All = overview + categories. NoOverview = categories. Classic = Overview only", onChangeFunc = function(currentNum) EID.MCM_OptionChanged = true - EID.ItemReminderSelectedItem = 0 + EID:ResetItemReminderSelectedItems() EID.ItemReminderSelectedCategory = currentNum ~= 2 and 0 or 1 -- reset category EID.Config["ItemReminderDisplayMode"] = itemReminderModes[currentNum] end}) diff --git a/features/eid_modifiers.lua b/features/eid_modifiers.lua index 02582af1..d230bb04 100644 --- a/features/eid_modifiers.lua +++ b/features/eid_modifiers.lua @@ -137,9 +137,9 @@ local pandorasStages = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 11, 11, 13 } local altStages = { [10] = false, [11] = true, [12] = false, [13] = true } -- Greed Mode Absolute Stage / Pandora's Box Behavior: 1: B2, 3: C2, 5: D2, 7: W1, 10: Sheol, 0: Chest local greedStages = { -1, 1, -1, 3, -1, 5, 7, -1, -1, 10, -1, -1, 0, -1 } +EID.PandorasGreedConditional = false -- helper variable to let item reminder know that we have the 6-entry greed pandora desc local function PandorasBoxCallback(descObj) - local strangeKeyOwned = EID.isRepentance and EID:PlayersHaveTrinket(175) local pandoraCount = 0 local level = game:GetLevel() local stageNum = level:GetAbsoluteStage() @@ -157,10 +157,15 @@ local function PandorasBoxCallback(descObj) end end local skip9and12 = false + EID.PandorasGreedConditional = false -- many localizations do not have the ???/Void entry and the Dark Room entry -- this seemed better than forcing them to have it if totalCount == (EID.isRepentance and 12 or 11) then skip9and12 = true + -- If the localization has a simplified Greed Mode desc, it will only have 6 entries + elseif totalCount == 6 then + EID.PandorasGreedConditional = true + greedStages = { 1, 3, 5, 7, 10, 0 } end for w in string.gmatch(descObj.Description, "([^#;]+)") do @@ -185,9 +190,6 @@ local function PandorasBoxCallback(descObj) if pandoraCount == (EID.isRepentance and 14 or 13) then break end end end - if strangeKeyOwned then - descObj.Description = descObj.Description .. "#{{Trinket175}} " .. EID:getDescriptionEntry("PandorasBoxStrangeKeyEffect") - end return descObj end @@ -242,6 +244,40 @@ local function BlackFeatherCallback(descObj) return descObj end +-- Handle VURP description addition +local function VurpCallback(descObj) + local adjustedID = EID:getAdjustedSubtype(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType) + if adjustedID - 1 ~= PillEffect.PILLEFFECT_VURP then return descObj end + + for i = 1,#EID.coopAllPlayers do + local player = EID.coopAllPlayers[i] + local playerID = EID:getPlayerID(player, true) + local playerType = player:GetPlayerType() + local pickupHistory = EID.PlayerItemInteractions[playerID].pickupHistory + if pickupHistory then + local lastUsedPill = PillEffect.PILLEFFECT_VURP + 1 + local lastUsedPillColor = 1 + local j = 1 + while (j <= #pickupHistory) do + local entry = pickupHistory[j] + -- ignore the pill if the pill color is Golden + if entry[1] == "pill" and entry[2] % 2048 ~= 14 then + lastUsedPill = entry[3] + lastUsedPillColor = entry[2] + break + end + j = j + 1 + end + local tableName = EID:getTableName(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType) + local name = EID:getPillName(lastUsedPill, tableName == "horsepills") + if #EID.coopAllPlayers == 1 then EID:appendToDescription(descObj, "#{{Pill" .. lastUsedPillColor .. "}}") + else EID:appendToDescription(descObj, "#" .. EID:GetPlayerIcon(playerType, "P" .. i .. ":")) end + EID:appendToDescription(descObj, " {{ColorPill}}" .. name) + end + end + return descObj +end + if EID.isRepentance then -- Handle Birthright local function BirthrightCallback(descObj) @@ -262,7 +298,7 @@ if EID.isRepentance then return descObj end - -- Handle Glowing Hourglass description + -- Handle Glowing Hourglass changed back into Hourglass description local function GlowingHourglassCallback(descObj) if REPENTOGON and EID.collectiblesOwned[422] then local transformedText = EID:getDescriptionEntry("GlowingHourglassTransformed") @@ -302,13 +338,6 @@ if EID.isRepentance then return descObj end - --simple decimal rounding - local function SimpleRound(num, dp) - dp = dp or 2 - local mult = 10^dp - return math.floor(num * mult + 0.5)/mult - end - -- 3 coins, 1 bomb, 1 key, 1 soul heart, 2 red hearts local consolationPickups = { "5.20", "5.40", "5.30", "5.10.3", "5.10" } local consolationQuantity = { "3", "1", "1", "1", "2" } @@ -321,10 +350,10 @@ if EID.isRepentance then local playerName = player:GetName() local playerStats = {} - playerStats[1] = SimpleRound((player.MoveSpeed * 4.5) - 2) - playerStats[2] = SimpleRound((((30/(player.MaxFireDelay + 1))^0.75) * 2.120391) - 2) - playerStats[3] = SimpleRound(((player.Damage^0.56)*2.231179) - 2) - playerStats[4] = SimpleRound(((player.TearRange - 230) / 60) + 2) + playerStats[1] = EID:SimpleRound((player.MoveSpeed * 4.5) - 2) + playerStats[2] = EID:SimpleRound((((30/(player.MaxFireDelay + 1))^0.75) * 2.120391) - 2) + playerStats[3] = EID:SimpleRound(((player.Damage^0.56)*2.231179) - 2) + playerStats[4] = EID:SimpleRound(((player.TearRange - 230) / 60) + 2) local playerPickups = {} playerPickups[1] = player:GetNumCoins() @@ -517,8 +546,42 @@ if EID.isRepentance then return descObj end - local hasTarot = false + -- Handle Wild Card description addition + local function WildCardCallback(descObj) + for i = 1,#EID.coopAllPlayers do + local player = EID.coopAllPlayers[i] + local playerID = EID:getPlayerID(player, true) + local playerType = player:GetPlayerType() + if EID.WildCardEffects[playerID] then + EID:appendToDescription(descObj, "#") + + if #EID.coopAllPlayers > 1 then EID:appendToDescription(descObj, EID:GetPlayerIcon(playerType, "P" .. i .. ":") .. " ") end + if EID.WildCardPillColor[playerID] then -- show the correct pill color; unnecessary attention to detail but why not + EID:appendToDescription(descObj, "{{Pill" .. EID.WildCardPillColor[playerID] .. "}} {{NameOnly" .. EID.WildCardEffects[playerID] .. "}}") + else EID:appendToDescription(descObj, "{{Name" .. EID.WildCardEffects[playerID] .. "}}") end + end + end + return descObj + end + -- Handle The Stars? description addition + local function TheStarsCallback(descObj) + EID:UpdateAllPlayerPassiveItems() + for i = 1,#EID.coopAllPlayers do + local player = EID.coopAllPlayers[i] + local playerID = EID:getPlayerID(player, true) + local playerType = player:GetPlayerType() + if EID.OldestItemIndex[playerID] and EID.RecentlyTouchedItems[playerID] then + local oldestItem = EID.RecentlyTouchedItems[playerID][EID.OldestItemIndex[playerID]] + if oldestItem then + EID:appendToDescription(descObj, "#") + if #EID.coopAllPlayers > 1 then EID:appendToDescription(descObj, EID:GetPlayerIcon(playerType, "P" .. i .. ":") .. " ") end + EID:appendToDescription(descObj, "{{NameC" .. oldestItem .. "}}") + end + end + end + return descObj + end local function VariableCharge(descObj, metadata, collID, chargeText) local text = EID:getDescriptionEntry(chargeText or "VariableCharge") @@ -528,6 +591,7 @@ if EID.isRepentance then end end + local hasTarot = false -- Handle Blank Card description addition local function BlankCardCallback(descObj) VariableCharge(descObj, EID.cardMetadata[descObj.ObjSubType], 286, "BlankCardCharge") @@ -552,42 +616,6 @@ if EID.isRepentance then VariableCharge(descObj, EID.pillMetadata[adjustedID-1], 348, "PlaceboCharge") return descObj end - - -- Handle VURP description addition - local function VurpCallback(descObj) - local adjustedID = EID:getAdjustedSubtype(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType) - if adjustedID - 1 ~= PillEffect.PILLEFFECT_VURP then return descObj end - - for i = 1,#EID.coopAllPlayers do - local player = EID.coopAllPlayers[i] - local playerID = EID:getPlayerID(player) - local playerType = player:GetPlayerType() - local pickupHistory = EID.PlayerItemInteractions[playerID].pickupHistory - -- Dead Tainted Lazarus exception - if playerType == 38 then - pickupHistory = EID.PlayerItemInteractions[playerID].altPickupHistory or pickupHistory - end - if pickupHistory then - local lastUsedPill = PillEffect.PILLEFFECT_VURP + 1 - local j = 1 - while (j <= #pickupHistory) do - local entry = pickupHistory[j] - -- ignore the pill if the pill color is Golden - if entry[1] == "pill" and entry[2] ~= 14 then - lastUsedPill = entry[3] - break - end - j = j + 1 - end - local tableName = EID:getTableName(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType) - local name = EID:getPillName(lastUsedPill, tableName == "horsepills") - if #EID.coopAllPlayers == 1 then EID:appendToDescription(descObj, "#{{Pill}}") - else EID:appendToDescription(descObj, "#" .. EID:GetPlayerIcon(playerType, "P" .. i .. ":")) end - EID:appendToDescription(descObj, " {{ColorSilver}}" .. name) - end - end - return descObj - end -- Handle Experimental Pill description addition local function ExperimentalPillCallback(descObj) @@ -734,11 +762,12 @@ if EID.isRepentance then if EID.collectiblesOwned[286] and not EID.blankCardHidden[descObj.ObjSubType] and EID.cardMetadata[descObj.ObjSubType] then table.insert(callbacks, BlankCardCallback) end if EID.collectiblesOwned[263] and EID.runeIDs[descObj.ObjSubType] and EID.cardMetadata[descObj.ObjSubType] then table.insert(callbacks, ClearRuneCallback) end + if descObj.ObjSubType == 80 then table.insert(callbacks, WildCardCallback) end + if descObj.ObjSubType == 73 then table.insert(callbacks, TheStarsCallback) end -- Pill Callbacks elseif descObj.ObjVariant == PickupVariant.PICKUP_PILL then if EID.collectiblesOwned[654] then table.insert(callbacks, FalsePHDCallback) end if EID.collectiblesOwned[348] then table.insert(callbacks, PlaceboCallback) end - table.insert(callbacks, VurpCallback) table.insert(callbacks, ExperimentalPillCallback) if EID.pillPlayer == nil and #EID.coopAllPlayers > 1 then @@ -782,6 +811,8 @@ local function EIDConditionsAB(descObj) elseif descObj.ObjVariant == PickupVariant.PICKUP_TRINKET then if descObj.ObjSubType == 80 then table.insert(callbacks, BlackFeatherCallback) end + elseif descObj.ObjVariant == PickupVariant.PICKUP_PILL then + table.insert(callbacks, VurpCallback) end return callbacks end diff --git a/features/eid_tmtrainer.lua b/features/eid_tmtrainer.lua index c7cbc8fb..f20f59ed 100644 --- a/features/eid_tmtrainer.lua +++ b/features/eid_tmtrainer.lua @@ -22,13 +22,6 @@ local triggerColors = { local actions = { [0] = "use_active_item", "add_temporary_effect", "convert_entities", "area_damage", "spawn_entity", "fart" } local triggers = { [0] = "active", "tear_fire", "enemy_hit", "enemy_kill", "damage_taken", "room_clear", "entity_spawned", "pickup_collected", "chain" } ---simple decimal rounding -local function SimpleRound(num, dp) - dp = dp or 2 - local mult = 10^dp - return math.floor(num * mult + 0.5)/mult -end - local function entityToName(Type, Variant, plural) plural = plural or false -- -1 is often used as an "any of this type", even if there's only one of that type, so converting it to 0 can help find names @@ -122,7 +115,7 @@ function EID:CheckGlitchedItemConfig(id) for i,func in ipairs(getFunctions) do local val = item[func](item) * statMult[i] if val ~= 0 then - local s = string.format("%.2g",SimpleRound(val)) + local s = string.format("%.2g",EID:SimpleRound(val)) local prefix = "↑ " if val > 0 then s = "+" .. s else prefix = "↓ " end attributes = attributes .. prefix .. EID:ReplaceVariableStr(voidNames[i], 1, s) .. "#" @@ -155,13 +148,13 @@ function EID:CheckGlitchedItemConfig(id) replacements[1] = "{{ColorGray}}" .. entityToName(effectProp.type, effectProp.variant) .. "{{CR}}" elseif effectType == "fart" then --I think the fart scale is strictly visual and therefore useless - --replacements[1] = SimpleRound(effectProp.scale) - replacements[1] = string.format("%.4g",SimpleRound(effectProp.radius)) + --replacements[1] = EID:SimpleRound(effectProp.scale) + replacements[1] = string.format("%.4g",EID:SimpleRound(effectProp.radius)) elseif effectType == "area_damage" then - replacements[1] = string.format("%.4g",SimpleRound(effectProp.damage)) + replacements[1] = string.format("%.4g",EID:SimpleRound(effectProp.damage)) --Fart scales are all small numbers and seem to be tiles; area damage is huge numbers, but still tiles? --so area damage is almost always the entire room - replacements[2] = string.format("%.4g",SimpleRound(effectProp.radius)) + replacements[2] = string.format("%.4g",EID:SimpleRound(effectProp.radius)) end if effectTrigger ~= lastEffectTrigger then diff --git a/main.lua b/main.lua index 2e9550a2..513a2ca0 100644 --- a/main.lua +++ b/main.lua @@ -51,6 +51,8 @@ EID.holdTabPlayer = 0 EID.holdTabCounter = 0 EID.DInfinityState = {} local forgottenDropTimer = 0 +EID.roomCount = 0 +local preHourglassStatus = {} EID.GameUpdateCount = 0 EID.GameRenderCount = 0 @@ -375,7 +377,8 @@ end -- Watch for a Void absorbing active items function EID:CheckVoidAbsorbs(_, _, player) - local playerID = EID:getPlayerID(player) + player = player or EID.player + local playerID = EID:getPlayerID(player, true) EID.absorbedItems[tostring(playerID)] = EID.absorbedItems[tostring(playerID)] or {} for _,v in ipairs(EID:VoidRoomCheck()) do EID.absorbedItems[tostring(playerID)][tostring(v)] = true @@ -672,7 +675,7 @@ function EID:printDescription(desc, cachedID) if #EID.coopAllPlayers > 1 then transformationName = transformationName .. EID:GetPlayerIcon(playerType) end - local numCollected = EID.TransformationProgress[EID:getPlayerID(player)] and EID.TransformationProgress[EID:getPlayerID(player)][transform] or 0 + local numCollected = EID.TransformationProgress[EID:getPlayerID(player, true)] and EID.TransformationProgress[EID:getPlayerID(player, true)][transform] or 0 local numMax = EID.TransformationData[transform] and EID.TransformationData[transform].NumNeeded or 3 transformationName = transformationName.."("..numCollected.."/"..numMax..") " end @@ -776,6 +779,18 @@ end function EID:onNewRoom() EID:CheckCurrentRoomGridEntities() + EID.roomCount = EID.roomCount + 1 + -- Store values here to be rewound after using Glowing Hour Glass + preHourglassStatus = {} + preHourglassStatus.absorbedItems = EID:CopyTable(EID.absorbedItems) + preHourglassStatus.PlayerItemInteractions = EID:CopyTable(EID.PlayerItemInteractions) + if EID.isRepentance then + preHourglassStatus.WildCardEffects = EID:CopyTable(EID.WildCardEffects) + preHourglassStatus.WildCardPillColor = EID:CopyTable(EID.WildCardPillColor) + preHourglassStatus.DInfinityState = EID:CopyTable(EID.DInfinityState) + preHourglassStatus.BagItems = EID:CopyTable(EID.BoC.BagItems) + preHourglassStatus.RoomQueries = EID:CopyTable(EID.BoC.RoomQueries) + end end EID:AddCallback(ModCallbacks.MC_POST_NEW_ROOM, EID.onNewRoom) @@ -1063,31 +1078,32 @@ function EID:onGameUpdate() EID.RecheckVoid = true end - - if EID.isRepentance and EID.GameUpdateCount % 10 == 0 then - -- Check wisp for adding reminder when using lemegeton - if EID.ShouldCheckWisp then - for _, wisp in ipairs(Isaac.FindByType(3, 237, -1, true, false)) do - if wisp.FrameCount < 10 then - local player = wisp:ToFamiliar() and wisp:ToFamiliar().Player - if player then - local playerID = EID:getPlayerID(player) - EID:InitItemInteractionIfAbsent(playerID) - table.insert(EID.RecentlyTouchedItems[playerID], wisp.SubType) - end - end + + -- Check player items for starting items + if EID.ShouldCheckStartingItems then + EID:UpdateAllPlayerPassiveItems() + EID:SetOldestItemIndex() + EID.ShouldCheckStartingItems = false + end + + if EID.isRepentance then + EID:UpdateWildCardEffects() + if EID.GameUpdateCount % 10 == 0 then + -- Check wisp for adding reminder when using lemegeton + if EID.ShouldCheckWisp then + EID:UpdateAllPlayerLemegetonWisps() + EID.ShouldCheckWisp = false end - EID.ShouldCheckWisp = false - end - -- Remove Crane Game item data if it's giving the prize out - for _, crane in ipairs(Isaac.FindByType(6, 16, -1, true, false)) do - if EID.CraneItemType[tostring(crane.InitSeed)] then - if crane:GetSprite():IsPlaying("Prize") then - EID.CraneItemType[tostring(crane.InitSeed)] = nil - -- Pair the Crane Game's new drop seed with the latest collectible ID it's gotten - -- (fixes Glowing Hour Glass rewinds) - elseif EID.CraneItemType[crane.InitSeed.."Drop"..crane.DropSeed] == nil then - EID.CraneItemType[crane.InitSeed.."Drop"..crane.DropSeed] = EID.CraneItemType[tostring(crane.InitSeed)] + -- Remove Crane Game item data if it's giving the prize out + for _, crane in ipairs(Isaac.FindByType(6, 16, -1, true, false)) do + if EID.CraneItemType[tostring(crane.InitSeed)] then + if crane:GetSprite():IsPlaying("Prize") then + EID.CraneItemType[tostring(crane.InitSeed)] = nil + -- Pair the Crane Game's new drop seed with the latest collectible ID it's gotten + -- (fixes Glowing Hour Glass rewinds) + elseif EID.CraneItemType[crane.InitSeed.."Drop"..crane.DropSeed] == nil then + EID.CraneItemType[crane.InitSeed.."Drop"..crane.DropSeed] = EID.CraneItemType[tostring(crane.InitSeed)] + end end end end @@ -1185,20 +1201,20 @@ function EID:CheckPosModifiers() else -- Tainted Isaac and Tainted ??? unique HUD adjustment if EID.player:GetPlayerType() == 21 or EID.player:GetPlayerType() == 25 then - EID:addTextPosModifier("Tainted HUD", Vector(0,30)) + EID:addTextPosModifier("Tainted HUD", Vector(0,26)) else EID:removeTextPosModifier("Tainted HUD") end -- Jacob and Esau card/pill adjustment if EID.player:GetPlayerType() == 19 then - EID:addTextPosModifier("J&E HUD", Vector(0,15)) + EID:addTextPosModifier("J&E HUD", Vector(0,16)) else EID:removeTextPosModifier("J&E HUD") end -- Magdalene Birthright, Keeper & Tainted Keeper third/fourth row of hearts adjustment if EID.player:GetEffectiveMaxHearts() + EID.player:GetSoulHearts() + (EID.player:GetBrokenHearts() * 2) >= 37 then EID:removeTextPosModifier("18 Heart HUD") - EID:addTextPosModifier("24 Heart HUD", Vector(0,22)) + EID:addTextPosModifier("24 Heart HUD", Vector(0,20)) elseif EID.player:GetEffectiveMaxHearts() + EID.player:GetSoulHearts() + (EID.player:GetBrokenHearts() * 2) >= 25 then EID:removeTextPosModifier("24 Heart HUD") EID:addTextPosModifier("18 Heart HUD", Vector(0,10)) @@ -1298,7 +1314,7 @@ function EID:OnRender() local dropTriggered, playerNumPressingDrop = EID:PlayersActionPressed(ButtonAction.ACTION_DROP, Input.IsActionTriggered) if dropTriggered then local playerPressingDrop = EID.coopMainPlayers[playerNumPressingDrop] - local playerID = EID:getPlayerID(playerPressingDrop) + local playerID = EID:getPlayerID(playerPressingDrop, true) local playerType = playerPressingDrop:GetPlayerType() -- Forgotten's drop requires a double tap to actually count for this if (playerType == 16 or playerType == 17) then @@ -1465,10 +1481,10 @@ function EID:OnRender() elseif closest.Type == 1000 and closest.Variant == 161 and closest.SubType <= 2 then local subtypeToCard = {18, 5, 19} -- Reuse the descriptions of The Emperor/Stars/Moon, so no localization needed - local descriptionObj = EID:getDescriptionObj(5, 300, subtypeToCard[closest.SubType+1], closest, false) + local descriptionObj = EID:getDescriptionObj(5, 300, subtypeToCard[closest.SubType+1], closest) -- Card Reading's name descriptionObj.Name = EID:getObjectName(5, 100, 660) - -- Only keep the first line of the description + -- Only keep the first line of the description (to avoid Tarot Cloth effects or Planetarium mention on The Stars) descriptionObj.Description = string.sub(descriptionObj.Description, 1, string.find(descriptionObj.Description, "#")) EID:addDescriptionToPrint(descriptionObj) -- Handle Crane Game @@ -1596,13 +1612,9 @@ EID:AddCallback(ModCallbacks.MC_POST_RENDER, EID.OnRender) local function AddActiveItemProgress(player, isD4) EID.ForceRefreshCache = true - local playerID = EID:getPlayerID(player) + local playerID = EID:getPlayerID(player, true) EID:InitItemInteractionIfAbsent(playerID) - -- Dead Tainted Lazarus exceptions local activesTable = EID.PlayerItemInteractions[playerID].actives - if player:GetPlayerType() == 38 then - activesTable = EID.PlayerItemInteractions[playerID].altActives or activesTable - end -- don't check pocket items after D4, they don't reroll and would get counted twice local maxSlot = 3 if isD4 then maxSlot = 1 end @@ -1629,22 +1641,61 @@ local function OnGameStartGeneral(_,isSave) EID:GetTransformationsOfModdedItems() EID:buildTransformationTables() EID.RecentlyTouchedItems = {} + EID.GulpedTrinkets = {} + EID.OldestItemIndex = {} if not isSave then EID.PlayerItemInteractions = {} EID.DInfinityState = {} end + EID.ShouldCheckWisp = true + EID.WildCardEffects = {} + EID.roomCount = 0 end EID:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, OnGameStartGeneral) +local function OnPostPlayerInit() + EID.ShouldCheckStartingItems = true -- for The Stars? tracking +end +EID:AddCallback(ModCallbacks.MC_POST_PLAYER_INIT, OnPostPlayerInit) + -- Add currently held active items after D4 was used. Used for Transformation Progress local function OnUseD4(_, _, _, player) -- in AB+, USE_ITEM doesn't provide a player AddActiveItemProgress(player or EID.player, true) - -- repentance fixed needing to blacklist your new items from counting (and AB+ counts everything) - --EID:CollectRerolledItemsOfPlayer(player or EID.player) + if EID.isRepentance then + local playerNum = EID:getPlayerID(player, true) + EID.OldestItemIndex[playerNum] = -1 -- D4 makes it impossible for us to know what the oldest item is for The Stars? + end end EID:AddCallback(ModCallbacks.MC_USE_ITEM, OnUseD4, CollectibleType.COLLECTIBLE_D4) +-- Watch for smelting trinkets; includes Gulp and Marbles +local function OnUseSmelter(_, _, _, player) + player = player or EID.player + local playerNum = EID:getPlayerID(player, true) + + EID.GulpedTrinkets[playerNum] = EID.GulpedTrinkets[playerNum] or {} + for i=0,1 do + local trinket = player:GetTrinket(i) + if trinket > 0 then table.insert(EID.GulpedTrinkets[playerNum], trinket) end + end +end +EID:AddCallback(ModCallbacks.MC_PRE_USE_ITEM, OnUseSmelter, CollectibleType.COLLECTIBLE_SMELTER) + +-- Watch for Glowing Hourglass to revert certain variables +local function OnUseGlowingHourglass(_, _, _, _) + EID.absorbedItems = EID:CopyTable(preHourglassStatus.absorbedItems) + EID.PlayerItemInteractions = EID:CopyTable(preHourglassStatus.PlayerItemInteractions) + if EID.isRepentance then + EID.WildCardEffects = EID:CopyTable(preHourglassStatus.WildCardEffects) + EID.WildCardPillColor = EID:CopyTable(preHourglassStatus.WildCardPillColor) + EID.DInfinityState = EID:CopyTable(preHourglassStatus.DInfinityState) + EID.BoC.BagItems = EID:CopyTable(preHourglassStatus.BagItems) + EID.BoC.RoomQueries = EID:CopyTable(preHourglassStatus.RoomQueries) + end +end +EID:AddCallback(ModCallbacks.MC_PRE_USE_ITEM, OnUseGlowingHourglass, CollectibleType.COLLECTIBLE_GLOWING_HOUR_GLASS) + -- Re-init transformation progress and item interactions after using Genesis if EID.isRepentance then local function OnUseGenesis(_, _, _, _) @@ -1658,6 +1709,14 @@ if EID.isRepentance then end EID:AddCallback(ModCallbacks.MC_PRE_USE_ITEM, OnUseLemegeton, CollectibleType.COLLECTIBLE_LEMEGETON) + -- Watch for any active item usage for Wild Card + local function OnUseGeneral(_, collectibleType, _, player, useFlags, activeSlot) + -- add the active to Wild Card tracking if it was a real active usage + if useFlags & UseFlag.USE_OWNED > 0 and activeSlot >= 0 then + EID:TrackWildCardEffects("5.100." .. collectibleType, player) + end + end + EID:AddCallback(ModCallbacks.MC_PRE_USE_ITEM, OnUseGeneral) end function EID:OnUsePill(pillEffectID, player, useFlags) @@ -1666,9 +1725,11 @@ function EID:OnUsePill(pillEffectID, player, useFlags) local pillColor = EID.PlayerHeldPill[EID:getPlayerID(player)] -- add the pill used to our history for Echo Chamber / Vurp! / transformation progress EID:AddPickupToHistory("pill", pillEffectID+1, player, useFlags, pillColor) + -- add the pill to Wild Card tracking, unless it's a Temperance? pill + if EID.isRepentance and useFlags ~= UseFlag.USE_NOANNOUNCER then EID:TrackWildCardEffects("5.70." .. (pillEffectID+1), player, pillColor) end EID.ForceRefreshCache = true -- for transformation progress update - -- for tracking used pills, ignore gold pills and noannouncer flag pills + -- for tracking used pills, ignore gold pills and noannouncer flag pills (Temperance?) -- (not using a bitmask, because Placebo is mimic+noannouncer, and we want to count those) if EID.isRepentance and (pillColor % PillColor.PILL_GIANT_FLAG == PillColor.PILL_GOLD or useFlags == UseFlag.USE_NOANNOUNCER) then return end EID.UsedPillColors[tostring(pillColor)] = true @@ -1678,6 +1739,8 @@ EID:AddCallback(ModCallbacks.MC_USE_PILL, EID.OnUsePill) function EID:OnUseCard(cardID, player, useFlags) player = player or EID.player --AB+ doesn't receive player in callback arguments! EID:AddPickupToHistory("card", cardID, player, useFlags) + -- add the card to Wild Card tracking unless it's Wild Card + if EID.isRepentance and cardID ~= 80 then EID:TrackWildCardEffects("5.300." .. cardID, player) end end EID:AddCallback(ModCallbacks.MC_USE_CARD, EID.OnUseCard) @@ -1693,16 +1756,34 @@ local configIgnoreList = { ["PlayerItemInteractions"] = true, ["UsedPillColors"] = true, ["RecentlyTouchedItems"] = true, + ["GulpedTrinkets"] = true, + ["WildCardEffects"] = true, + ["DInfinityState"] = true, + ["OldestItemIndex"] = true, } -------------------------------- ---------Handle Savadata--------- +--------Handle Savedata--------- -------------------------------- function EID:OnGameStart(isSave) --Loading Moddata-- - if EID:HasData() then local savedEIDConfig = json.decode(Isaac.LoadModData(EID)) - + + -- JSON saves integer table keys as strings. we need to transform them back... used in OnGameStart + local function ConvertSavedTable(tableName) + for playerID, data in pairs(savedEIDConfig[tableName] or {}) do + local convertedData = {} + if type(data) == "table" then + for key, value in pairs(data) do + convertedData[tonumber(key) or key] = value + end + else + convertedData = data + end + EID[tableName][tonumber(playerID)] = convertedData + end + end + -- collection progress EID.CollectedItems = savedEIDConfig["CollectedItems"] or {} if EID.SaveGame and savedEIDConfig["SaveGameNumber"] > 0 then @@ -1713,44 +1794,35 @@ function EID:OnGameStart(isSave) EID.PlayerItemInteractions = {} if isSave then -- JSON saves integer table keys as strings. we need to transform them back... - for playerID, data in pairs(savedEIDConfig["PlayerItemInteractions"] or {}) do - local convertedData = {} - for key, value in pairs(data) do - convertedData[tonumber(key) or key] = value - end - EID.PlayerItemInteractions[tonumber(playerID)] = convertedData - end - - for playerID, data in pairs(savedEIDConfig["RecentlyTouchedItems"] or {}) do - local convertedData = {} - for key, value in pairs(data) do - convertedData[tonumber(key) or key] = value - end - EID.RecentlyTouchedItems[tonumber(playerID)] = convertedData - end + ConvertSavedTable("PlayerItemInteractions") + ConvertSavedTable("RecentlyTouchedItems") + ConvertSavedTable("GulpedTrinkets") + ConvertSavedTable("WildCardEffects") + ConvertSavedTable("DInfinityState") + ConvertSavedTable("OldestItemIndex") else -- check for the players' starting active items CheckAllActiveItemProgress() end EID.UsedPillColors = {} + EID.absorbedItems = {} if isSave then EID.UsedPillColors = savedEIDConfig["UsedPillColors"] or {} + EID.absorbedItems = savedEIDConfig["AbsorbedItems"] or {} end - + if EID.isRepentance then EID.BoC.BagItems = {} EID.BoC.LearnedRecipes = {} EID.CraneItemType = {} EID.flipItemPositions = {} - EID.absorbedItems = {} - + if isSave then EID.BoC.BagItems = savedEIDConfig["BagContent"] or {} EID.BoC.LearnedRecipes = savedEIDConfig["BagLearnedRecipes"] or {} EID.BoC.RoomQueries = savedEIDConfig["BagFloorContent"] or {} EID.CraneItemType = savedEIDConfig["CraneItemType"] or {} - EID.absorbedItems = savedEIDConfig["AbsorbedItems"] or {} - + -- turn list back into dict because json cant save dict indices. local flipItemTable = {} for _, v in ipairs(savedEIDConfig["FlipItemPositions"]) do @@ -1794,7 +1866,7 @@ function EID:OnGameStart(isSave) EID.UsedPosition = Vector(EID.Config["XPosition"], EID.Config["YPosition"]) EID.Scale = EID.Config["Size"] EID.ItemReminderSelectedCategory = EID.Config["ItemReminderDisplayMode"] == "NoOverview" and 1 or 0 - + EID:fixDefinedFont() EID:loadFont(EID.modPath .. "resources/font/eid_"..EID.Config["FontType"]..".fnt") end @@ -1808,8 +1880,8 @@ function EID:OnGameExit() EID.Config["BagLearnedRecipes"] = EID.BoC.LearnedRecipes or {} EID.Config["BagFloorContent"] = EID.BoC.RoomQueries or {} EID.Config["CraneItemType"] = EID.CraneItemType or {} - EID.Config["AbsorbedItems"] = EID.absorbedItems or {} - + EID.Config["WildCardEffects"] = EID.WildCardEffects or {} + EID.Config["DInfinityState"] = EID.DInfinityState or {} -- turn dictionary into list because json cant save dict indices. local flipItemTable = {} for k, v in pairs(EID.flipItemPositions) do @@ -1824,7 +1896,10 @@ function EID:OnGameExit() EID.Config["CollectedItems"] = EID.CollectedItems EID.Config["PlayerItemInteractions"] = EID.PlayerItemInteractions or {} EID.Config["RecentlyTouchedItems"] = EID.RecentlyTouchedItems or {} + EID.Config["GulpedTrinkets"] = EID.GulpedTrinkets or {} EID.Config["UsedPillColors"] = EID.UsedPillColors + EID.Config["AbsorbedItems"] = EID.absorbedItems or {} + EID.Config["OldestItemIndex"] = EID.OldestItemIndex or {} EID.SaveData(EID, json.encode(EID.Config)) EID:hidePermanentText()