Merge pull request #280 from Buurazu/master

Add basic modded items support to Bag of Crafting
This commit is contained in:
wofsauge authored and GitHub committed 2022-01-01 21:53:18 +01:00
commit dda3f8ff2a
8 files changed
+124 -39

No files matched your search

+1 -1
View File
@@ -450,7 +450,7 @@ EID.descriptions["en_us"].collectibles={
{"435", "Lil Loki", "Shoots 4 tears in a cross pattern#Deals 3.5 damage per shot"},
{"436", "Milk!", "After taking damage, you gain a Tears up for the rest of the room"},
{"437", "D7", "Respawn all enemies of the room#Allows you to farm room clear rewards#!!! If used in a Greed fight, it can reroll the room into a Shop"},
{"438", "Binky", "+1 Soul Heart#↑ +0.7 Tears up#↑ Makes Isaac very small, reducing his hitbox size"},
{"438", "Binky", "+1 Soul Heart#↑ +0.75 Tears up#↑ Makes Isaac very small, reducing his hitbox size"},
{"439", "Mom's Box", "Spawns a random trinket#↑ +1 Luck while held#While held, doubles the effect of trinkets"},
{"440", "Kidney Stone", "Randomly while firing you will stop firing and release a burst of tears and a kidney stone#↓ -0.2 Speed down#↓ -17 Range down#↑ +2 Tear Height"},
{"441", "Mega Blast", "Fires a huge Mega Satan laser for 15 seconds#The laser persists between rooms and floors"},
+6 -6
View File
@@ -138,7 +138,7 @@ local repCollectibles={
[345] = {"345", "Synthoil", "↑ +1 Damage up#↑ +1.5 Range up"}, -- Synthoil
[346] = {"346", "A Snack", "↑ +1 Health up#Heals 1 Red Heart"}, -- A Snack
[350] = {"350", "Toxic Shock", "At the start of every room every enemy gets a poison effect#Killed enemies leave puddle of creep#Grants immunity for toxic clouds"}, -- Toxic Shock
[352] = {"352", "Glass Cannon", "Fires one large piercing spectral tear ((DMG x 10) + 10)#!!! When you take damage:#↓ You will lose an extra 2 hearts of health#↑ + 1.5 Range up#Self-damage does not count"}, -- Glass Cannon
[352] = {"352", "Glass Cannon", "Fires one large piercing spectral tear ((DMG x 10) + 10)#!!! When you take damage:#↓ You will lose an extra 2 hearts of health#↑ +1.5 Range up and leave blood creep for the room#The extra damage can't kill you#Self-damage does not count"}, -- Glass Cannon
[354] = {"354", "Crack Jacks", "↑ +1 Health up#Spawns random trinket on the floor#Heals 1 Red Heart"}, -- Crack Jacks
[355] = {"355", "Mom's Pearls", "↑ +0.38 Range up#↑ +1 Luck up#+1 Soul Heart"}, -- Mom's Pearls
[360] = {"360", "Incubus", "Gain a demonic familiar that shoots the same tears as Isaac#Deals 75% of your damage unless playing as Lilith"}, -- Incubus
@@ -290,7 +290,7 @@ local repCollectibles={
[639] = {"639", "Yuck Heart", "Grants a Rotten Heart upon use"}, -- Yuck Heart
[640] = {"640", "Urn of Souls", "Spews a stream of flames#Gains charges each time an enemy dies"}, -- Urn of Souls
[641] = {"641", "Akeldama", "Creates a chain of tears behind you while in combat#The tears deal 3.5 damage"}, -- Akeldama
[642] = {"642", "Magic Skin", "Consumes 1 Heart Container or 2 Soul Hearts and replaces them with a Broken Heart#Spawns an item from the current room's item pool#The more times it's used, the higher the chance it replaces future items if Isaac isn't holding it#Lower chance if Magic Skin is on a pedestal on the current floor#{{Warning}} Single use item for The Lost, doesn't replace future items"}, -- Magic Skin
[642] = {"642", "Magic Skin", "Turns 1 Heart Container or 2 Soul Hearts into a Broken Heart#Spawns an item from the current room's item pool#The more times it's used, the higher the chance it replaces future items if Isaac isn't holding it#33% after 1 use, 50% after 2, 100% after 3#Lower chance if Magic Skin is on a pedestal on the current floor#{{Warning}} Single use item for The Lost"}, -- Magic Skin
[643] = {"643", "Revelation", "Grants flight and 2 Soul Hearts#Shooting for 2.5 seconds will fire a holy laser when fire button is released"}, -- Revelation
[644] = {"644", "Consolation Prize", "Increases your lowest stat#Drops either 3 coins, 1 bomb or 1 key depending on what you have the least of"}, -- Consolation Prize
[645] = {"645", "Tinytoma", "Teratoma orbital#Blocks shots#Deals contact damage#Splits into smaller versions of itself upon taking 3 hits#The smaller versions break into blue spiders#Respawns 5 seconds after it's fully gone"}, -- Tinytoma
@@ -350,8 +350,8 @@ local repCollectibles={
[699] = {"699", "Azazel's Rage", "Build rage every time a room is cleared#After 4 rooms, fires a large Brimstone laser when entering the next room"}, -- Azazel's Rage
[700] = {"700", "Echo Chamber", "When Isaac uses a Card, Pill, or Rune, he also uses a copy of the last 3 Cards/Pills/Runes he used after picking up Echo Chamber"}, -- Echo Chamber
[701] = {"701", "Isaac's Tomb", "Spawns an Old Chest at the start of every floor which requires a key to unlock#Can contain Soul Hearts, trinkets or items#Items are from the {{AngelRoom}}Angel Room pool and Mom and Dad themed"}, -- Isaac's Tomb
[702] = {"702", "Vengeful Spirit", "After taking damage, spawns an orbital wisp that lasts for the entire floor#Maximum of 6#They fire tears, but do not block shots"}, -- Vengeful Spirit
[703] = {"703", "Esau Jr.", "Swaps between the current character and Esau Jr., who has 3 Black Hearts, +2 damage and flight#Characters have independent items and health#{{Warning}} Dying as either character ends the run"}, -- Esau Jr.
[702] = {"702", "Vengeful Spirit", "After taking damage, spawns an orbital wisp that lasts for the entire floor#Maximum of 6#They fire tears but do not block shots"}, -- Vengeful Spirit
[703] = {"703", "Esau Jr.", "Swaps between the current character and Esau Jr., who has 3 Black Hearts, +2 Damage and flight#Characters have independent items and health#On first use, he gains random passive items equal to how many you have#{{Warning}} Dying as either character ends the run"}, -- Esau Jr.
[704] = {"704", "Berserk!", "Berserk mode for 5 seconds:#↑ +0.4 Speed up#↑ Tears up#↑ +3 Damage up#Restricts attacks to a melee weapon"}, -- Berserk!
[705] = {"705", "Dark Arts", "Gain +1 Speed and walk through enemies and bullets for 1 second#On touch, deal 2x your damage + 3.5 damage per touched enemy#Explosion on effect end that deals damage equal to how much you dealt"}, -- Dark Arts
[706] = {"706", "Abyss", "Destroys all items in the room and spawns an attack fly familiar for each#Effects of fly depends on the items"}, -- Abyss
@@ -844,7 +844,7 @@ local repCards={
[94] = {"94", "Soul of Apollyon", "Spawn 15 random locust flies"}, -- Soul of Apollyon
[95] = {"95", "Soul of the Forgotten", "Spawns The Forgotten as a temporary secondary character for current room"}, -- Soul of the Forgotten
[96] = {"96", "Soul of Bethany", "Spawns 6 {{Collectible584}}Book of Virtues wisps with random properties"}, -- Soul of Bethany
[97] = {"97", "Soul of Jacob and Esau", "Spawns Esau as a temporary secondary character for current room"}, -- Soul of Jacob and Esau
[97] = {"97", "Soul of Jacob and Esau", "Spawns Esau as a temporary secondary character for current room#He spawns with random passive items equal to how many you have"}, -- Soul of Jacob and Esau
}
EID:updateDescriptionsViaTable(repCards, EID.descriptions["en_us"].cards)
@@ -981,4 +981,4 @@ EID.descriptions["en_us"].AchievementWarningText = "Achievements are disabled!#I
EID.descriptions["en_us"].OldGameVersionWarningText = "Your version of Repentance is not up-to-date!#Only the newest version is officially supported#(This warning can be disabled in the config)"
EID.descriptions["en_us"].ModdedRecipesWarningText = "Modded items are likely to make the crafting recipe calculation inaccurate!#Use the No Recipes display mode or turn off the Bag of Crafting display if your recipes are incorrect#(This warning can be disabled in the config)"
EID.descriptions["en_us"].ModdedRecipesWarningText = "Modded items could make the crafting recipe calculation inaccurate!#Use the No Recipes display mode or turn off the Bag of Crafting display if your recipes are incorrect#(This warning can be disabled in the config)"
+105 -24
View File
@@ -170,6 +170,14 @@ local componentShifts = {
{0x00000011, 0x0000000F, 0x0000001A}
}
local poolToIcon = { [0]="{{TreasureRoom}}",[1]="{{Shop}}",[2]="{{BossRoom}}",[3]="{{DevilRoom}}",[4]="{{AngelRoom}}",
[5]="{{SecretRoom}}",[7]="{{PoopRoomIcon}}",[8]="{{GoldenChestRoomIcon}}",[9]="{{RedChestRoomIcon}}",[12]="{{CursedRoom}}",[26]="{{Planetarium}}" }
local CraftingMaxItemID = EID.XMLMaxItemID
local CraftingFixedRecipes = EID.XMLRecipes
local CraftingItemPools = EID.XMLItemPools
local CraftingItemQualities = EID.XMLItemQualities
--These are recipes that have already been calculated, plus the contents of recipes.xml
local calculatedRecipes = {}
--Backup recipes in case of potential achievement lock
@@ -179,19 +187,26 @@ local lastSeedUsed = 0
--A list of item IDs, sorted by quality, then by name, to help with sorting our recipe list faster
local sortedIDs = {}
for i = 1, EID.XMLMaxItemID do
if EID.XMLItemQualities[i] ~= nil then
table.insert(sortedIDs, i)
end
end
table.sort(sortedIDs, function(a, b)
if EID.XMLItemQualities[a] == EID.XMLItemQualities[b] then
return (EID:getObjectName(5, 100, a) < EID:getObjectName(5, 100, b))
else
return (EID.XMLItemQualities[a] > EID.XMLItemQualities[b])
local function sortAllItems()
sortedIDs = {}
for i = 1, CraftingMaxItemID do
if CraftingItemQualities[i] ~= nil then
table.insert(sortedIDs, i)
end
end
end)
table.sort(sortedIDs, function(a, b)
if CraftingItemQualities[a] == CraftingItemQualities[b] then
return (EID:getObjectName(5, 100, a) < EID:getObjectName(5, 100, b))
else
return (CraftingItemQualities[a] > CraftingItemQualities[b])
end
end)
end
-- delay the initial sort until needed, as it's a tad slow
local sortNeeded = true
local customRNGSeed = 0x77777770
local customRNGShift = {0,0,0}
@@ -226,9 +241,6 @@ function EID:getBagOfCraftingID(Variant, SubType)
return nil
end
local poolToIcon = { [0]="{{TreasureRoom}}",[1]="{{Shop}}",[2]="{{BossRoom}}",[3]="{{DevilRoom}}",[4]="{{AngelRoom}}",
[5]="{{SecretRoom}}",[7]="{{PoopRoomIcon}}",[8]="{{GoldenChestRoomIcon}}",[9]="{{RedChestRoomIcon}}",[12]="{{CursedRoom}}",[26]="{{Planetarium}}" }
function EID:simulateBagOfCrafting(componentsTable)
local components = componentsTable
local compTotalWeight = 0
@@ -292,10 +304,10 @@ function EID:simulateBagOfCrafting(componentsTable)
qualityMin = 0
qualityMax = 2
end
local pool = EID.XMLItemPools[poolWeight.idx + 1]
local pool = CraftingItemPools[poolWeight.idx + 1]
for _, item in ipairs(pool) do
local quality = EID.XMLItemQualities[item[1]]
local quality = CraftingItemQualities[item[1]]
if quality >= qualityMin and quality <= qualityMax then
local w = item[2] * poolWeight.weight
poolWeight.totalWeight = poolWeight.totalWeight + w
@@ -339,7 +351,7 @@ function EID:calculateBagOfCrafting(componentsTable)
local componentsAsString = table.concat(components, ",")
--Check the fixed recipes. Currently, the fixed recipes ignore item unlock status
local cacheResult = EID.XMLRecipes[componentsAsString]
local cacheResult = CraftingFixedRecipes[componentsAsString]
if cacheResult ~= nil then
return cacheResult, cacheResult
end
@@ -382,7 +394,7 @@ function EID:calculateBagOfCrafting(componentsTable)
local itemWeights = {}
local maxItemID = EID.XMLMaxItemID
local maxItemID = CraftingMaxItemID
for i = 1, maxItemID do
itemWeights[i] = 0
end
@@ -414,10 +426,10 @@ function EID:calculateBagOfCrafting(componentsTable)
qualityMin = 0
qualityMax = 2
end
local pool = EID.XMLItemPools[poolWeight.idx + 1]
local pool = CraftingItemPools[poolWeight.idx + 1]
for _, item in ipairs(pool) do
local quality = EID.XMLItemQualities[item[1]]
local quality = CraftingItemQualities[item[1]]
if quality >= qualityMin and quality <= qualityMax then
local w = item[2] * poolWeight.weight
itemWeights[item[1]] = itemWeights[item[1]] + w
@@ -459,6 +471,64 @@ function EID:calculateBagOfCrafting(componentsTable)
end
end
-- simple table of tables copy function
local function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
local moddedCrafting = false
-- Check for modded items past the known max item ID on game start (can also support game updates)
-- Only works if the new items are at Weight 1.0 in their item pools, but better than nothing
local function GameStartCrafting()
if EID.itemConfig:GetCollectible(EID.XMLMaxItemID+1) ~= nil then
-- Items past max ID detected; copy our XML tables so we can add to them
CraftingMaxItemID = EID.XMLMaxItemID
CraftingItemPools = deepcopy(EID.XMLItemPools)
CraftingItemQualities = deepcopy(EID.XMLItemQualities)
-- Add new item qualities
local coll = EID.itemConfig:GetCollectible(CraftingMaxItemID+1)
while coll ~= nil do
CraftingMaxItemID = CraftingMaxItemID + 1
CraftingItemQualities[coll.ID] = coll.Quality
coll = EID.itemConfig:GetCollectible(CraftingMaxItemID+1)
end
local itemPool = game:GetItemPool()
-- Add new items to the crafting item pools, assuming Weight 1.0
for poolNum,_ in pairs(poolToIcon) do
for i=1,EID.XMLMaxItemID do itemPool:AddRoomBlacklist(i) end
local collID = itemPool:GetCollectible(poolNum, false, 1, 25)
while collID ~= 25 do
table.insert(CraftingItemPools[poolNum+1], {collID, 1.0})
itemPool:AddRoomBlacklist(collID)
collID = itemPool:GetCollectible(poolNum, false, 1, 25)
end
itemPool:ResetRoomBlacklist()
end
moddedCrafting = true
sortNeeded = true
elseif moddedCrafting then
CraftingMaxItemID = EID.XMLMaxItemID
CraftingItemPools = EID.XMLItemPools
CraftingItemQualities = EID.XMLItemQualities
moddedCrafting = false
sortNeeded = true
end
end
EID:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, GameStartCrafting)
------------------------------------------
------------------------------------------
-----------Bag content detection ---------
@@ -702,6 +772,7 @@ function EID:handleBagOfCraftingRendering()
lockedRecipes = {}
calcResultCache = {}
randResultCache = {}
lockedResults = nil
end
lastSeedUsed = curSeed
@@ -812,9 +883,14 @@ function EID:handleBagOfCraftingRendering()
--sort by ingredient quality
table.sort(itemQuery, qualitySort)
--No Recipes Mode display
if (EID.Config["BagOfCraftingDisplayMode"] == "No Recipes") then
if EID.Config["BagOfCraftingDisplayMode"] == "Pickups Only" then
EID:appendToDescription(customDescObj, getHotkeyString())
EID:appendToDescription(customDescObj, getFloorItemsString(false, roomItems))
EID:printDescription(customDescObj)
return true
elseif EID.Config["BagOfCraftingDisplayMode"] == "No Recipes" then
EID:appendToDescription(customDescObj, getHotkeyString())
EID:appendToDescription(customDescObj, getFloorItemsString(false, roomItems))
@@ -834,6 +910,11 @@ function EID:handleBagOfCraftingRendering()
EID:printDescription(customDescObj)
return true
end
if sortNeeded then
sortAllItems()
sortNeeded = false
end
local queryString = table.concat(itemQuery,",")
if lockedResults ~= nil then
@@ -995,12 +1076,12 @@ function EID:handleBagOfCraftingRendering()
--tack on the secondary recipe image to achievement-locked recipes
if v[3] then customDescObj.Description = customDescObj.Description.."({{Collectible" .. v[3] .. "}})" end
--color the equals sign with the item quality, so the order of the list can make sense
customDescObj.Description = customDescObj.Description.. qualities[EID.XMLItemQualities[v[2]]] .. "={{CR}}"
customDescObj.Description = customDescObj.Description.. qualities[CraftingItemQualities[v[2]]] .. "={{CR}}"
--only display the item name if it's the first occurrence
else
if prevItem ~= v[2] then
--substring the first 18 characters of the item name so it fits on one line; is there a way to get around desc line length limits?
customDescObj.Description = customDescObj.Description.."# {{Collectible"..v[2].."}} ".. qualities[EID.XMLItemQualities[v[2]]] ..
customDescObj.Description = customDescObj.Description.."# {{Collectible"..v[2].."}} ".. qualities[CraftingItemQualities[v[2]]] ..
string.sub(EID:getObjectName(5, 100, v[2]),1,18).."#"
else
customDescObj.Description = customDescObj.Description.."#"
+2 -1
View File
@@ -211,10 +211,11 @@ EID.UserConfig = {
-- Default = "always"
["DisplayBagOfCrafting"] = "always",
-- Display modes for the Bag of Crafting display
-- Options: "Recipe List", "Preview Only", "No Recipes"
-- Options: "Recipe List", "Preview Only", "No Recipes", "Pickups Only"
-- The recipe list is a detailed calculated list of recipes based on what you have available on the floor
-- Preview Only shows the description of the item you can currently craft in your bag
-- No Recipes shows percentages of what item you might get from your bag / best option on the floor, for a more intended experience
-- Pickups Only just shows the room/floor pickup count
-- (No Recipes is recommended if you have modded items)
-- Default = "Recipe List"
["BagOfCraftingDisplayMode"] = "Recipe List",
+2 -2
View File
@@ -408,6 +408,6 @@ EID.GoldenTrinketData = {
[159] = {goldenOnly = true, fullReplace = true, mult=1}, [160] = 1, [162] = {t={50}, mults={1.32, 2}}, [163] = {t={1},mult=2}, [164] = 1,
-- Modeling Clay, Polished Bone (25, 33, 50), Hollow Heart, Kid's Drawing, Crystal Key
[166] = {t={50},mult=2}, [167] = {t={25}, mults={1.32, 2}}, [168] = 1, [169] = {t={1}, goldenOnly = true, mult = 2}, [170] = {t={33}, mults={1.5,3}},
-- Lil Clot, Swallowed M80, The Twins (default text), Cricket Leg (1/6, 2/6, 3/6), Apollyon's Best Friend, Broken Glasses
[176] = 1, [178] = {t={50},mult=2}, [183] = 0, [185] = 1, [186] = 1, [187] = {t={50,50}, mult=2},
-- Lil Clot, Swallowed M80, The Twins (default text), Cricket Leg (17%, 1 in 6), Apollyon's Best Friend, Broken Glasses
[176] = 1, [178] = {t={50},mult=2}, [183] = 0, [185] = 17, [186] = 1, [187] = {t={50,50}, mult=2},
}
+4 -1
View File
@@ -232,7 +232,10 @@ local game = Game()
descObj.Description = string.gsub(descObj.Description, "%d*%.?%d+", function(s)
if (s == tostring(v) and count == 0) then
count = count + 1
if v == 33 and (multiplier == 1.5 or multiplier == 3) then v = (1/3)*100 end -- convert 33% to 50% or 100%
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) .. "{{ColorText}}"
end
end)
+1 -1
View File
@@ -640,7 +640,7 @@ local function renderAchievementInfo()
demoDescObj.Description = EID:getDescriptionEntry("OldGameVersionWarningText") or ""
EID:displayPermanentText(demoDescObj)
hasShownAchievementWarning = true
elseif EID.player:HasCollectible(710) and EID:DetectModdedItems() and EID.Config.DisplayBagOfCrafting ~= "never" and EID.Config.BagOfCraftingDisplayMode ~= "No Recipes" then
elseif EID.player:HasCollectible(710) and EID:DetectModdedItems() and EID.Config.DisplayBagOfCrafting ~= "never" and (EID.Config.BagOfCraftingDisplayMode == "Recipe List" or EID.Config.BagOfCraftingDisplayMode == "Preview Only") then
local demoDescObj = EID:getDescriptionObj(-999, -1, 1)
demoDescObj.Name = EID:getDescriptionEntry("AchievementWarningTitle") or ""
demoDescObj.Description = EID:getDescriptionEntry("ModdedRecipesWarningText") or ""
+3 -3
View File
@@ -1205,7 +1205,7 @@ if MCMLoaded then
}
)
-- Bag of Crafting Display Mode
local bagDisplayModes = {"Recipe List","Preview Only","No Recipes"}
local bagDisplayModes = {"Recipe List","Preview Only","No Recipes","Pickups Only"}
MCM.AddSetting(
"EID",
"Crafting",
@@ -1215,14 +1215,14 @@ if MCMLoaded then
return AnIndexOf(bagDisplayModes, EID.Config["BagOfCraftingDisplayMode"])
end,
Minimum = 1,
Maximum = 3,
Maximum = 4,
Display = function()
return "Display Mode: " .. EID.Config["BagOfCraftingDisplayMode"]
end,
OnChange = function(currentNum)
EID.Config["BagOfCraftingDisplayMode"] = bagDisplayModes[currentNum]
end,
Info = {"Toggle showing a list of calculated recipes, showing just an item preview when the bag is full, or showing percentages of what item you might get (No Recipes is recommended if using modded items)"}
Info = {"Toggle showing a list of recipes, an item preview when bag is full, what item pool/quality you might get, or only the floor pickups"}
}
)
-- Bag of Crafting Hide in Battle