Fix Tarot Cloth Rep modifiers + Car Battery overhaul (#675)

* Tarot Cloth Rep fix, Car Battery updates

* oops I readded detailed

* oops I readded detailed

* Less full replaces in tarot cloth table

* oops bad copypaste

* Finished AB+ car battery

* Car Battery rep check in progress

* Yum Heart co-op

* Car Battery in progress

* Finish Car Battery, add Isaac color

* Move the functions out

* Add files via upload

* No effect

* Finished Car Battery synergies

* some Greed conditionals

* some Greed conditionals

* fix Black Feather in AB+

* move the apply function

* Greed conds + move apply func
This commit is contained in:
Buurazu authored and GitHub committed 2024-06-15 13:04:32 +02:00
1 parent 6c79d567ff
commit 2ae25e6752
6 files changed
+552 -401

No files matched your search

+55 -327
View File
@@ -4,126 +4,41 @@ EID.DescriptionConditions = {}
-- table of collectible IDs to periodically check if the players own. used by eid_modifiers.lua
EID.collectiblesToCheck = {}
--[[
Argument 1: The item ID
For convenience, numbers are assumed to be a collectible ID; strings in Type.Var.Sub format can be used as well
If you're adding a condition that applies to many items, you can pass in a table of IDs
If you're adding a condition that applies to an entire type of item, pass in a string in Type.Var format
Argument 2: The function that will apply the condition if it returns true; descObj will be passed into this function
Argument 3 (optional): Additional text for finding the string in the localization file
For example, if you put "Overridden" here, modifying Tech X, it will look for ConditionalDescs["5.100.395 (Overridden)"] AND ConditionalDescs["Overridden"]
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...)
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
useResult: The condition function can return a collectible ID; if true, use that ID to generate bulletpoint/variable text. This is to be able to use specific collectible icons/names while using more general modded-item-friendly conditions
Argument 5 (very optional): The position in the table to insert this new condition, in case mods want to insert modifiers before our base ones
]]
function EID:AddConditional(IDs, funcText, modText, extraTable, insertPoint)
if type(IDs) ~= "table" then IDs = { IDs } end
if modText == "" then modText = nil end
extraTable = extraTable or {}
-- make a shallow copy of the passed-in table
local newTable = {}; for k, v in pairs(extraTable) do newTable[k] = v end
newTable.func = funcText;
newTable.modifierText = modText;
if newTable.noFallback == nil then newTable.noFallback = true end
for _, id in ipairs(IDs) do
if type(id) ~= "string" then id = "5.100." .. id end
-- Check for only one period in the id string to mark it as being a general condition, which looks for a SubType entry in the localization file
local _, count = string.gsub(id, "%.", "")
if count == 1 and newTable.general == nil then newTable.general = true end
EID.DescriptionConditions[id] = EID.DescriptionConditions[id] or {}
if insertPoint then
table.insert(EID.DescriptionConditions[id], insertPoint, newTable)
else
table.insert(EID.DescriptionConditions[id], newTable)
end
end
end
require("features.eid_conditionals_funcs")
-- Easy to use functions for conditionals that rely on having a specific collectible/character
-- By default, they'll have a bulletpoint for the coll/char, and {1} becomes the coll/char's name
-- checkInReminder is for if a synergy is no longer relevant once the item isn't obtainable (e.g. Abyss locusts) (true by default)
function EID:AddCollectibleConditional(IDs, ownedIDs, modText, extraTable, checkInReminder)
if type(ownedIDs) ~= "table" then ownedIDs = { ownedIDs } end
if checkInReminder == nil then checkInReminder = true end
extraTable = extraTable or {}
for _, ownedID in ipairs(ownedIDs) do
EID.collectiblesToCheck[ownedID] = true
local newTable = {}; for k, v in pairs(extraTable) do newTable[k] = v end
if newTable.variableText == nil then newTable.variableText = "{{NameOnlyC" .. ownedID .. "}}" end
if newTable.bulletpoint == nil then newTable.bulletpoint = "Collectible" .. ownedID end
EID:AddConditional(IDs, function() return EID:ConditionalCollCheck(ownedID, checkInReminder) end, modText, newTable)
end
end
--[[ The general meaning of the arguments when adding a conditional are as follows:
-- For adding pairs of abilities to collectibles; you can define a different modifier text for each of them
-- example: EID:AddSynergyConditional({list of IDs overridden by Brimstone}, Brimstone, "Overridden", "Overrides")
function EID:AddSynergyConditional(IDs, ownedIDs, modText1, modText2, extraTable1, extraTable2, checkInReminder)
EID:AddCollectibleConditional(IDs, ownedIDs, modText1, extraTable1, checkInReminder)
EID:AddCollectibleConditional(ownedIDs, IDs, modText2 or modText1, extraTable2 or extraTable1, checkInReminder)
end
-- includeTainted is for if you want to check the normal and tainted version of a character (true by default)
function EID:AddPlayerConditional(IDs, charIDs, modText, extraTable, includeTainted)
if type(charIDs) ~= "table" then charIDs = { charIDs } end
if includeTainted == nil then includeTainted = true end
extraTable = extraTable or {}
for _, charID in ipairs(charIDs) do
local newTable = {}; for k, v in pairs(extraTable) do newTable[k] = v end
if newTable.variableText == nil then newTable.variableText = "{{NameOnlyI" .. charID .. "}}" end
if newTable.bulletpoint == nil then newTable.bulletpoint = "Player" .. charID end
EID:AddConditional(IDs, function() return EID:ConditionalCharCheck(charID, includeTainted) end, modText, newTable)
end
end
-- Adding our conditionals (should this be its own file away from the functions that add/print conditionals?)
--[[
TODO STUFF:
new config options for spoiler-heavy descriptions if we make any (chaos card and plan C?)
CO-OP BENEFIT ITEMS! tech zero / extension cord in co-op? yum heart and such
Items that override other items (brimstone, mom's knife, epic fetus stuff)
BFFs / the fly booster + Tainted Apollyon, Tainted Eve; melody trinket + Tainted Eve; these are insane synergies that deserve attention
TODO: Make some of these coll/char IDs be from the enum for readability? or just keep it to comments
TODO: 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
-- hey what if all these Luck addons that I kinda don't like instead showed an accurate percentage of what they do at your current luck (although this is Modifier territory, with a per character display) (might be annoying though)
-- todo: TrinketConditional, use it for the crystal key + pandora's box or whichever key it is, and Black Feather items maybe (leave the damage calc to modifiers, but we can append text to black feather items)
-- todo: Vibrant/Dim Bulb + doesn't work with pocket consumables
-- todo: idk about the BFFs synergy stuff but I noticed that Brown Nugget is incorrectly dealt with, it gained double damage in rep
-- hive mind and BFFs synergy has a lot of overlap also
-- brown nugget + sacrificial altar in AB+?!
-- ? Card + single use actives in Repentance? Alabaster Box. is there a way to detect an active is a single-use active or do we need a hardcode list?
-- unrelated to conditionals: Glyph of Balance prediction in Item Reminder?
-- Jacob's Ladder synergies with other battery items (I didn't even know about this)
Argument 1: ID of the item we're looking at the description of (assumed to be a collectible ID if it's just a number)
Argument 2: Function that needs to be true for us to change the description (or ID of the collectible/character that we need to have)
Argument 3: Optional text for finding the correct string in the localization file (can also be a function, which is passed descObj, that returns the location)
Argument 4: Optional table that can define a variety of different settings
]]
-- General conditions
-- Tarot Cloth (AB+ and Rep) (fallback to English because most are just find+replace)
EID:AddCollectibleConditional("5.300", 451, nil, {locTable = "tarotClothBuffsAB", replaceColor = "ColorShinyPurple", noFallback = false})
if EID.isRepentance then EID:AddCollectibleConditional("5.300", 451, nil, {locTable = "tarotClothBuffs", replaceColor = "ColorShinyPurple", noFallback = false}) end
EID:AddCollectibleConditional("5.100", 356, nil, {locTable = "carBattery", replaceColor = "BlinkYellowGreen"}) -- Car Battery
if not EID.isRepentance then
EID:AddCollectibleConditional("5.300", 451, nil, {locTable = "tarotClothBuffsAB", replaceColor = "ColorShinyPurple", noFallback = false})
else EID:AddCollectibleConditional("5.300", 451, nil, {locTable = "tarotClothBuffs", replaceColor = "ColorShinyPurple", noFallback = false}) end
EID:AddCollectibleConditional("5.100", 356, EID.CheckForCarBattery, {locTable = "carBattery", replaceColor = "BlinkYellowGreen", noFallback = false}) -- Car Battery
-- IV Bag conditions (need to be applied in a specific order)
EID:AddCollectibleConditional(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, function() return EID:IsHardMode() end, "Hard Mode") end -- Hard Mode pays out less
if EID.isRepentance then EID:AddConditional(135, EID.IsHardMode, "Hard Mode") end -- Hard Mode pays out less
-- Greed Mode
EID:AddConditional({241, "5.300.15"}, function() return game:IsGreedMode() end) -- Contract From Below, Temperance
EID:AddConditional("5.300.15", function() return game:IsGreedMode() and EID:PlayersHaveCollectible(451) end, "Tarot") -- Temperance + Greed + Tarot
-- "No effect" append
EID:AddConditional({134, 241, 464, "5.300.22", "5.350.59", "5.350.83", "5.350.84", "5.350.85", "5.350.102", "5.350.110", "5.350.111", "5.350.124"}, EID.IsGreedMode, "No Effect (Greed)") -- Guppy's Tail, Contract from Below, Glyph of Balance, The World, Cain's Eye, Store Key, Rib of Greed, Karma, Fragmented Card, Silver Dollar, Bloody Crown, Door Stop
EID:AddConditional({483, 535, "5.300.15", "5.300.19", "5.300.20"}, EID.IsGreedMode) -- Mama Mega, Blanket, Temperance, The Moon, The Sun
-- todo: isaac's fork; and maybe some other "room" effects that also work on wave clear could mention waves
-- change the "room clear reward" trinkets (like ace of spades) to not mention room clears
-- broken modem room clear duplication shouldn't be mentioned?
if EID.isRepentance then
EID:AddConditional({344, 416, "5.300.74"}, EID.IsGreedMode) -- Match Book, Deep Pockets, The Moon?
end
-- Different effect for No Red Health players
EID:AddConditional({442, "5.350.107", "5.350.119"}, function() return EID:CheckForNoRedHealthPlayer() end, "No Red") -- Dark Prince's Crown, Crow Heart, Stem Cell
@@ -149,17 +64,20 @@ EID:AddCollectibleConditional("5.300.48", 286, nil, { lineColor = "ColorSilver"
-- This got a bit complicated, since it looks at a trait of every collectible + you having another, but hey, it's compact
EID:AddConditional(116, function() return EID:CheckPlayersForActiveChargeType(1) end, "1 Room", { useResult = true })
EID:AddConditional(116, function() return EID:CheckPlayersForActiveChargeType(nil, 1) end, "Timed", { useResult = true })
EID:AddConditional("5.100", function(descObj) return EID:PlayersHaveCollectible(116) and EID:CheckActiveChargeType(descObj.ObjSubType, 1) end, "1 Room", { bulletpoint = "Collectible116", general = false })
EID:AddConditional("5.100", function(descObj) return EID:PlayersHaveCollectible(116) and EID:CheckActiveChargeType(descObj.ObjSubType, nil, 1) end, "Timed", { bulletpoint = "Collectible116", general = false })
EID:AddConditional("5.100", function(descObj) return EID:PlayersHaveCollectible(116) and EID:CheckActiveChargeType(descObj.ObjSubType, 1) end, "1 Room", { bulletpoint = "Collectible116" })
EID:AddConditional("5.100", function(descObj) return EID:PlayersHaveCollectible(116) and EID:CheckActiveChargeType(descObj.ObjSubType, nil, 1) end, "Timed", { bulletpoint = "Collectible116" })
-- Overridden by Brimstone
-- Haven't done it yet but I'm sure preparing the system for it
-- Remember most of these will be AB+ only (not EID.isRepentance)
-- AB+ only conditionals
if not EID.isRepentance then
EID:AddCollectibleConditional(205, {116, 276}, "No Effect") -- Sharp Plug + 9 Volt, Isaac's Heart
EID:AddCollectibleConditional(205, 108, "Wafer") -- Sharp Plug + The Wafer
EID:AddSynergyConditional(205, 441, "Can't Charge", "Can't Be Charged") -- Sharp Plug + Mega Blast
EID:AddConditional(208, function() return EID:IsHardMode() end) -- Champion Belt
end
if EID.isRepentance then
@@ -169,7 +87,8 @@ if EID.isRepentance then
EID:AddCollectibleConditional("5.100", 664, nil, { locTable = "bingeEaterBuffs" }) -- Binge Eater
-- Co-op friendly items
-- todo (yum heart, extension cord)
-- todo: which? (extension cord)
EID:AddConditional(45, function() return EID:MultiplePlayerCharacters() end) -- Yum Heart
-- No red health
EID:AddConditional({569, 671, 676}, function() return EID:CheckForNoRedHealthPlayer() end, "No Red") -- Blood Oath, Candy Heart, Empty Heart
@@ -209,225 +128,34 @@ if EID.isRepentance then
-- 9 Volt, Sharp Plug + Special Charge Actives
EID:AddConditional({116, 205}, function() return EID:CheckPlayersForActiveChargeType(nil, 2) end, "Can't Charge", { useResult = true })
EID:AddConditional("5.100", function(descObj) return EID:PlayersHaveCollectible(116) and EID:CheckActiveChargeType(descObj.ObjSubType, nil, 2) end, "Can't Be Charged", { bulletpoint = "Collectible116", variableText = "{{NameOnlyC116}}", general = false })
EID:AddConditional("5.100", function(descObj) return EID:PlayersHaveCollectible(205) and EID:CheckActiveChargeType(descObj.ObjSubType, nil, 2) end, "Can't Be Charged", { bulletpoint = "Collectible205", variableText = "{{NameOnlyC205}}", general = false })
EID:AddConditional("5.100", function(descObj) return EID:PlayersHaveCollectible(116) and EID:CheckActiveChargeType(descObj.ObjSubType, nil, 2) end, "Can't Be Charged", { bulletpoint = "Collectible116", variableText = "{{NameOnlyC116}}" })
EID:AddConditional("5.100", function(descObj) return EID:PlayersHaveCollectible(205) and EID:CheckActiveChargeType(descObj.ObjSubType, nil, 2) end, "Can't Be Charged", { bulletpoint = "Collectible205", variableText = "{{NameOnlyC205}}" })
end
----- Evaluation Functions -----
function EID:ConditionalCharCheck(playerType, includeTainted)
-- We don't want to display irrelevant item info inside the Item Reminder
if EID.InsideItemReminder then
local player = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player
if player:GetPlayerType() == playerType then
return true
elseif includeTainted and EID.isRepentance and EID.TaintedToRegularID[player:GetPlayerType()] == playerType then
return true
else
return false
end
else
return EID:PlayersHaveCharacter(playerType, includeTainted)
end
end
--[[
TODO STUFF:
function EID:ConditionalCollCheck(collectibleID, checkInReminder)
-- We don't want to display irrelevant item info inside the Item Reminder
if EID.InsideItemReminder then
if not checkInReminder then return false end
local player = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player
if player:HasCollectible(collectibleID) then return true end
-- Check for the item being inside the Item Reminder player's Void (not sure if this is ever relevant...)
if player:HasCollectible(477) then
local playerID = EID:getPlayerID(player)
if EID.absorbedItems[tostring(playerID)] and EID.absorbedItems[tostring(playerID)][tostring(collectibleID)] then
return true
end
end
else
return EID.collectiblesOwned[collectibleID] or EID.collectiblesAbsorbed[collectibleID]
end
return false
end
function EID:IsGreedMode()
return game:IsGreedMode()
end
function EID:IsHardMode()
if game.Difficulty == 1 or game.Difficulty == 3 then
return true
else
return false
end
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
local player = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player
return EID.NoRedHeartsPlayerIDs[player:GetPlayerType()]
else
for i = 0, game:GetNumPlayers() - 1 do
local player = Isaac.GetPlayer(i)
if EID.NoRedHeartsPlayerIDs[player:GetPlayerType()] then
return true
end
end
end
return false
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)
local players = {}
if EID.InsideItemReminder then
players[1] = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player
else
for i = 0, game:GetNumPlayers() - 1 do
table.insert(players, Isaac.GetPlayer(i))
end
end
for i = 1, #players do
local player = players[i]
for j = 0, EID.isRepentance and 3 or 0 do
local activeID = player:GetActiveItem(j)
if EID:CheckActiveChargeType(activeID, maxCharge, chargeType) then return activeID end
end
end
return false
end
function EID:CheckActiveChargeType(itemID, maxCharge, chargeType)
local active = EID.itemConfig:GetCollectible(itemID)
if active then
if active.MaxCharges == maxCharge and active.ChargeType ~= 2 then return true
elseif EID.isRepentance and active.ChargeType == chargeType then return true
elseif not EID.isRepentance and chargeType == 1 and active.MaxCharges >= 30 then return true end
end
return false
end
-- Check for multiple players existing for items like Yum Heart and Extension Cord, includes J&E (but does it include Forgotten?)
function EID:MultiplePlayerCharacters()
return #EID.coopAllPlayers > 1
end
-- Check the bestiary/character list if we have REPENTOGON, to help avoid spoilers. Otherwise just return true
function EID:HaveEncountered(monsterType, monsterVariant)
if not REPENTOGON then return true end
return Isaac.GetPersistentGameData():GetBestiaryEncounterCount(monsterType, monsterVariant) > 0
end
function EID:HaveNotEncountered(monsterType, monsterVariant)
if not REPENTOGON then return true end
return not EID:HaveEncountered(monsterType, monsterVariant)
end
function EID:HaveUnlockedAchievement(achID)
if not REPENTOGON then return true end
return Isaac.GetPersistentGameData():Unlocked(achID)
end
function EID:HaveNotUnlockedAchievement(achID)
if not REPENTOGON then return true end
return not EID:HaveUnlockedAchievement(achID)
end
----- Apply Conditionals -----
-- thing to fix find/replace pairs with hyphens (like "1-2") breaking because hyphen is a special char
-- https://stackoverflow.com/questions/29072601/lua-string-gsub-with-a-hyphen
local function replace(str, what, with, count)
what = string.gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1") -- escape pattern
with = string.gsub(with, "[%%]", "%%%%") -- escape replacement
return string.gsub(str, what, with, count)
end
-- super simple table concatenation: https://www.tutorialspoint.com/concatenation-of-tables-in-lua-programming
local function TableConcat(t1, t2)
for i = 1, #t2 do
t1[#t1 + 1] = t2[i]
end
return t1
end
function EID:applyConditionals(descObj)
EID:CheckPlayersCollectibles()
local typeVar = descObj.ObjType.."."..descObj.ObjVariant -- for general conditions (Tarot Cloth, Book of Virtues)
local typeVarSub = descObj.fullItemString -- for specific conditions
-- Combine general+specific conditions into one table
local conds = {}
if EID.DescriptionConditions[typeVar] then TableConcat(conds, EID.DescriptionConditions[typeVar]) end
if EID.DescriptionConditions[typeVarSub] then TableConcat(conds, EID.DescriptionConditions[typeVarSub]) end
-- Check every condition we need to check for this item
for _, cond in ipairs(conds) do
-- Search for the localization string; "S" (for generals) or "T.V.S" or "T.V.S (Modifier)" or "Modifier"
local locTable = cond.locTable or "ConditionalDescs"
local text = nil
if cond.noTable then
text = EID:getDescriptionEntry(cond.modifierText, nil, cond.noFallback)
elseif cond.general then
text = EID:getDescriptionEntry(locTable, descObj.ObjSubType, cond.noFallback)
elseif cond.modifierText then
text = EID:getDescriptionEntry(locTable, typeVarSub .. " (" .. cond.modifierText .. ")", cond.noFallback)
if text == nil then text = EID:getDescriptionEntry(locTable, cond.modifierText, cond.noFallback) end
else
text = EID:getDescriptionEntry(locTable, typeVarSub, cond.noFallback)
end
-- If we find the localization string for this condition, and it passes the test, modify the description text
local result = cond.func(descObj)
if text ~= nil and result then
local variableText, bulletpoint
if cond.useResult then
variableText = "{{NameOnlyC"..result.."}}"
bulletpoint = "Collectible"..result
else
variableText = type(cond.variableText) == "function" and cond.variableText(descObj) or cond.variableText
bulletpoint = cond.bulletpoint
end
-- String = append
if type(text) == "string" then
text = EID:ReplaceVariableStr(text, 1, variableText)
local iconStr = "#"
if bulletpoint then iconStr = iconStr .. "{{" .. bulletpoint .. "}} " end
if cond.lineColor then iconStr = iconStr .. "{{" .. cond.lineColor .. "}}" end
EID:appendToDescription(descObj, iconStr .. text:gsub("#", iconStr))
-- Table with 1 entry = replace
elseif #text == 1 then
descObj.Description = EID:ReplaceVariableStr(text[1], 1, variableText)
-- Table with 2+ entries = find and replace pairs
-- Entry 1 is replaced with entry 2, entry 3 is replaced with entry 4, etc.
else
local pos = 1
while pos < #text do
local toFind = text[pos]
local replaceWith = EID:ReplaceVariableStr(text[pos + 1], 1, variableText)
if cond.replaceColor then replaceWith = "{{" .. cond.replaceColor .. "}}" .. replaceWith .. "{{CR}}" end
--"%d*%.?%d+" will grab every number group (1, 10, 0.5), this will allow us to not replace the "1" in "10" erroneously
if (type(toFind) == "number") then
local count = 0
descObj.Description = string.gsub(descObj.Description, "%d*%.?%d+", function(s)
if (s == tostring(toFind) and count == 0) then
count = count + 1
return replaceWith
end
end)
-- Basic find+replace for non-numbers
else
descObj.Description = replace(descObj.Description, tostring(toFind), replaceWith, 1)
end
pos = pos + 2
end
end
end
end
return descObj
end
new config options for spoiler-heavy descriptions if we make any (chaos card and plan C?)
CO-OP BENEFIT ITEMS! tech zero / extension cord in co-op? yum heart and such
Items that override other items (brimstone, mom's knife, epic fetus stuff)
BFFs / the fly booster + Tainted Apollyon, Tainted Eve; melody trinket + Tainted Eve; these are insane synergies that deserve attention
TODO: Make some of these coll/char IDs be from the enum for readability? or just keep it to comments
TODO: 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
-- hey what if all these Luck addons that I kinda don't like instead showed an accurate percentage of what they do at your current luck (although this is Modifier territory, with a per character display) (might be annoying though)
-- todo: TrinketConditional, use it for the crystal key + pandora's box or whichever key it is, and Black Feather items maybe (leave the damage calc to modifiers, but we can append text to black feather items)
-- todo: Vibrant/Dim Bulb + doesn't work with pocket consumables
-- todo: idk about the BFFs synergy stuff but I noticed that Brown Nugget is incorrectly dealt with, it gained double damage in rep
-- hive mind and BFFs synergy has a lot of overlap also, need a data list of hivemind familiars
-- brown nugget + sacrificial altar in AB+?!
-- ? Card + single use actives in Repentance? Alabaster Box. is there a way to detect an active is a single-use active or do we need a hardcode list?
-- unrelated to conditionals: Glyph of Balance prediction in Item Reminder?
-- Jacob's Ladder synergies with other battery items (I didn't even know about this)
-- Bean actives synegies with ghost pepper/bird's eye
-- todo: head of krampus damage (needs rep version), shoop da whoop damage / tick count (differs by 1 in repentance lol ok), head of krampus car battery, rep car battery
-- Greed Mode useless World card, Moon warps to super secret, Glyph of Balance very bad in AB+ (removed from Greed in rep), Blanket bad, Deep Pockets bad
car battery "no effect" items (also apply this as a synergy where car battery has no effect text, and other bad synergies, based on your active)
-- overall todo: remove "tick" language from all descriptions; Lil brimstone, black powder, black hole
Cain + items that close his eyes? mostly AB+ only?
Pandora's Box achievement check if moving box is unlocked find replace that with "nothing"
]]
+315
View File
@@ -0,0 +1,315 @@
local game = Game()
--[[
Argument 1: The item ID
For convenience, numbers are assumed to be a collectible ID; strings in Type.Var.Sub format can be used as well
If you're adding a condition that applies to many items, you can pass in a table of IDs
If you're adding a condition that applies to an entire type of item, pass in a string in Type.Var format
Argument 2: The function that will apply the condition if it returns true; descObj will be passed into this function
Argument 3 (optional): Additional text for finding the string in the localization file
For example, if you put "Overridden" here, modifying Tech X, it will look for ConditionalDescs["5.100.395 (Overridden)"] AND ConditionalDescs["Overridden"]
Can also be a function for more dynamic deciding
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...)
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
useResult: The condition function can return a collectible ID; if true, use that ID to generate bulletpoint/variable text. This is to be able to use specific collectible icons/names while using more general modded-item-friendly conditions
Argument 5 (very optional): The position in the table to insert this new condition, in case mods want to insert modifiers before our base ones
]]
function EID:AddConditional(IDs, funcText, modText, extraTable, insertPoint)
if type(IDs) ~= "table" then IDs = { IDs } end
if modText == "" then modText = nil end
extraTable = extraTable or {}
-- make a shallow copy of the passed-in table
local newTable = {}; for k, v in pairs(extraTable) do newTable[k] = v end
newTable.func = funcText;
newTable.modifierText = modText;
if newTable.noFallback == nil then newTable.noFallback = true end
for _, id in ipairs(IDs) do
if type(id) ~= "string" then id = "5.100." .. id end
EID.DescriptionConditions[id] = EID.DescriptionConditions[id] or {}
if insertPoint then
table.insert(EID.DescriptionConditions[id], insertPoint, newTable)
else
table.insert(EID.DescriptionConditions[id], newTable)
end
end
end
-- Easy to use functions for conditionals that rely on having a specific collectible/character
-- By default, they'll have a bulletpoint for the coll/char, and {1} becomes the coll/char's name
-- checkInReminder is for if a synergy is no longer relevant once the item isn't obtainable (e.g. Abyss locusts) (true by default)
function EID:AddCollectibleConditional(IDs, ownedIDs, modText, extraTable, checkInReminder)
if type(ownedIDs) ~= "table" then ownedIDs = { ownedIDs } end
if checkInReminder == nil then checkInReminder = true end
extraTable = extraTable or {}
for _, ownedID in ipairs(ownedIDs) do
EID.collectiblesToCheck[ownedID] = true
local newTable = {}; for k, v in pairs(extraTable) do newTable[k] = v end
if newTable.variableText == nil then newTable.variableText = "{{NameOnlyC" .. ownedID .. "}}" end
if newTable.bulletpoint == nil then newTable.bulletpoint = "Collectible" .. ownedID end
EID:AddConditional(IDs, function() return EID:ConditionalCollCheck(ownedID, checkInReminder) end, modText, newTable)
end
end
-- For adding pairs of abilities to collectibles; you can define a different modifier text for each of them
-- example: EID:AddSynergyConditional({list of IDs overridden by Brimstone}, Brimstone, "Overridden", "Overrides")
function EID:AddSynergyConditional(IDs, ownedIDs, modText1, modText2, extraTable1, extraTable2, checkInReminder)
EID:AddCollectibleConditional(IDs, ownedIDs, modText1, extraTable1, checkInReminder)
EID:AddCollectibleConditional(ownedIDs, IDs, modText2 or modText1, extraTable2 or extraTable1, checkInReminder)
end
-- includeTainted is for if you want to check the normal and tainted version of a character (true by default)
function EID:AddPlayerConditional(IDs, charIDs, modText, extraTable, includeTainted)
if type(charIDs) ~= "table" then charIDs = { charIDs } end
if includeTainted == nil then includeTainted = true end
extraTable = extraTable or {}
for _, charID in ipairs(charIDs) do
local newTable = {}; for k, v in pairs(extraTable) do newTable[k] = v end
if newTable.variableText == nil then newTable.variableText = "{{NameOnlyI" .. charID .. "}}" end
if newTable.bulletpoint == nil then newTable.bulletpoint = "Player" .. charID end
EID:AddConditional(IDs, function() return EID:ConditionalCharCheck(charID, includeTainted) end, modText, newTable)
end
end
----- Evaluation Functions -----
function EID:ConditionalCharCheck(playerType, includeTainted)
-- We don't want to display irrelevant item info inside the Item Reminder
if EID.InsideItemReminder then
local player = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player
if player:GetPlayerType() == playerType then
return true
elseif includeTainted and EID.isRepentance and EID.TaintedToRegularID[player:GetPlayerType()] == playerType then
return true
else
return false
end
else
return EID:PlayersHaveCharacter(playerType, includeTainted)
end
end
function EID:ConditionalCollCheck(collectibleID, checkInReminder)
-- We don't want to display irrelevant item info inside the Item Reminder
if EID.InsideItemReminder then
if not checkInReminder then return false end
local player = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player
if player:HasCollectible(collectibleID) then return true end
-- Check for the item being inside the Item Reminder player's Void (not sure if this is ever relevant...)
if player:HasCollectible(477) then
local playerID = EID:getPlayerID(player)
if EID.absorbedItems[tostring(playerID)] and EID.absorbedItems[tostring(playerID)][tostring(collectibleID)] then
return true
end
end
else
return EID.collectiblesOwned[collectibleID] or EID.collectiblesAbsorbed[collectibleID]
end
return false
end
function EID:IsGreedMode()
return game:IsGreedMode()
end
function EID:IsGreedModePlusTarot()
return game:IsGreedMode() and EID:ConditionalCollCheck(451, true)
end
function EID:IsHardMode()
if game.Difficulty == 1 or game.Difficulty == 3 then
return true
else
return false
end
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
local player = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player
return EID.NoRedHeartsPlayerIDs[player:GetPlayerType()]
else
for i = 0, game:GetNumPlayers() - 1 do
local player = Isaac.GetPlayer(i)
if EID.NoRedHeartsPlayerIDs[player:GetPlayerType()] then
return true
end
end
end
return false
end
function EID:CheckForCarBattery(descObj)
if EID.CarBatteryNoSynergy[descObj.ObjSubType] then return "No Effect"
else return descObj.ObjSubType end
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)
local players = {}
if EID.InsideItemReminder then
players[1] = EID:ItemReminderGetAllPlayers()[EID.ItemReminderSelectedPlayer + 1] or EID.player
else
for i = 0, game:GetNumPlayers() - 1 do
table.insert(players, Isaac.GetPlayer(i))
end
end
for i = 1, #players do
local player = players[i]
for j = 0, EID.isRepentance and 3 or 0 do
local activeID = player:GetActiveItem(j)
if EID:CheckActiveChargeType(activeID, maxCharge, chargeType) then return activeID end
end
end
return false
end
function EID:CheckActiveChargeType(itemID, maxCharge, chargeType)
local active = EID.itemConfig:GetCollectible(itemID)
if active then
if active.MaxCharges == maxCharge and active.ChargeType ~= 2 then return true
elseif EID.isRepentance and active.ChargeType == chargeType then return true
elseif not EID.isRepentance and chargeType == 1 and active.MaxCharges >= 30 then return true end
end
return false
end
-- Check for multiple players existing for items like Yum Heart and Extension Cord, includes J&E (but does it include Forgotten?)
function EID:MultiplePlayerCharacters()
return #EID.coopAllPlayers > 1
end
-- Check the bestiary/character list if we have REPENTOGON, to help avoid spoilers. Otherwise just return true
function EID:HaveEncountered(monsterType, monsterVariant)
if not REPENTOGON then return true end
return Isaac.GetPersistentGameData():GetBestiaryEncounterCount(monsterType, monsterVariant) > 0
end
function EID:HaveNotEncountered(monsterType, monsterVariant)
if not REPENTOGON then return true end
return not EID:HaveEncountered(monsterType, monsterVariant)
end
function EID:HaveUnlockedAchievement(achID)
if not REPENTOGON then return true end
return Isaac.GetPersistentGameData():Unlocked(achID)
end
function EID:HaveNotUnlockedAchievement(achID)
if not REPENTOGON then return true end
return not EID:HaveUnlockedAchievement(achID)
end
----- Apply Conditionals -----
-- thing to fix find/replace pairs with hyphens (like "1-2") breaking because hyphen is a special char
-- https://stackoverflow.com/questions/29072601/lua-string-gsub-with-a-hyphen
local function replace(str, what, with, count)
what = string.gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1") -- escape pattern
with = string.gsub(with, "[%%]", "%%%%") -- escape replacement
return string.gsub(str, what, with, count)
end
-- super simple table concatenation: https://www.tutorialspoint.com/concatenation-of-tables-in-lua-programming
local function TableConcat(t1, t2)
for i = 1, #t2 do
t1[#t1 + 1] = t2[i]
end
return t1
end
function EID:applyConditionals(descObj)
EID:CheckPlayersCollectibles()
local typeVar = descObj.ObjType.."."..descObj.ObjVariant -- for general conditions (Tarot Cloth, Book of Virtues)
local typeVarSub = descObj.fullItemString -- for specific conditions
-- Combine general+specific conditions into one table
local conds = {}
if EID.DescriptionConditions[typeVarSub] then TableConcat(conds, EID.DescriptionConditions[typeVarSub]) end
if EID.DescriptionConditions[typeVar] then TableConcat(conds, EID.DescriptionConditions[typeVar]) end
-- Check every condition we need to check for this item
for _, cond in ipairs(conds) do
-- Search for the localization string; "S" (for generals) or "T.V.S" or "T.V.S (Modifier)" or "Modifier"
local locTable = cond.locTable or "ConditionalDescs"
local text = nil
local modifierText = type(cond.modifierText) == "function" and cond.modifierText(EID, descObj) or cond.modifierText
-- Find our string in the base localization table
if cond.noTable then
text = EID:getDescriptionEntry(modifierText, nil, cond.noFallback)
-- Find our string as either "Type.Var.Sub (Mod Text)" or "Mod Text"
elseif modifierText then
text = EID:getDescriptionEntry(locTable, typeVarSub .. " (" .. modifierText .. ")", cond.noFallback)
if text == nil then text = EID:getDescriptionEntry(locTable, modifierText, cond.noFallback) end
-- Find our string as either "Type.Var.Sub" or just the Subtype
else
text = EID:getDescriptionEntry(locTable, typeVarSub, cond.noFallback)
if text == nil then text = EID:getDescriptionEntry(locTable, descObj.ObjSubType, cond.noFallback) end
end
-- If we find the localization string for this condition, and it passes the test, modify the description text
local result = cond.func(descObj)
if text ~= nil and result then
local variableText, bulletpoint
if cond.useResult then
variableText = "{{NameOnlyC"..result.."}}"
bulletpoint = "Collectible"..result
else
variableText = type(cond.variableText) == "function" and cond.variableText(EID, descObj) or cond.variableText
bulletpoint = cond.bulletpoint
end
-- String = append
if type(text) == "string" then
text = EID:ReplaceVariableStr(text, 1, variableText)
local iconStr = "#"
if bulletpoint then iconStr = iconStr .. "{{" .. bulletpoint .. "}} " end
if cond.lineColor then iconStr = iconStr .. "{{" .. cond.lineColor .. "}}" end
EID:appendToDescription(descObj, iconStr .. text:gsub("#", iconStr))
-- Table with 1 entry = replace
elseif #text == 1 then
descObj.Description = EID:ReplaceVariableStr(text[1], 1, variableText)
-- Table with 2+ entries = find and replace pairs
-- Entry 1 is replaced with entry 2, entry 3 is replaced with entry 4, etc.
else
local pos = 1
while pos < #text do
local toFind = text[pos]
local replaceWith = EID:ReplaceVariableStr(text[pos + 1], 1, variableText)
if cond.replaceColor then replaceWith = "{{" .. cond.replaceColor .. "}}" .. replaceWith .. "{{CR}}" end
--"%d*%.?%d+" will grab every number group (1, 10, 0.5), this will allow us to not replace the "1" in "10" erroneously
if (type(toFind) == "number") then
local count = 0
descObj.Description = string.gsub(descObj.Description, "%d*%.?%d+", function(s)
if (s == tostring(toFind) and count == 0) then
count = count + 1
return replaceWith
end
end)
-- Basic find+replace for non-numbers
else
descObj.Description = replace(descObj.Description, tostring(toFind), replaceWith, 1)
end
pos = pos + 2
end
end
end
end
return descObj
end
+31 -5
View File
@@ -707,6 +707,9 @@ EID.InlineColors = {
["ColorLightYellow"] = KColor(1, 1, 0.5, 1),
["ColorBagComplete"] = KColor(0, 1, 0, 0.6),
["ColorBagOverfill"] = KColor(1, 0.5, 0.1, 0.6),
["ColorIsaac"] = KColor(0.89, 0.776, 0.773, 1),
["ColorTaintedIsaac"] = KColor(0.812, 0.612, 0.608, 1),
-- Swag Colors
-- Rainbow color effect
@@ -915,17 +918,40 @@ EID.LocalModePositionOffset = {
Shop = function(entity) if EID:EntitySanityCheck(entity) and not EID:IsGridEntity(entity) and entity:ToPickup() and entity:ToPickup():IsShopItem() then return Vector(0, 35) end end,
}
-- Items that dont have an effect with BFFS! item
EID.BFFSNoSynergy = {[11] = true, [81] = true, [238] = true, [239] = true, [243] = true, [265] = true, [268] = true, [269] = true, [278] = true, [280] = true, [281] = true, [387] = true, [404] = true, [405] = true, [431] = true, [436] = true, [469] = true, [472] = true, [492] = true, [516] = true, [528] = true, [542] = true, [543] = true }
-- Actives that have no additional effect from Car Battery
EID.CarBatteryNoSynergy = { [33] = true, [34] = true, [36] = true, [39] = true, [40] = true, [41] = true, [42] = true, [44] = true, [47] = true, [49] = true, [56] = true, [84] = true, [105] = true, [126] = true, [127] = true, [130] = true, [133] = true, [135] = true, [137] = true, [147] = true, [164] = true, [166] = true, [175] = true, [177] = true, [181] = true, [186] = true, [192] = true, [282] = true, [285] = true, [289] = true, [290] = true, [291] = true, [295] = true, [296] = true, [297] = true, [323] = true, [324] = true, [325] = true, [326] = true, [338] = true, [347] = true, [352] = true, [382] = true, [386] = true, [396] = true, [406] = true, [419] = true, [422] = true, [434] = true, [437] = true, [441] = true, [475] = true, [478] = true, [479] = true, [483] = true, [484] = true, [487] = true, [490] = true, [512] = true, [515] = true, [522] = true, [527] = true, [536] = true, [552] = true }
if EID.isRepentance then
EID.CarBatteryNoSynergy[34] = false -- The Book of Belial
EID.CarBatteryNoSynergy[284] = true -- D4 (essentially does nothing)
EID.CarBatteryNoSynergy[285] = false -- D10
EID.CarBatteryNoSynergy[296] = false -- Converter
EID.CarBatteryNoSynergy[323] = false -- Isaac's Tears
EID.CarBatteryNoSynergy[386] = false -- D12
EID.CarBatteryNoSynergy[421] = true -- Kidney Bean
EID.CarBatteryNoSynergy[522] = false -- Telekinesis TODO: REPENTANCE DIFFERENCES IN MAIN DESC TOO
EID.CarBatteryNoSynergy[523] = true -- Moving Box
-- Repentance actives
EID.CarBatteryNoSynergy[555] = true; EID.CarBatteryNoSynergy[577] = true; EID.CarBatteryNoSynergy[578] = true; EID.CarBatteryNoSynergy[580] = true;
EID.CarBatteryNoSynergy[585] = true; EID.CarBatteryNoSynergy[604] = true; EID.CarBatteryNoSynergy[622] = true; EID.CarBatteryNoSynergy[623] = true;
EID.CarBatteryNoSynergy[628] = true; EID.CarBatteryNoSynergy[636] = true; EID.CarBatteryNoSynergy[638] = true; EID.CarBatteryNoSynergy[640] = true;
EID.CarBatteryNoSynergy[653] = true; EID.CarBatteryNoSynergy[655] = true; EID.CarBatteryNoSynergy[703] = true; EID.CarBatteryNoSynergy[706] = true;
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
-- Familiars that have no effect from BFFS!
EID.BFFSNoSynergy = { [11] = true, [81] = true, [238] = true, [239] = true, [243] = true, [265] = true, [268] = true, [269] = true, [278] = true, [280] = true, [281] = true, [387] = true, [404] = true, [405] = true, [431] = true, [436] = true, [469] = true, [472] = true, [492] = true, [516] = true, [528] = true, [542] = true, [543] = true }
-- todo: this table is wrong, for instance brown nugget didnt have one and then got one
if REPENTANCE then
if EID.isRepentance then
EID.BFFSNoSynergy[504] = true -- Brown Nugget
EID.BFFSNoSynergy[567] = true -- Paschal Candle
EID.BFFSNoSynergy[651] = true -- Star of Bethlehem
EID.BFFSNoSynergy[697] = true -- Vanishing Twin
end
-- Familiars that have a BFFS! effect from Hive Mind
EID.HiveMindFamiliars = {}
-- 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,
@@ -933,7 +959,7 @@ EID.TaintedToRegularID = { [20] = 19, [21] = 0, [22] = 1, [23] = 2, [24] = 3, [2
-- Character IDs that are Soul/Black Hearts only: ???, The Lost, The Soul
EID.NoRedHeartsPlayerIDs = { [4] = true, [10] = true, [17] = true }
if REPENTANCE then
if EID.isRepentance then
-- ???, The Lost, Black Judas, The Soul, Tainted Judas, Tainted ???, Tainted Lost, Tainted Forgotten, Tainted Bethany, Tainted Soul
EID.NoRedHeartsPlayerIDs = { [4] = true, [10] = true, [12] = true, [17] = true, [24] = true, [25] = true, [31] = true, [35] = true, [36] = true, [40] = true }
end
@@ -949,7 +975,7 @@ EID.runeIDs = {[32]=true,[33]=true,[34]=true,[35]=true,[36]=true,[37]=true,[38]=
---------------- BAG OF CRAFTING DATA ------------------
if not REPENTANCE then return end
if not EID.isRepentance then return end
EID.BoC = {}
+7 -1
View File
@@ -235,7 +235,13 @@ local function BlackFeatherCallback(descObj)
itemCounter = itemCounter + EID.player:GetCollectibleNum(itemID)
end
for trinketID, _ in pairs(EID.blackFeatherTrinkets) do
itemCounter = itemCounter + EID.player:GetTrinketMultiplier(trinketID)
-- in AB+, GetTrinketMultiplier doesn't take an ID, it's just 1 (or 2 with Mom's Box)
if EID.isRepentance then
itemCounter = itemCounter + EID.player:GetTrinketMultiplier(trinketID)
else
if EID.player:GetTrinket(0) == trinketID then itemCounter = itemCounter + EID.player:GetTrinketMultiplier() end
if EID.player:GetTrinket(1) == trinketID then itemCounter = itemCounter + EID.player:GetTrinketMultiplier() end
end
end
local hasBox = EID.collectiblesOwned[439]