Files
lzrdblzzrd 007b972a10 Update and complete Russian localization (#989)
* Update Russian localization

* Fix Russian TMTRAINER chain spacing

* Fix blank lines after dynamic health conversion

* Update Russian item names from Dead God

* fix(ru): refresh Repentance descriptions

* fix(ru): update Repentance+ descriptions from Dead God

* Fix Russian AB+ description omissions

* Refine Russian modular and REPENTOGON descriptions

* [verified] fix(ru): finalize localization accuracy
2026-09-17 20:08:20 +02:00

1369 lines
60 KiB
Lua

local game = Game()
EID.TabPreviewID = 0
-- Modifiers switching the previewed description can cause infinite loops or undesired text, use this to help prevent it
EID.inModifierPreview = false
-- The "Item Reminder" needs to know if it shouldn't display because we're in a Hold Tab desc
EID.TabDescThisFrame = false
-- Some modifiers (e.g. Glitched Crown) want to know if Tab was pressed/released, rather than held
EID.TabHeldThisFrame = false
EID.TabHeldLastFrame = false
-- Returns true if Tab was pressed this frame, false otherwise
function EID:TabPressed()
return EID.TabHeldThisFrame and not EID.TabHeldLastFrame
end
-- Returns true if Tab was released this frame, false otherwise
function EID:TabReleased()
return EID.TabHeldLastFrame and not EID.TabHeldThisFrame
end
-- List of collectible IDs for us to check if a player owns them; feel free to add to this in mods that add description modifiers!
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_VOID] = true
EID.collectiblesToCheck["5.300.41"] = true -- Black Rune
-- Repentance modifiers
if EID.isRepentance then
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_BOOK_OF_VIRTUES] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_SPINDOWN_DICE] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_MOMS_BOX] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_BLANK_CARD] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_CLEAR_RUNE] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_PLACEBO] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_FALSE_PHD] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_FLIP] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_GLOWING_HOUR_GLASS] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_GLITCHED_CROWN] = true
EID.collectiblesToCheck[CollectibleType.COLLECTIBLE_ABYSS] = true
end
EID.collectiblesOwned = {}
EID.collectiblesAbsorbed = {}
EID.LastCollectibleCheck = 0
-- Check if any players own a collectible, returns true if at least one player has it
function EID:CheckPlayersCollectibles()
-- recheck the players' owned collectibles periodically, not every frame
-- (has to be checked regularly due to mechanics like D4 / Tainted Eden)
if EID.GameUpdateCount >= EID.LastCollectibleCheck + 15 then
EID.LastCollectibleCheck = EID.GameUpdateCount
for v,_ in pairs(EID.collectiblesToCheck) do
EID.collectiblesOwned[v] = false
EID.collectiblesAbsorbed[v] = false
-- Check for any player having the item, set collectiblesOwned to that player's ID
local result, player, playerNum = EID:PlayersHaveItem(v)
if result then EID.collectiblesOwned[v] = playerNum
-- Check for the item being inside a player's Void if it's a collectible
-- note: currently Absorb checks are only done as a backup, will always be false if it's owned legitimately
elseif type(v) == "number" then
result, player, playerNum = EID:PlayersVoidedCollectible(v)
if result then EID.collectiblesAbsorbed[v] = playerNum end
end
end
end
end
-- Handle description changes that occur while holding Map
local function TabCallback(descObj)
if EID.TabPreviewID == 0 then return descObj end
EID.TabDescThisFrame = true
EID.inModifierPreview = true
local descEntry = EID:getDescriptionObj(5, 100, EID.TabPreviewID)
EID.inModifierPreview = false
descEntry.Entity = descObj.Entity
EID.TabPreviewID = 0
return descEntry
end
-- Handle Void
local voidStatUps = { 0.2, 0.5, 1, 0.5, 0.2, 1 }
local voidStatIcons = {"{{Speed}}", "{{Tears}}", "{{Damage}}", "{{Range}}", "{{Shotspeed}}", "{{Luck}}"}
if EID.isRepentance then voidStatUps[4] = 2.5 end
local lastVoidCheck = -30
EID.VoidStatIncreases = {{},{},{}}
EID.BlackRuneStatIncreases = {{},{},{}}
EID.VoidOptionIndexes = {}
local function VoidCallback(descObj, isRune)
if EID.InsideItemReminder then return descObj end
-- Recheck RNG periodically (picking up passive collectibles will change the Void results without any easy trigger to track)
-- Do both Void and Rune here since they could both be requested in the same frame
if EID.GameUpdateCount >= lastVoidCheck + 30 or EID.RecheckVoid then
EID:VoidRoomCheck()
if EID.collectiblesOwned[477] then EID:VoidRNGCheck(Isaac.GetPlayer(EID.collectiblesOwned[477]), false) end
if EID.collectiblesOwned["5.300.41"] then EID:VoidRNGCheck(Isaac.GetPlayer(EID.collectiblesOwned["5.300.41"]), true) end
lastVoidCheck = EID.GameUpdateCount
EID.RecheckVoid = false
end
local prefix = (isRune and "{{Card41}} ") or "{{Collectible477}} "
local pickup = descObj.Entity and descObj.Entity:ToPickup()
local isAltOption = false
local shopItem = pickup and pickup:IsShopItem()
-- Test if this is an Option pedestal, Repentance only absorbs the lowest index one
-- (Don't check for options if it's a shop item, e.g. Tainted Keeper choice pedestals)
if EID.isRepentance and not shopItem then
local optionIndex = pickup and pickup.OptionsPickupIndex
local firstOption = EID.VoidOptionIndexes[optionIndex]
if (optionIndex and optionIndex ~= 0 and firstOption and descObj.ObjSubType ~= firstOption) then
EID:appendToDescription(descObj, "#" .. prefix .. "{{NameC"..firstOption.."}}" .. EID:getDescriptionEntry("VoidOptionText"))
isAltOption = true
end
end
-- Print Stat up info if Black Rune or non-active item
if isRune or EID.itemConfig:GetCollectible(descObj.ObjSubType).Type ~= 3 then
-- Afterbirth+ really can't do anything with Void and a shop item, so just return
if (not EID.isRepentance and shopItem) then return descObj end
local voidIntro = ((shopItem or isAltOption) and EID:getDescriptionEntry("VoidShopText")) or EID:getDescriptionEntry("VoidText")
local voidNames = EID:getDescriptionEntry("VoidNames")
local eidTable = (isRune and EID.BlackRuneStatIncreases) or EID.VoidStatIncreases
local increases = ((isAltOption or not descObj.Entity) and eidTable[3]) or (shopItem and eidTable[2]) or eidTable[1]
EID:appendToDescription(descObj, "#" .. prefix .. voidIntro .. "#")
for i,v in ipairs(increases) do
if v > 0 then
local statIncreaseStr = "↑ " .. voidStatIcons[i] .. " " .. voidNames[i] .. "#"
local replaceCount = 0
statIncreaseStr, replaceCount = EID:ReplaceVariableStr(statIncreaseStr, 1, "+" .. string.format("%.4g",v*voidStatUps[i]))
if replaceCount == 0 then statIncreaseStr = "↑ " .. voidStatIcons[i] .. " +" .. string.format("%.4g",v*voidStatUps[i]) .. " " .. voidNames[i] .. "#" end
EID:appendToDescription(descObj, statIncreaseStr)
end
end
-- Print unique synergies with Void and Active Items
elseif not isRune then
end
return descObj
end
local function BlackRuneCallback(descObj)
return VoidCallback(descObj, true)
end
local function RandomStatIncreaseCallback(descObj)
for i = 1, #EID.coopAllPlayers do
local player = EID.coopAllPlayers[i]
-- only add description for relevant players
if not EID.InsideItemReminder or (EID.InsideItemReminder and EID.ItemReminderPlayerEntity == player) then
local playerID = player:GetPlayerType()
EID:appendToDescription(descObj, "#"..EID:GetPlayerIcon(playerID) .. " {{ColorIsaac}}"..player:GetName().."{{CR}}#")
if descObj.ObjSubType == 240 then
-- Experimental Treatment
local itemDataTable = EID:ExperimentalTreatmentRNGCheck(player)
EID:appendToDescription(descObj, EID:GenerateDescriptionFromStatTable(itemDataTable))
else
-- Dataminer
local itemDataTable = EID:DataminerRNGCheck(player)
EID:appendToDescription(descObj, EID:GenerateDescriptionFromStatTable(itemDataTable))
end
end
end
return descObj
end
-- Map each text block of Pandora's Box to the AbsoluteStage number. Second entry is the block used for Alt-Stages
local pandoraStages = {
[1] = { 1, 1 }, -- B1
[2] = { 2, 2 }, -- B2
[3] = { 3, 3 }, -- C1
[4] = { 4, 4 }, -- C2
[5] = { 5, 5 }, -- D1
[6] = { 6, 6 }, -- D2
[7] = { 7, 7 }, -- W1
[8] = { 8, 8 }, -- W2
[9] = { 9, 9 }, -- ???
[10] = { 10, 11 }, -- Sheol, Cathedral
[11] = { 12, 13 }, -- Dark Room, Chest
[12] = { 9, 9 }, -- Void
[13] = { 14, 14 }, -- Home
}
local pandoraGreedStages = {
[1] = { 2, 2, 1 }, -- Basement
[2] = { 4, 4, 2 }, -- Cellar
[3] = { 6, 6, 3 }, -- Depths
[4] = { 8, 8, 4 }, -- Womb
[5] = { 10, 10, 5 }, -- Sheol
[6] = { 13, 13, 6 }, -- Shop
[7] = { 13, 13, 6 }, -- Chest
}
local function PandorasBoxCallback(descObj)
local hasModifier = game:IsGreedMode() and EID:getDescriptionEntry("ConditionalDescs", "5.100.297 (Greed)", true)
local level = game:GetLevel()
local stageNum = game:IsGreedMode() and level:GetStage() or level:GetAbsoluteStage()
local stageTable = game:IsGreedMode() and pandoraGreedStages or pandoraStages
local textBlockToUse = stageTable[stageNum][hasModifier and 3 or level:IsAltStage() and 2 or 1]
-- floor result information must be separated by line breaks or semicolons in localizations
local levelDescriptions = {}
local hasVoidEntry = false
for lvlDesc in string.gmatch(descObj.Description, "([^#;]+)") do
table.insert(levelDescriptions, lvlDesc)
if lvlDesc:find("???") then
hasVoidEntry = true
end
end
-- Some languages are missing the ???/Void entry or more. We account for that
if not hasVoidEntry and not hasModifier then
-- void entry is supposed to be highlighted, but its missing, or the description is lacking multiple entries
if textBlockToUse == 9 or (#levelDescriptions < 15 and textBlockToUse > 9) then
return descObj
end
-- void entry is missing and a later entry should be highlighted. We adjust the id accordingly
if textBlockToUse > 9 then
textBlockToUse = textBlockToUse - 1
end
end
-- look up the line to highlight. skip the first 2 lines, because they are generic item infos
local textBlockToHighlight = levelDescriptions[textBlockToUse + 2]
if textBlockToHighlight then
-- Escape the content of the string, because gsub() expects pattern definitions
local clearedTextBlock = string.gsub(textBlockToHighlight, "%p", "%%%0")
local replacementText = "{{ColorBagComplete}}" .. textBlockToHighlight .. "{{CR}}"
descObj.Description = string.gsub(descObj.Description, clearedTextBlock, replacementText)
end
return descObj
end
-- Handle Item Collection description addition
local function ItemCollectionPageCallback(descObj)
descObj.Name = "{{"..EID.Config["ItemCollectionColor"].."}}"..descObj.Name
local text = EID:getDescriptionEntry("CollectionPageInfo")
EID:appendToDescription(descObj, "#{{Warning}} "..text)
return descObj
end
-- Handle Black Feather dynamic damage up text
local function BlackFeatherCallback(descObj)
for i = 1,#EID.coopAllPlayers do
local player = EID.coopAllPlayers[i]
local playerType = player:GetPlayerType()
local itemCounter = 0
for itemID, _ in pairs(EID.BlackFeatherItems) do
itemCounter = itemCounter + player:GetCollectibleNum(itemID)
end
for trinketID, _ in pairs(EID.BlackFeatherTrinkets) do
if EID.isRepentance then
itemCounter = itemCounter + player:GetTrinketMultiplier(trinketID)
else
if player:HasTrinket(trinketID) then itemCounter = itemCounter + 1 end
end
end
local hasBox = player:HasCollectible(439)
local isGolden = EID.isRepentance and ((descObj.ObjSubType & TrinketType.TRINKET_GOLDEN_FLAG) == TrinketType.TRINKET_GOLDEN_FLAG)
local base = EID.isRepentance and 0.5 or 0.2
local damageMultiplied = base * itemCounter * (hasBox and 2 or 1) * (isGolden and 2 or 1)
local dmgColor = itemCounter == 0 and "CR" or (hasBox or isGolden) and "ColorGold" or "BlinkGray"
local description = EID:getDescriptionEntry("BlackFeatherInformation")
description, _ = EID:ReplaceVariableStr(description, 1, tostring(itemCounter))
description, _ = EID:ReplaceVariableStr(description, 2, "{{"..dmgColor.."}}"..damageMultiplied.."{{CR}}")
if #EID.coopAllPlayers > 1 then description = EID:GetPlayerIcon(playerType, "P" .. i .. ":") .. " " .. description end
EID:appendToDescription(descObj, "#"..description)
end
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
-- Handle dynamic Luck chance modifiers
local function LuckChanceCallback(descObj)
for i = 1,#EID.coopAllPlayers do
local player = EID.coopAllPlayers[i]
if player.Luck ~= 0 then
local playerType = player:GetPlayerType()
local result = math.max(math.min(EID.LuckFormulas[descObj.fullItemString](player.Luck), 100), 0)
local luckLine = EID:getDescriptionEntry("LuckModifier")
luckLine = EID:ReplaceVariableStr(luckLine, 1, string.format("%.3g", result))
luckLine = EID:ReplaceVariableStr(luckLine, 2, "{{BlinkGreen}}" .. string.format("%.3g", player.Luck) .. "{{CR}}")
if #EID.coopAllPlayers == 1 then EID:appendToDescription(descObj, "#{{Luck}} ")
else EID:appendToDescription(descObj, "#" .. EID:GetPlayerIcon(playerType, "P" .. i .. ":") .. " ") end
EID:appendToDescription(descObj, "{{NoLB}}" .. luckLine)
end
end
return descObj
end
-- Handle changing health up text for non-red HP players
local function HealthUpCallback(descObj)
if not EID.Config["DynamicHealthUps"] then return descObj end
local adjustedSubtype = EID:getAdjustedSubtype(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType)
if descObj.ObjVariant == 70 and descObj.ObjSubType > 2048 then adjustedSubtype = adjustedSubtype + 2048 end -- horsepill exception
local typeVarSub = descObj.ObjType.."."..descObj.ObjVariant.."."..adjustedSubtype
local closestPlayer = EID:ClosestPlayerTo(descObj.Entity)
local playerType = closestPlayer:GetPlayerType()
local heartType = EID.CharacterToHeartType[playerType] or "Red"
if heartType ~= "Red" then
-- find/replace Health Up lines
local numHearts = EID.HealthUpData[typeVarSub] or 1
local text = EID:getDescriptionEntry("RedToX", "Red to " .. heartType)
local pos = 1
while pos <= #text do
-- replace {1} with the number of hearts and {pluralize} with the plural character
local toFind = EID:ReplaceVariableStr(text[pos], numHearts)
toFind = EID:TryPluralizeString(toFind, numHearts)
if text[pos + 1] then
local replaceWith = EID:ReplaceVariableStr(text[pos + 1], numHearts)
descObj.Description = EID:SimpleReplace(descObj.Description, tostring(toFind), replaceWith, 1)
end
pos = pos + 2
end
-- pluralize
descObj.Description = EID:TryPluralizeString(descObj.Description, numHearts)
-- remove HealingRed lines entirely for Soul/Black/None health chars
descObj.Description = descObj.Description .. "#" -- gsub finds final lines better if the desc ends with a line break
if EID.HealthTypesWithoutHealing[heartType] then
-- Find any lines containing {{HealingRed}} or {{HealingHalfRed}} and remove the line
descObj.Description = descObj.Description:gsub("{{HealingRed}}(.-)#", "")
descObj.Description = descObj.Description:gsub("{{HealingHalfRed}}(.-)#", "")
end
descObj.Description = descObj.Description:gsub("^#+", ""):gsub("#+$", ""):gsub("#+", "#")
-- check if we just made the description blank
if descObj.Description:gsub("#", "") == "" then
local noEffectStr = EID:getDescriptionEntry("ConditionalDescs", "No Effect")
local playerName = "{{ColorIsaac}}"..EID:getPlayerName(playerType) .. "{{CR}}"
descObj.Description = EID:GetPlayerIcon(playerType) .. " " .. EID:ReplaceVariableStr(noEffectStr, playerName)
end
end
-- test the other players for if they have a different effect
-- todo: this simple check could have false positives (i.e. Yum Heart is identical for Soul and Black, but this thinks they'll be different)
for i = 1, #EID.coopAllPlayers do
local t = EID.coopAllPlayers[i]:GetPlayerType()
local playerHeartType = EID.CharacterToHeartType[t] or "Red"
if playerHeartType ~= heartType then
table.insert(EID.DifferentEffectPlayers, t)
end
end
return descObj
end
-- Handle Single use Item description add-on
local function SingleUseCallback(descObj)
local infoText = EID:getDescriptionEntry("SingleUseInfo")
descObj.Description = infoText .. "#" .. descObj.Description
return descObj
end
if EID.isRepentance then
-- Handle Birthright
local function BirthrightCallback(descObj)
descObj.Description = ""
if EID.InsideItemReminder then
local player = EID.ItemReminderPlayerEntity
local playerID = player:GetPlayerType()
local birthrightDesc = EID:getDescriptionEntry("birthright", playerID+1)
if birthrightDesc ~= nil then
local playerName = birthrightDesc[1] or player:GetName()
EID:appendToDescription(descObj, EID:GetPlayerIcon(playerID) .. " {{ColorIsaac}}"..playerName.."{{CR}}#"..birthrightDesc[3].."#")
end
return descObj
end
local describedPlayerTypes = {}
for i = 1, #EID.coopAllPlayers do
local player = EID.coopAllPlayers[i]
local playerID = player:GetPlayerType()
if descObj.Entity and playerID == PlayerType.PLAYER_CAIN_B and not EID.isDeathCertRoom then
-- Don't add a description if Tainted Cain is looking at a Birthright pedestal, he can't get it...
else
if not player:IsSubPlayer() and player:GetMainTwin( ):GetPlayerType() == playerID and not describedPlayerTypes[playerID] then
describedPlayerTypes[playerID] = true
local birthrightDesc = EID:getDescriptionEntry("birthright", playerID+1)
if birthrightDesc ~= nil then
local playerName = birthrightDesc[1] or player:GetName()
EID:appendToDescription(descObj, EID:GetPlayerIcon(playerID) .. " {{ColorIsaac}}"..playerName.."{{CR}}#"..birthrightDesc[3].."#")
end
end
end
end
return descObj
end
-- Handle Glowing Hourglass changed back into Hourglass description
local function GlowingHourglassCallback(descObj)
if REPENTOGON and EID.collectiblesOwned[422] then
local transformedText = EID:getDescriptionEntry("GlowingHourglassTransformed")
local numUses = Isaac.GetPlayer(EID.collectiblesOwned[422]):GetActiveItemDesc().VarData
if numUses == 3 then
-- Replace with the description of The Hourglass
descObj.Description = EID:getDescriptionObj(5, 100, 66).Description
if transformedText ~= nil then
EID:appendToDescription(descObj, "#{{Warning}} "..transformedText)
end
end
end
return descObj
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" }
-- Handle Consolation Prize stat prediction
local function ConsolationPrizeCallback(descObj)
for p = 1,#EID.coopAllPlayers do
local player = EID.coopAllPlayers[p]
local playerID = player:GetPlayerType()
local playerName = EID:getPlayerName(player:GetPlayerType())
local playerStats = {}
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()
playerPickups[2] = player:GetNumBombs() * 3
playerPickups[3] = player:GetNumKeys() * 3
playerPickups[4] = playerID == 18 and (player:GetSoulCharge() * 2) - 1 or 9999
playerPickups[5] = playerID == 36 and (player:GetBloodCharge() * 2) - 1 or 9999
local statsToDisplay = { 1 }
local lowestStat = playerStats[1]
for i = 2,4 do
if playerStats[i] == lowestStat then table.insert(statsToDisplay, i)
elseif playerStats[i] < lowestStat then
statsToDisplay = { i }
lowestStat = playerStats[i]
end
end
local pickupsToDisplay = { 1 }
lowestStat = playerPickups[1]
for i = 2,5 do
if playerPickups[i] == lowestStat then table.insert(pickupsToDisplay, i)
elseif playerPickups[i] < lowestStat then
pickupsToDisplay = { i }
lowestStat = playerPickups[i]
end
end
local newStr = "#" .. EID:GetPlayerIcon(playerID) .. " {{ColorGray}}"..playerName.."{{CR}}#"
local voidNames = EID:getDescriptionEntry("VoidNames")
for i,v in ipairs(statsToDisplay) do
local statIncreaseStr = "↑ " .. voidStatIcons[v] .. " " .. voidNames[v]
local replaceCount = 0
statIncreaseStr, replaceCount = EID:ReplaceVariableStr(statIncreaseStr, 1, "+" .. string.format("%.4g",voidStatUps[v]))
if replaceCount == 0 then statIncreaseStr = "↑ " .. voidStatIcons[v] .. " +" .. string.format("%.4g",voidStatUps[v]) .. " " .. voidNames[v] end
newStr = newStr .. statIncreaseStr
if #statsToDisplay > 1 then newStr = newStr .. "?" end
newStr = newStr .. "#"
end
local pickupNames = EID:getDescriptionEntry("PickupNames")
for i,v in ipairs(pickupsToDisplay) do
local statIncreaseStr = pickupNames[consolationPickups[v]]
-- Insert the quantity that will spawn after the icon, if there is an icon
local replaceCount = 0
statIncreaseStr, replaceCount = string.gsub(statIncreaseStr, "}} ", "}} " .. consolationQuantity[v] .. " ")
if replaceCount == 0 then statIncreaseStr = consolationQuantity[v] .. " " .. pickupNames[consolationPickups[v]] end
newStr = newStr .. statIncreaseStr
if #pickupsToDisplay > 1 then newStr = newStr .. "?" end
newStr = newStr .. "#"
end
EID:appendToDescription(descObj, newStr)
end
return descObj
end
-- Handle Spindown Dice description addition
local function SpindownDiceCallback(descObj)
-- don't display in item reminder, or if we've already printed it earlier in the desc
if EID.InsideItemReminder or string.match(descObj.Description, "#{{Collectible723}} :") then return descObj end
-- get the ID of the player that owns the Spindown Dice
local playerID = (EID.collectiblesOwned[723] or EID.collectiblesAbsorbed[723])
EID:appendToDescription(descObj, "#{{Collectible723}} :")
local refID = descObj.ObjSubType
local hasCarBattery = Isaac.GetPlayer(playerID):HasCollectible(CollectibleType.COLLECTIBLE_CAR_BATTERY)
local firstID = 0
for i = 1,EID.Config["SpindownDiceResults"] do
local spinnedID = EID:getSpindownResult(refID)
if hasCarBattery and spinnedID ~= 668 then
refID = spinnedID
spinnedID = EID:getSpindownResult(refID)
end
refID = spinnedID
if refID > 0 and refID < 4294960000 then
if i == 1 then firstID = refID end
EID:appendToDescription(descObj, "{{Collectible"..refID.."}}")
if EID.itemUnlockStates[refID] == false then EID:appendToDescription(descObj, "?") end
if EID.Config["SpindownDiceDisplayID"] then
EID:appendToDescription(descObj, "/".. refID)
end
if EID.Config["SpindownDiceDisplayName"] then
EID:appendToDescription(descObj, "/".. EID:getObjectName(5, 100, refID))
if refID == 668 then break end
if i ~= EID.Config["SpindownDiceResults"] then
EID:appendToDescription(descObj, "#{{Blank}}")
end
end
if refID == 668 then break end -- Dad's Note is not affected by Spindown Dice
if i ~= EID.Config["SpindownDiceResults"] then
EID:appendToDescription(descObj, " ->")
end
else
local errorMsg = EID:getDescriptionEntry("spindownError") or ""
EID:appendToDescription(descObj, errorMsg)
break
end
end
if hasCarBattery then
EID:appendToDescription(descObj, " " .. EID:ReplaceVariableStr(EID:getDescriptionEntry("ResultsWithX"), 1, "{{Collectible356}}"))
end
if firstID ~= 0 and EID.TabPreviewID == 0 then
EID.TabPreviewID = firstID
EID:appendToDescription(descObj, "#{{Blank}} ".. EID:getDescriptionEntry("FlipItemToggleInfo"))
end
return descObj
end
local hasBox = false
local isGolden = false
-- Handle Golden Trinket / Mom's Box description addition
local function GoldenTrinketCallback(descObj)
local trinketID = descObj.ObjSubType % TrinketType.TRINKET_GOLDEN_FLAG
local data = EID:getDescriptionEntry("goldenTrinketData", trinketID) or EID.GoldenTrinketData[trinketID]
local multiplier = 2
local textChoice = 1
if isGolden and hasBox then
multiplier = 3
textChoice = 3
elseif hasBox then
textChoice = 2
end
local count = 0
if data then
if type(data) == "number" then data = {t={data}} end
if data.goldenOnly and not isGolden then return descObj end
--custom multipliers (either manually defined, or just changing the max multiplier
if data.mults then
if isGolden and hasBox then multiplier = data.mults[2]
else multiplier = data.mults[1] end
elseif data.mult and ((isGolden and hasBox) or data.mult < 2) then multiplier = data.mult end
--custom additions table (manually defined)
-- index 1 is 2x, index 2 is 3x, index 3 is 4x
local addition = 0
if data.additions then
if hasBox then
addition = data.additions[textChoice-1] or 0
else
addition = data.additions[textChoice]
end
end
--replacing numeric text based on our multiplier
if (data.t) then
local numReplacements = 0
--"%d*%.?%d+" will grab every number group (1, 10, 0.5), this will allow us to not replace the "1" in "10" erroneously
descObj.Description = string.gsub(descObj.Description, "%d*%.?%d+", function(s)
count = 0
for _, v in ipairs(data.t) do
if (s == tostring(v) and count == 0) then
count = count + 1
numReplacements = numReplacements + 1
if v == 17 then
if multiplier == 2 then v = 16.5
elseif multiplier == 3 then v = (1/6)*100 end -- convert 17% to 33% or 50%
elseif v == 33 and (multiplier == 1.5 or multiplier == 3) then v = (1/3)*100 end -- convert 33% to 50% or 100%
return "{{ColorGold}}" .. string.format("%.4g",v * multiplier + addition) .. "{{CR}}"
end
end
end)
count = count + numReplacements
end
--replacing a phrase, such as "half a heart", with a double or tripled version
--the table should be 3 entries long (can also be 6, 9, etc. for multiple find+replaces)
--it can be 2 entries long if there's no triple effect
--If you need to differentiate between Golden and Mom's, use fullReplace
if data.findReplace then
local textTable = EID:getDescriptionEntry("goldenTrinketEffects", trinketID)
if textTable then
for i=1,#textTable,3 do
local newText = textTable[math.min(i+multiplier-1, #textTable)]
descObj.Description, count = string.gsub(descObj.Description, textTable[i], "{{ColorGold}}" .. newText .. "{{ColorText}}", 1)
end
end
end
--append some new text to the description
--the append table can be 1, 2, or 3 entries long (2 entries = double and triple, 3 entries = Golden, Mom's, Both)
if data.append then
local textTable = EID:getDescriptionEntry("goldenTrinketEffects", trinketID)
if textTable then
if #textTable == 2 and textChoice == 2 then textChoice = 1 -- some items have only 2 descriptions (double and triple), so Golden and Mom's should both choose text #1
else textChoice = math.min(textChoice, #textTable) end -- some items have only 1 description
if textTable[textChoice] ~= "" then
descObj.Description = descObj.Description .. "#{{ColorGold}}" .. textTable[textChoice]
count = 1
end
end
end
--the nuclear option: replacing the entire description; 1 = Golden, 2 = Mom's Box, 3 = Both
--only replace if the chosen language has defined it
if data.fullReplace then
local textTable = EID:getDescriptionEntry("goldenTrinketEffects", trinketID, true)
if (textTable) then
if #textTable == 2 and textChoice == 2 then textChoice = 1 -- some items have only 2 descriptions (double and triple), so Golden and Mom's should both choose text #1
else textChoice = math.min(textChoice, #textTable) end -- some items have only 1 description
descObj.Description = textTable[textChoice]
count = 1
end
end
-- we didn't replace everything; maybe the language didn't have the number? append a simple "effect is doubled/tripled"
if count == 0 and multiplier > 1 then
local goldenDesc = EID:getDescriptionEntry("goldenTrinket") or ""
if multiplier == 3 then goldenDesc = EID:getDescriptionEntry("tripledTrinket") or ""
elseif multiplier == 4 then goldenDesc = EID:getDescriptionEntry("quadrupledTrinket") end
descObj.Description = descObj.Description .. "#" .. "{{ColorGold}}"..goldenDesc
end
end
return descObj
end
-- 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()
local modified = false
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 repetitions = player:HasCollectible(451) and 2 or 1 -- Tarot Cloth removes 2 items
for indexOffset = 0, repetitions - 1 do
local oldestItem = EID.RecentlyTouchedItems[playerID][EID.OldestItemIndex[playerID] + indexOffset]
if oldestItem then
-- when inside the item reminder's overview, wipe the description before appending the results
if EID.InsideSpecialDescriptions and not modified then
descObj.Description = ""
modified = true
end
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
end
return descObj
end
local function VariableCharge(descObj, config, collID, chargeText)
local text = EID:getDescriptionEntry(chargeText or "VariableCharge")
if text ~= nil and config ~= nil and config.MimicCharge and config.MimicCharge ~= -1 and descObj.SubType ~= 48 then
text = EID:ReplaceVariableStr(text, 1, "{{NameOnlyC" .. collID .. "}}")
EID:appendToDescription(descObj, "#{{Collectible" .. collID .. "}} " .. text .. " {{"..config.MimicCharge.."}}{{Battery}}")
end
end
local hasTarot = false
-- Handle Blank Card description addition
local function BlankCardCallback(descObj)
VariableCharge(descObj, EID.itemConfig:GetCard(descObj.ObjSubType), 286)
-- If the player has Tarot Cloth and Blank Card, display additional text
if hasTarot then
local text = EID:getDescriptionEntry("BlankCardEffect")
local buffText = EID:getDescriptionEntry("tarotClothBlankCardBuffs", descObj.ObjSubType)
if buffText then EID:appendToDescription(descObj, "#{{Collectible286}} " .. text .. " " .. buffText) end
end
return descObj
end
-- Handle Clear Rune description addition
local function ClearRuneCallback(descObj)
VariableCharge(descObj, EID.itemConfig:GetCard(descObj.ObjSubType), 263)
return descObj
end
-- Handle Placebo description addition
local function PlaceboCallback(descObj)
local adjustedID = EID:getAdjustedSubtype(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType)
VariableCharge(descObj, EID.itemConfig:GetPillEffect(adjustedID-1), 348)
return descObj
end
-- Handle Experimental Pill description addition
local function ExperimentalPillCallback(descObj)
local adjustedID = EID:getAdjustedSubtype(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType)
if adjustedID - 1 ~= PillEffect.PILLEFFECT_EXPERIMENTAL then return descObj end
local horse = descObj.ObjSubType > 2048
for i = 1,#EID.coopAllPlayers do
local player = EID.coopAllPlayers[i]
local playerType = player:GetPlayerType()
local pillModifierID = 0
local goodAndBad = false
-- Check for PHD, Lucky Foot, Virgo, and False PHD
if player:HasCollectible(75) then pillModifierID = 75
elseif player:HasCollectible(46) then pillModifierID = 46
elseif player:HasCollectible(303) then pillModifierID = 303
end
-- Check for False PHD, and if we have good AND bad pills, just print the damage up text
if player:HasCollectible(654) then
if pillModifierID ~= 0 then goodAndBad = true end
pillModifierID = 654
end
if pillModifierID ~= 0 then
local expPillString = ""
if pillModifierID == 654 then
local damageUpString = ""
if horse then damageUpString = EID:getDescriptionEntry("FalsePHDHorseDamage")
else damageUpString = EID:getDescriptionEntry("FalsePHDDamage") end
if goodAndBad then
expPillString = damageUpString
else
expPillString = EID:getDescriptionEntry("ExperimentalPillFalsePHD") .. "#{{Collectible654}} " .. damageUpString
end
else expPillString = EID:getDescriptionEntry("ExperimentalPillPHD") end
EID:appendToDescription(descObj, "#")
if #EID.coopAllPlayers > 1 then EID:appendToDescription(descObj, EID:GetPlayerIcon(playerType, "P" .. i .. ":")) end
EID:appendToDescription(descObj, "{{Collectible" .. pillModifierID .. "}} " .. expPillString)
end
end
return descObj
end
-- Handle False PHD description addition
local function FalsePHDCallback(descObj)
local adjustedID = EID:getAdjustedSubtype(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType)
local horse = descObj.ObjSubType > 2048
local data = EID.pillMetadata[adjustedID-1]
if data ~= nil then
local damageUp = string.find(data.class,"3") and string.find(data.class,"-")
if adjustedID-1 == PillEffect.PILLEFFECT_SHOT_SPEED_DOWN then damageUp = true end
-- why doesn't I'm Excited have a - in the xml data yet spawn a black heart...
local blackHeart = (string.find(data.class,"2") or string.find(data.class,"1")) and (string.find(data.class,"-") or adjustedID-1 == PillEffect.PILLEFFECT_IM_EXCITED)
local text = ""
if damageUp and horse then text = EID:getDescriptionEntry("FalsePHDHorseDamage")
elseif damageUp then text = EID:getDescriptionEntry("FalsePHDDamage")
elseif blackHeart then text = EID:getDescriptionEntry("FalsePHDHeart") end
if text ~= "" then
local iconStr = "#{{Collectible654}} "
EID:appendToDescription(descObj, iconStr..text)
end
end
return descObj
end
-- Handle co-op players seeing different pill effects
local function CoopPillCallback(descObj)
-- Don't do this check when holding Tab for pill effects
if not descObj.Entity then return descObj end
local printedDescs = { [descObj.Name] = true }
for i = 1,#EID.coopAllPlayers do
local player = EID.coopAllPlayers[i]
EID.pillPlayer = player
local playerID = player:GetPlayerType()
-- Regrab the pill description object with this player
local pillObj = EID:getDescriptionObjByEntity(descObj.Entity)
-- only print a given pill description once, even if it affects multiple players
-- if it's 4P co-op and P1 has PHD, and P2/P3/P4 all get same effect, no one wants to see that 3 times
if not printedDescs[pillObj.Name] then
-- new pill effect to print
printedDescs[pillObj.Name] = true
EID:appendToDescription(descObj, "#" .. EID:GetPlayerIcon(playerID, "P" .. i .. ":") .." {{ColorObjName}}"..pillObj.Name .."{{CR}}#"..pillObj.Description)
end
end
EID.pillPlayer = nil
return descObj
end
-- Handle Flip description addition
local function FlipCallback(descObj)
local flipItemID = EID:getEntityData(descObj.Entity, "EID_FlipItemID")
if not flipItemID or flipItemID <= 0 or not EID.Config["DisplayFlipItemDescriptions"] then return descObj end
-- Empty pedestal
if descObj.ObjSubType == 0 then
return EID:getDescriptionObj(5, 100, flipItemID)
end
local infoText = EID:getDescriptionEntry("FlipItemToggleInfo")
local itemName = EID:getObjectName(5, 100, flipItemID)
local appendText = "#{{Collectible711}} -> {{Collectible"..flipItemID.."}} "..itemName
if EID.TabPreviewID == 0 then
EID.TabPreviewID = flipItemID
appendText = appendText .. "#{{Blank}} "..infoText
end
EID:appendToDescription(descObj, appendText)
return descObj
end
-- Handle Tainted Cain pedestals
local function TaintedCainPedestalCallback(descObj)
if game.Challenge == Challenge.CHALLENGE_CANTRIPPED or EID.isDeathCertRoom or not descObj.Entity or not EID.Config["DisplayTCainSalvageResults"] then return descObj end
local item = EID.itemConfig:GetCollectible(descObj.ObjSubType)
if (item.Tags and item.Tags & ItemConfig.TAG_QUEST == ItemConfig.TAG_QUEST) then return descObj end
local closestPlayer = EID:ClosestPlayerTo(descObj.Entity)
local playerType = closestPlayer:GetPlayerType()
if playerType == PlayerType.PLAYER_CAIN_B then
-- Construct the Salvage description
local hasBirthright = closestPlayer:HasCollectible(619)
local salvageDesc = hasBirthright and EID:getDescriptionEntry("TaintedCainPedestalBaseBirthright") or EID:getDescriptionEntry("TaintedCainPedestalBase")
local pickupNames = EID:getDescriptionEntry("PickupNames")
-- add T-Cain icon at start of line
salvageDesc = "{{Player23}} ".. salvageDesc
-- Guaranteed items from room type
local roomType = game:GetLevel():GetCurrentRoomDesc().Data.Type
local roomPool = game:GetItemPool():GetPoolForRoom(roomType, 69)
if roomType ~= RoomType.ROOM_ERROR and EID.SalvageRoomTypes[roomPool] then
local newLine = EID.RoomTypeToMarkup[roomType] .. " " .. EID:getDescriptionEntry("TaintedCainPedestalGuaranteed")
newLine = EID:ReplaceVariableStr(newLine, 1, pickupNames[EID.SalvageRoomTypes[roomPool]]:gsub("{{[.-}}]+ ", ""))
newLine = EID:ReplaceVariableStr(newLine, "n", EID.PickupStartsWithVowel[EID.SalvageRoomTypes[roomPool]] and "n" or "")
salvageDesc = salvageDesc .. "#" .. newLine
end
-- Bonus items from trinkets
for trinketID,pickupID in pairs(EID.SalvageTrinkets) do
if closestPlayer:HasTrinket(trinketID) then
local newLine = "{{Trinket" .. trinketID .. "}} " .. EID:getDescriptionEntry("TaintedCainPedestalBonus")
newLine = EID:ReplaceVariableStr(newLine, 1, pickupNames[pickupID]:gsub("{{[.-}}]+ ", ""))
salvageDesc = salvageDesc .. "#" .. newLine
end
end
-- Bonus item from Lucky Toe
if closestPlayer:HasTrinket(42) then
local newLine = "{{Trinket42}} " .. (hasBirthright and EID:getDescriptionEntry("TaintedCainPedestalLuckyToeBirthright") or EID:getDescriptionEntry("TaintedCainPedestalLuckyToe"))
salvageDesc = salvageDesc .. "#" .. newLine
end
-- Daemon's Tail reduces hearts
if closestPlayer:HasTrinket(22) then
local newLine = "{{Trinket22}} " .. EID:getDescriptionEntry("TaintedCainPedestalDaemonsTail")
salvageDesc = salvageDesc .. "#" .. newLine
end
descObj.Description = salvageDesc
descObj.Transformation = nil -- remove transformation info. useless for T-Cain because item will not count towards transformations
for i = 1, #EID.coopAllPlayers do
local t = EID.coopAllPlayers[i]:GetPlayerType()
if t ~= PlayerType.PLAYER_CAIN_B then
table.insert(EID.DifferentEffectPlayers, t)
end
end
else
table.insert(EID.DifferentEffectPlayers, PlayerType.PLAYER_CAIN_B)
end
return descObj
end
-- Handle Glitched Crown style pedestals
local currentSelection = {} -- keep track of which description we're looking at for a given pedestal
local goingToSpindown = false
local inGlitchedCrown = false
local function GlitchedCrownCallback(descObj)
if not descObj.Entity or inGlitchedCrown then return descObj end
local entity = descObj.Entity
local curRoomIndex = game:GetLevel():GetCurrentRoomIndex()
local pedestalID = descObj.Entity.InitSeed .. descObj.Entity.Index
if EID.GlitchedCrownCheck[curRoomIndex] and EID.GlitchedCrownCheck[curRoomIndex][pedestalID] then
-- this table has collectible ID keys that define the first frame and most recent frame that that ID has been seen on this pedestal
-- we need to filter out items that haven't been seen in a while (due to a reroll perhaps), then sort by first frame
local items = EID.GlitchedCrownCheck[curRoomIndex][pedestalID]
local sortedItems = {}
for id, frames in pairs(items) do
if EID.GameUpdateCount - frames[2] > 120 then
items[id] = nil
else
table.insert(sortedItems, { id, frames[1] })
end
end
if #sortedItems < 5 then return descObj end
table.sort(sortedItems, function(a, b) return a[2] < b[2] end)
inGlitchedCrown = true
currentSelection[pedestalID] = currentSelection[pedestalID] or 0
-- watch for Tab being pressed to advance our selection by 1
-- when spindown dice is involved, watch for tab being released instead of pressed, it makes more sense
if goingToSpindown and EID:TabReleased() or not goingToSpindown and EID:TabPressed() then
currentSelection[pedestalID] = currentSelection[pedestalID] + 1
if currentSelection[pedestalID] > #sortedItems then currentSelection[pedestalID] = 0 end
end
-- display the overview description
if currentSelection[pedestalID] == 0 then
descObj = EID:getDescriptionObj(5, 100, 689, nil, false)
descObj.Description = ""
for _, item in ipairs(sortedItems) do
descObj.Description = descObj.Description .. "{{Collectible" .. item[1] .. "}} {{NameOnlyC" .. item[1] .. "}}#"
end
-- display a specific description
else
descObj = EID:getDescriptionObj(5, 100, sortedItems[currentSelection[pedestalID]][1])
end
descObj.Entity = entity
local nextIcon = "{{Collectible689}}"
if currentSelection[pedestalID] + 1 <= #sortedItems then nextIcon = "{{Collectible" ..
sortedItems[currentSelection[pedestalID] + 1][1] .. "}}" end
EID:appendToDescription(descObj,
"#{{Blank}} " .. EID:ReplaceVariableStr(EID:getDescriptionEntry("GlitchedCrownToggleInfo"), 1, nextIcon))
-- manually apply Flip; changing the description's item stops future callbacks to avoid infinite loops, but we want Flip still, it works fine
if descObj.ObjSubType ~= entity.SubType then
if EID.collectiblesOwned[711] then descObj = FlipCallback(descObj) end
-- manually apply Tainted Cain
if EID:PlayersHaveCharacter(PlayerType.PLAYER_CAIN_B) then descObj = TaintedCainPedestalCallback(descObj) end
end
end
inGlitchedCrown = false
return descObj
end
-----------------------------
-- Abyss Locust Handling-----
-----------------------------
local function GetChanceString(effectID, chance1, chance2, chance3)
local chanceText = ""
local chance = (effectID == 3 and chance3 < 1 and chance3) or (effectID == 2 and chance2 < 1 and chance2) or chance1 or 1
if chance ~= 1 then
chanceText = EID:getDescriptionEntry("AbyssTexts", "Chance")
local chancePercent = string.format("%.2f", chance * 100):gsub("%.?0+$", "") -- formated and without trailing zeros
chanceText = EID:ReplaceVariableStr(chanceText, chancePercent)
end
return chanceText
end
local function GetFlagString(id, tableName, color, flagArray, chance1, chance2, chance3)
local text = ""
for _, locustFlag in ipairs(flagArray) do
local locustEffectText = EID:getDescriptionEntry(tableName, locustFlag)
if locustEffectText then
text = text .. "#".. color .. locustEffectText .. GetChanceString(id, chance1, chance2, chance3)
end
if not EID.isRepentancePlus then
-- In Repentance, there is a bug where only the first flag of the array is able to trigger
return text
end
end
return text
end
local function AbyssCallback(descObj)
local textColor = "{{ColorRed}}"
-- Display explicit "abyssSynergies" table entry, if present
local overrideDesc = EID:getDescriptionEntry("abyssSynergies", descObj.ObjSubType)
if overrideDesc then
EID:appendToDescription(descObj, "#{{Collectible706}} " .. textColor .. overrideDesc)
return descObj
end
-- Display automatically generated description
-- Default locust Data
local locustData = { 1, 1, 1, { -1 }, { -1 }, { -1 }, { -1 }, { -1 }, { -1 }, 1, 1, 1, 1, 1 }
-- Check if an XML entry exists and load if exists
if EID.XMLLocusts and EID.XMLLocusts[descObj.ObjSubType] then
locustData = EID.XMLLocusts[descObj.ObjSubType]
end
local descriptionText = ""
local amount = locustData[1]
local scale = locustData[2]
local speed = locustData[3]
local locustFlags1 = locustData[4] -- array
local locustFlags2 = locustData[5] -- array
local locustFlags3 = locustData[6] -- array
local tearFlags1 = locustData[7] -- array
local tearFlags2 = locustData[8] -- array
local tearFlags3 = locustData[9] -- array
local procChance1 = locustData[10]
local procChance2 = locustData[11]
local procChance3 = locustData[12]
local damageMultiplier1 = locustData[13]
local damageMultiplier2 = locustData[14]
-- base damage via Quality and multiplier
local damageText = EID:getDescriptionEntry("AbyssTexts", "DamageMult")
local dmg = (EID.isRepentancePlus and EID.QualityToLocustDamageMultiplier[descObj.Quality] or 1) * damageMultiplier1
damageText = EID:ReplaceVariableStr(damageText, dmg)
-- size
local scaleText = ""
if scale < 1 then scaleText = EID:getDescriptionEntry("AbyssTexts", "SizeSmall")
elseif scale > 1 then scaleText = EID:getDescriptionEntry("AbyssTexts", "SizeBig") end
-- speed
local speedText = ""
if speed < 1 then speedText = EID:getDescriptionEntry("AbyssTexts", "SpeedSlow")
elseif speed >=6 then speedText = EID:getDescriptionEntry("AbyssTexts", "SpeedDash")
elseif speed > 1 then speedText = EID:getDescriptionEntry("AbyssTexts", "SpeedFast") end
-- overview / headline
local infoText = amount > 1 and EID:getDescriptionEntry("AbyssTexts", "InfoTextPlural") or EID:getDescriptionEntry("AbyssTexts", "InfoText")
descriptionText = "#{{Collectible706}} " .. textColor .. infoText
descriptionText = EID:ReplaceVariableStr(descriptionText, "amount", amount)
descriptionText = EID:ReplaceVariableStr(descriptionText, "size", scaleText)
descriptionText = EID:ReplaceVariableStr(descriptionText, "speed", speedText)
descriptionText = EID:ReplaceVariableStr(descriptionText, "dmg", damageText)
-- damage multiplier based on proc chance
if damageMultiplier2 ~= 1 then
local dmgText2 = EID:ReplaceVariableStr(EID:getDescriptionEntry("AbyssTexts", "DamageMult"), damageMultiplier2)
descriptionText = descriptionText .. "#"..textColor .. dmgText2 .. GetChanceString(1, procChance1, procChance2, procChance3)
end
-- create list of locust effect descriptions
descriptionText = descriptionText .. GetFlagString(1, "AbyssLocustEffects", textColor, locustFlags1, procChance1, procChance2, procChance3)
descriptionText = descriptionText .. GetFlagString(2, "AbyssLocustEffects", textColor, locustFlags2, procChance1, procChance2, procChance3)
descriptionText = descriptionText .. GetFlagString(3, "AbyssLocustEffects", textColor, locustFlags3, procChance1, procChance2, procChance3)
-- create list of tear effect descriptions
descriptionText = descriptionText .. GetFlagString(1, "TearFlagNames", textColor, tearFlags1, procChance1, procChance2, procChance3)
descriptionText = descriptionText .. GetFlagString(2, "TearFlagNames", textColor, tearFlags2, procChance1, procChance2, procChance3)
descriptionText = descriptionText .. GetFlagString(3, "TearFlagNames", textColor, tearFlags3, procChance1, procChance2, procChance3)
-- pluralize
descriptionText = EID:TryPluralizeString(descriptionText, amount)
-- Put everything together
EID:appendToDescription(descObj, descriptionText)
return descObj
end
---------------------------------------
---- Book of Virtues Wisp Handling-----
---------------------------------------
local ringIconLookup = {
[-1] = "{{StationaryWisp}}",
[0] = "{{InnerWisp}}",
[1] = "{{MiddleWisp}}",
[2] = "{{OuterWisp}}"
}
local function BookOfVirtuesWispDescriptionBuilder(descObj, itemID)
local textColor = "{{ColorPastelBlue}}"
-- Get explicit "bookOfVirtuesWisps" table entry, if present
local additionalDesc = EID:getDescriptionEntry("bookOfVirtuesWisps", itemID)
-- Display automatically generated description
-- Check if an XML entry exists and load if exists
if not EID.XMLWisps or not EID.XMLWisps[itemID] then
if additionalDesc then -- try to display additional Description if available
EID:appendToDescription(descObj, "#{{VirtuesCollectible"..itemID.."}} " .. textColor .. additionalDesc)
end
return descObj
end
-- Disable removal of stat up icons for this description object
descObj.IgnoreBulletPointIconConfig = true
-- read xml data
local wispData = EID.XMLWisps[itemID]
local descriptionText = ""
local hp = wispData[1]
local layer = wispData[2]
local damage = wispData[3]
local stageDamage = wispData[4]
local damageMultiplier2 = wispData[5]
local shotSpeed = wispData[6]
local fireDelay = wispData[7]
local procChance = wispData[8]
local canShoot = wispData[9] and fireDelay ~= -1
local amount = wispData[10]
local tearFlags = wispData[11] -- array
local tearFlags2 = wispData[12] -- array
-- Display "No Wisp" text if defined. else, display more detailed description
if EID.WispData.NoWisp[itemID] then
local noWispsText = EID:getDescriptionEntry("BookOfVirtuesWispTexts", "NoWisps")
descriptionText = "#{{VirtuesCollectible"..itemID.."}} " .. textColor .. noWispsText
else
-- base damage via damage, stage damage and firedelay
-- Display "-" text if it cant shoot
local damageText = "-"
if canShoot then
local dmg = (damage + (stageDamage * (game:GetLevel():GetAbsoluteStage() - 1))) * (30 / fireDelay)
damageText = string.format("%.2f", dmg):gsub("%.?0+$", "") -- formated and without trailing zeros
end
-- HP Text
local hpText = EID:getDescriptionEntry("BookOfVirtuesWispTexts", "Health")
hpText = EID:ReplaceVariableStr(hpText, hp)
-- HP and damage text
local statText = EID:getDescriptionEntry("BookOfVirtuesWispTexts", "StatDescription")
statText = EID:ReplaceVariableStr(statText, "ringIcon", textColor .. ringIconLookup[layer] or ringIconLookup[1])
statText = EID:ReplaceVariableStr(statText, "amount", amount ~= 0 and amount or "") -- TODO: Fix issue where color definition before icon definition causes 1 extra space
statText = EID:ReplaceVariableStr(statText, "health", hp)
statText = EID:ReplaceVariableStr(statText, "damage", damageText)
-- overview / headline
descriptionText = "#{{VirtuesCollectible"..itemID.."}} " .. textColor .. statText
-- Single room warning
if EID.WispData.SingleRoom[itemID] then
local singleRoomText = EID:getDescriptionEntry("BookOfVirtuesWispTexts", "SingleRoom")
descriptionText = descriptionText .. "#{{Warning}} " .. textColor .. singleRoomText
end
if canShoot then
-- shot speed
if shotSpeed ~= 1 then
local shotSpeedText = EID:getDescriptionEntry("BookOfVirtuesWispTexts", "Shotspeed")
local shotSpeedtxt = string.format("%.2f", shotSpeed * 100):gsub("%.?0+$", "") -- formated and without trailing zeros
shotSpeedText = EID:ReplaceVariableStr(shotSpeedText, shotSpeedtxt)
descriptionText = descriptionText .. "#{{Shotspeed}} " .. textColor .. shotSpeedText
end
-- damage multiplier based on proc chance
if damageMultiplier2 ~= 1 then
local damageText2 = EID:ReplaceVariableStr(EID:getDescriptionEntry("BookOfVirtuesWispTexts", "Damage"), damageMultiplier2 * 100)
descriptionText = descriptionText .. "#{{Damage}} " .. textColor .. damageText2 .. GetChanceString(2, procChance, procChance, 0)
end
-- create list of tear effect descriptions
descriptionText = descriptionText .. GetFlagString(1, "TearFlagNames", "{{Shotspeed}} " .. textColor, tearFlags, procChance, procChance)
descriptionText = descriptionText .. GetFlagString(2, "TearFlagNames", "{{Shotspeed}} " .. textColor, tearFlags2, procChance, procChance)
end
end
-- add additional descriptions. Do a loop to ensure correct line color
if additionalDesc then
for line in string.gmatch(additionalDesc, "([^#]+)") do
descriptionText = descriptionText .. "#" .. textColor .. line
end
end
-- pluralize
descriptionText = EID:TryPluralizeString(descriptionText, amount)
-- Put everything together
EID:appendToDescription(descObj, descriptionText)
return descObj
end
-- Handle Book of Virtues description addition
local function BookOfVirtuesCallback(descObj)
-- Display players' current active item's wisp effect when looking at a Book of Virtues pedestal
if descObj.ObjSubType == 584 and not EID.InsideItemReminder then
for i = 1,#EID.coopAllPlayers do
local player = EID.coopAllPlayers[i]
local activeItemID = player:GetActiveItem()
if activeItemID > 0 then
descObj = BookOfVirtuesWispDescriptionBuilder(descObj, activeItemID)
end
end
return descObj
end
-- Display wisp effect of a pedestal / your active while holding Book of Virtues
return BookOfVirtuesWispDescriptionBuilder(descObj, descObj.ObjSubType)
end
--------------------------------
-- Although individual conditions/callbacks work well for mods to be able to add through the API,
-- As we kept adding callbacks for vanilla items, a lot of code got repeated over and over
-- This one-condition setup fixes that and can only help performance
-- It also allows us to pick the order that modifiers are appended to descriptions (stats/effects first, then reroll/flip/recharge info)
-- REPENTANCE-ONLY MODIFIERS
local function EIDConditions(descObj)
-- currently, only pickup descriptions have modifiers
if descObj.ObjType ~= 5 then return false end
EID:CheckPlayersCollectibles()
local callbacks = {}
-- Collectible Pedestal Callbacks
if descObj.ObjVariant == PickupVariant.PICKUP_COLLECTIBLE then
-- Using magic numbers here in case it's slightly faster, and because the callback names give context
-- Check Birthright first because it overwrites the description instead of appending to it
if descObj.ObjSubType == 619 then table.insert(callbacks, BirthrightCallback) end
if (REPENTOGON or EID.collectiblesOwned[689]) and EID.Config["GlitchedCrownSummary"] then table.insert(callbacks, GlitchedCrownCallback) end
-- Glowing Hourglass overwrites the description when used three times
if REPENTOGON and descObj.ObjSubType == 422 then table.insert(callbacks, GlowingHourglassCallback) end
if descObj.ObjSubType == 644 then table.insert(callbacks, ConsolationPrizeCallback) end
if EID.collectiblesOwned[584] or descObj.ObjSubType == 584 then table.insert(callbacks, BookOfVirtuesCallback) end
if EID:PlayersHaveCharacter(PlayerType.PLAYER_CAIN_B) then table.insert(callbacks, TaintedCainPedestalCallback) end
if EID.collectiblesOwned[711] and EID:getEntityData(descObj.Entity, "EID_FlipItemID") then table.insert(callbacks, FlipCallback) end
if EID.collectiblesOwned[706] then table.insert(callbacks, AbyssCallback) end
if EID.Config["SpindownDiceResults"] > 0 and (EID.collectiblesOwned[723] or EID.collectiblesAbsorbed[723]) and descObj.ObjSubType ~= 668 then
goingToSpindown = true
table.insert(callbacks, SpindownDiceCallback)
else goingToSpindown = false end
-- Card / Rune Callbacks
elseif descObj.ObjVariant == PickupVariant.PICKUP_TAROTCARD then
hasTarot = EID.collectiblesOwned[451]
if EID.collectiblesOwned[286] and EID.itemConfig:GetCard(descObj.ObjSubType) and EID.itemConfig:GetCard(descObj.ObjSubType):IsCard() then table.insert(callbacks, BlankCardCallback) end
if EID.collectiblesOwned[263] and EID.itemConfig:GetCard(descObj.ObjSubType) and EID.itemConfig:GetCard(descObj.ObjSubType):IsRune() 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] and EID.itemConfig:GetPillEffect(descObj.ObjSubType) then table.insert(callbacks, PlaceboCallback) end
table.insert(callbacks, ExperimentalPillCallback)
if EID.pillPlayer == nil and #EID.coopAllPlayers > 1 then
table.insert(callbacks, CoopPillCallback)
end
-- Trinket Callbacks
elseif descObj.ObjVariant == PickupVariant.PICKUP_TRINKET then
-- Golden Trinket / Mom's Box
isGolden = ((descObj.ObjSubType & TrinketType.TRINKET_GOLDEN_FLAG) == TrinketType.TRINKET_GOLDEN_FLAG)
hasBox = EID.collectiblesOwned[439]
if isGolden or hasBox then table.insert(callbacks, GoldenTrinketCallback) end
end
return callbacks
end
EID:addDescriptionModifier("EID Repentance", EIDConditions, nil)
end
-- AFTERBIRTH+ OR REPENTANCE MODIFIERS
local function EIDConditionsAB(descObj)
-- currently, only pickup descriptions have modifiers
if descObj.ObjType ~= 5 then return false end
EID:CheckPlayersCollectibles()
local callbacks = {}
if EID.LuckFormulas[descObj.fullItemString] then table.insert(callbacks, LuckChanceCallback) end
local adjustedSubtype = EID:getAdjustedSubtype(descObj.ObjType, descObj.ObjVariant, descObj.ObjSubType)
local typeVarSub = descObj.ObjType.."."..descObj.ObjVariant.."."..adjustedSubtype
if EID.HealthUpData[typeVarSub] or EID.HealingItemData[typeVarSub] then table.insert(callbacks, HealthUpCallback) end
-- Collectible Pedestal Callbacks
if descObj.ObjVariant == PickupVariant.PICKUP_COLLECTIBLE then
if EID.Config["ItemCollectionIndicator"] and EID:requiredForCollectionPage(descObj.ObjSubType) then table.insert(callbacks, ItemCollectionPageCallback) end
if EID.SingleUseCollectibles[descObj.ObjSubType] then table.insert(callbacks, SingleUseCallback) end
if descObj.ObjSubType == 297 then table.insert(callbacks, PandorasBoxCallback) end
if EID.Config["DisplayVoidStatInfo"] then
if EID.collectiblesOwned[477] then table.insert(callbacks, VoidCallback) end
if EID.collectiblesOwned["5.300.41"] then table.insert(callbacks, BlackRuneCallback) end
end
if EID.Config["ItemReminderShowRNGCheats"] then
if descObj.ObjSubType == 240 then table.insert(callbacks, RandomStatIncreaseCallback) end
if descObj.ObjSubType == 481 then table.insert(callbacks, RandomStatIncreaseCallback) end
end
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
EID:addDescriptionModifier("EID Afterbirth+", EIDConditionsAB, nil, 1)
-- should this be done differently so that mods can add tab previews? (tab conditions is done last, but would be done before callbacks mods add, maybe tab should be checked in EID:getDescriptionObj
local function TabConditions(_)
if EID.TabHeldThisFrame and not EID.inModifierPreview then return true end
EID.TabPreviewID = 0
return false
end
EID:addDescriptionModifier("EID Tab Previews", TabConditions, TabCallback)
-- If an item has a radically different effect for 2 players, show the effect for the closest player, then append what chars have a different effect here
-- This is the final change made to the desc, after all other conditionals and modifiers
local function CoopDifferentEffectCallback(descObj)
local displayedChars = {}
local differentEffectText = EID:getDescriptionEntry("ConditionalDescs", "Different Effect")
for _,v in ipairs(EID.DifferentEffectPlayers) do
if not displayedChars[v] then
descObj.Description = descObj.Description .. "#" .. EID:GetPlayerIcon(v) .. " " .. EID:ReplaceVariableStr(differentEffectText, EID:getPlayerName(v))
end
displayedChars[v] = true
end
EID.DifferentEffectPlayers = {} -- avoid appending this twice
return descObj
end
local function CoopDifferentEffectConditions(_)
return #EID.DifferentEffectPlayers > 0
end
EID:addDescriptionModifier("EID Different Effect Players", CoopDifferentEffectConditions, CoopDifferentEffectCallback)