diff --git a/descriptions/rep/en_us.lua b/descriptions/rep/en_us.lua index b9c648da..91173a68 100644 --- a/descriptions/rep/en_us.lua +++ b/descriptions/rep/en_us.lua @@ -22,7 +22,7 @@ local repCollectibles={ [24] = {"24", "Dessert", "↑ +1 Health up#Heals one heart"}, -- Dessert [25] = {"25", "Breakfast", "↑ +1 Health up#Heals one heart"}, -- Breakfast [26] = {"26", "Rotten Meat", "↑ +1 Health up#Heals one heart"}, -- Rotten Meat - [29] = {"29", "Moms Underwear", "↑ +1.5 Range up#↑ +0.5 Tear Height#Spawns 3-6 Blue Flies"}, -- Moms Underwear + [29] = {"29", "Mom's Underwear", "↑ +1.5 Range up#↑ +0.5 Tear Height#Spawns 3-6 Blue Flies"}, -- Mom's Underwear [30] = {"30", "Mom's Heels", "↑ +1.5 Range up#↑ +0.5 Tear Height#Touched enemies take 12 damage"}, -- Mom's Heels [31] = {"31", "Mom's Lipstick", "↑ +2.25 Range up#↑ +0.5 Tear Height#Spawns 1 random heart"}, -- Mom's Lipstick [40] = {"40", "Kamikaze!", "Causes a big explosion at the player's location#Deals 185 damage"}, -- Kamikaze! @@ -75,7 +75,7 @@ local repCollectibles={ [214] = {"214", "Anemic", "↑ +1.5 Range up#Leave creep on floor when getting hit"}, -- Anemic [218] = {"218", "Placenta", "↑ +1 Health up#Heals 1 red heart#Regenerate health slowly"}, -- Placenta [224] = {"224", "Cricket's Body", "↓ -20% Range down#Tears split up in 4 on hit#Splitted tears deal half damage#↑ -1 tear delay that can go below capped value"}, -- Cricket's Body - [228] = {"228", "Mom's Perfume", "Chance to shoot fearing tears#↑ -1 tear delay that can go below capped value"}, -- Moms Perfume + [228] = {"228", "Mom's Perfume", "Chance to shoot fearing tears#↑ -1 tear delay that can go below capped value"}, -- Mom's Perfume [230] = {"230", "Abaddon", "↑ +1.5 Damage up#↑ +0.2 Speed up#Fear tears#Converts all red heart container into Black hearts#+2 Black hearts"}, -- Abaddon [232] = {"232", "Stop Watch", "Permanent slow effect for enemies#↑ +0.3 Speed up"}, -- Stop Watch [240] = {"240", "Experimental Treatment", "↑ Gain 4 random Stat Ups and ↓2 Stat Downs"}, -- Experimental Treatment @@ -672,6 +672,6 @@ EID.descriptions["en_us"].horsepills={ EID.descriptions["en_us"].CraftingBagContent = "Bag:" EID.descriptions["en_us"].CraftingRoomContent = "Room:" EID.descriptions["en_us"].CraftingFloorContent = "Floor:" -EID.descriptions["en_us"].CraftingResults = "Results:#(Scroll: hold {{CONFIG_BoC_Toggle}} + {{ButtonY}} {{ButtonA}})" +EID.descriptions["en_us"].CraftingResults = "(Scroll: hold {{CONFIG_BoC_Toggle}} + {{ButtonY}} {{ButtonA}},#Lock: {{ButtonX}}, Refresh: {{ButtonB}})" EID.descriptions["en_us"].CraftingBagModError = "!!! {{ColorRed}}ERROR:#{{ColorRed}}The \"Crafting result calculation\" feature currently does not work when item mods are installed!#{{ColorRed}}Please uninstall the item mods or deactivate the Bag of Crafting descriptions in the config" diff --git a/eid_bagOfCrafting.lua b/eid_bagOfCrafting.lua index ec00c31b..0f67bf2f 100644 --- a/eid_bagOfCrafting.lua +++ b/eid_bagOfCrafting.lua @@ -506,6 +506,11 @@ EID.bagOfCraftingFloorQuery = {} EID.BagItems = {} EID.icount = 0 +EID.lockedResults = nil +EID.refreshNextTick = false +EID.downHeld = 0 +EID.upHeld = 0 + local function GetMaxCollectibleID() local id = CollectibleType.NUM_COLLECTIBLES-1 local step = 16 @@ -564,11 +569,12 @@ function EID:handleBagOfCraftingRendering() if detectModdedItems() or Game():GetRoom():GetFrameCount ()<2 then return false end - local curRoomIndex = Game():GetLevel():GetCurrentRoomIndex() + local curRoomIndex = Game():GetLevel():GetCurrentRoomDesc().SafeGridIndex local results = {} local roomItems = {} local pickups = Isaac.FindByType(5, -1, -1, true, false) + if EID.bagOfCraftingCurPickupCount ~= #pickups then for i, entity in ipairs(pickups) do local craftingIDs = EID:getBagOfCraftingID(entity.Variant, entity.SubType) @@ -586,34 +592,93 @@ function EID:handleBagOfCraftingRendering() end local itemQuery = {} + local itemCount = {} + + --max 8 copies of a single item in our list, to avoid repeat recipes for i, v in ipairs(EID.bagOfCraftingFloorQuery) do - table.insert(itemQuery, v) + if (not itemCount[v] or itemCount[v] < 8) then + table.insert(itemQuery, v) + if (not itemCount[v]) then itemCount[v] = 1 + else itemCount[v] = itemCount[v] + 1 end + end end for i, v in ipairs(EID.BagItems) do - table.insert(itemQuery, v) + if (not itemCount[v] or itemCount[v] < 8) then + table.insert(itemQuery, v) + if (not itemCount[v]) then itemCount[v] = 1 + else itemCount[v] = itemCount[v] + 1 end + end end -- Calculate result from pickups on floor if #itemQuery < 8 then return false end - table.sort(itemQuery, function(a, b) return a > b end) - local queryString = table.concat(itemQuery,",") - if randResultCache[queryString] == nil then - local randResults = {} - for i = 0, 250 do - local newTable = {} - local tableCopy = {table.unpack(itemQuery)} - for k = 1, 8 do - local pos = math.random(1, #tableCopy) - table.insert(newTable, tableCopy[pos]) - table.remove(tableCopy, pos) - end - table.sort(newTable, function(a, b) return a > b end) - randResults[table.concat(newTable,",")] = newTable + local function qualitySort(a, b) + if (pickupValues[a+1] == pickupValues[b+1]) then + return a > b + else + return pickupValues[a+1] > pickupValues[b+1] end + end + + --sort by ingredient quality + table.sort(itemQuery, qualitySort) + + local queryString = table.concat(itemQuery,",") + if EID.lockedResults ~= nil then + results = randResultCache[EID.lockedResults] + elseif randResultCache[queryString] == nil or EID.refreshNextTick then + local randResults = {} + local skipRandom = false + --check every single possible recipe for our highest value pickups + --limit it in the options, since the number of total combinations quickly grows (nCr): + --12 = 495, 13 = 1287, 14 = 3003, 15 = 6435, 16 = 12870 + local mostValuable = {} + --we have less items than our threshold; every single recipe will be listed + if (#itemQuery <= EID.Config["BagOfCraftingCombinationMax"]) then + mostValuable = itemQuery + skipRandom = true + else + for i=1,EID.Config["BagOfCraftingCombinationMax"] do + mostValuable[i] = itemQuery[i] + end + end + --this combination algorithm was adopted from this Java code: https://stackoverflow.com/a/16256122 + --note that it will run into duplicates, for example if you have eight pennies and a key, it can't tell the difference between + --PPPPPPPK (pennies 1-7) and PPPPPPPK (pennies 2-8) and PPPPPPPK (pennies 1-4,6-8) etc..., I don't know of a way to prevent that + local function combinations(arr, length, startPos, tempResult) + local length = length or 8 + local startPos = startPos or 1 + local tempResult = tempResult or {} + if (length == 0) then + randResults[table.concat(tempResult,",")] = {table.unpack(tempResult)} + return + end + for i = startPos, #arr-length+1 do + tempResult[8-length+1] = arr[i] + combinations(arr,length-1,i+1,tempResult) + end + end + combinations(mostValuable) + + --do random pulls for some more recipe choices + if (not skipRandom) then + for i = 0, EID.Config["BagOfCraftingRandomResults"] do + local newTable = {} + local tableCopy = {table.unpack(itemQuery)} + for k = 1, 8 do + local pos = math.random(1, #tableCopy) + table.insert(newTable, tableCopy[pos]) + table.remove(tableCopy, pos) + end + table.sort(newTable, qualitySort) + randResults[table.concat(newTable,",")] = newTable + end + end + local calcResults = {} for k, v in pairs(randResults) do local resultID = EID:calculateBagOfCrafting(v) @@ -623,7 +688,18 @@ function EID:handleBagOfCraftingRendering() end randResultCache[queryString] = calcResults results = calcResults - EID.bagOfCraftingOffset = 0 + --sort our final results by quality, then alphabetical item name + table.sort(results, function(a, b) + if (EID.itemWeightsLookup[a[2]] == EID.itemWeightsLookup[b[2]]) then + return (EID:getObjectName(5, 100, a[2]) < EID:getObjectName(5, 100, b[2])) + else + return (EID.itemWeightsLookup[a[2]] > EID.itemWeightsLookup[b[2]]) + end + end) + + --don't reset our location on refresh, so you can more easily refresh for lower quality recipes + if (not EID.refreshNextTick) then EID.bagOfCraftingOffset = 0 end + EID.refreshNextTick = false else results = randResultCache[queryString] end @@ -667,38 +743,66 @@ function EID:handleBagOfCraftingRendering() EID.player:SetShootingCooldown(2) if Input.IsActionTriggered(ButtonAction.ACTION_SHOOTDOWN, EID.player.ControllerIndex) then EID.bagOfCraftingOffset = math.min(#results-(#results%EID.Config["BagOfCraftingResults"]), EID.bagOfCraftingOffset + EID.Config["BagOfCraftingResults"]) + EID.downHeld = Isaac.GetTime() elseif Input.IsActionTriggered(ButtonAction.ACTION_SHOOTUP, EID.player.ControllerIndex) then EID.bagOfCraftingOffset = math.max(0, EID.bagOfCraftingOffset - EID.Config["BagOfCraftingResults"]) + EID.upHeld = Isaac.GetTime() + --lock the current results so you can actually do a recipe that you've scrolled down to without losing it + elseif Input.IsActionTriggered(ButtonAction.ACTION_SHOOTLEFT, EID.player.ControllerIndex) then + if (EID.lockedResults == nil) then EID.lockedResults = queryString + else EID.lockedResults = nil end + --refresh the recipes + elseif Input.IsActionTriggered(ButtonAction.ACTION_SHOOTRIGHT, EID.player.ControllerIndex) then + if (EID.lockedResults == nil) then EID.refreshNextTick = true end + end + --scroll pages quickly if the button is held + if Input.IsActionPressed(ButtonAction.ACTION_SHOOTDOWN, EID.player.ControllerIndex) and Isaac.GetTime() - EID.downHeld > 750 then + EID.bagOfCraftingOffset = math.min(#results-(#results%EID.Config["BagOfCraftingResults"]), EID.bagOfCraftingOffset + EID.Config["BagOfCraftingResults"]) + EID.downHeld = Isaac.GetTime() - 700 + elseif Input.IsActionPressed(ButtonAction.ACTION_SHOOTUP, EID.player.ControllerIndex) and Isaac.GetTime() - EID.upHeld > 750 then + EID.bagOfCraftingOffset = math.max(0, EID.bagOfCraftingOffset - EID.Config["BagOfCraftingResults"]) + EID.upHeld = Isaac.GetTime() - 700 end end + --fix bug with being allowed to go to an empty page if recipe count = multiple of page size (or if we refresh on last page) + if (EID.bagOfCraftingOffset >= #results) then EID.bagOfCraftingOffset = EID.bagOfCraftingOffset - EID.Config["BagOfCraftingResults"] end local resultCount = 0 local skips = 0 - for quality = 4, 0, -1 do -- sort by result quality - for i, v in ipairs(results) do - if EID.itemWeightsLookup[v[2]]== quality then - if skips < EID.bagOfCraftingOffset then - skips = skips + 1 - if skips == EID.bagOfCraftingOffset then - customDescObj.Description = customDescObj.Description.."#{{Blank}} ...+"..skips.." more" - end - else - customDescObj.Description = customDescObj.Description.."# {{Collectible"..v[2].."}} =" - customDescObj.Description = customDescObj.Description..EID:tableToCraftingIconsMerged(v[1]) - resultCount = resultCount + 1 - if resultCount > EID.Config["BagOfCraftingResults"]-1 then - if #results > EID.Config["BagOfCraftingResults"] then - customDescObj.Description = customDescObj.Description.."#{{Blank}} ...+"..(#results-EID.Config["BagOfCraftingResults"]- skips).." more" - end - break - end - end + local prevItem = 0 + + local qualities = { [0] = "{{ColorSilver}}", "{{ColorLime}}", "{{ColorTransform}}", "{{ColorObjName}}", "{{ColorGold}}" } + local prefix = "#{{Blank}} " + if (EID.lockedResults) then + prefix = "#{{Trinket159}} " + end + + --results is now sorted by quality and item name, and doesn't iterate over the entire table, instead starting at the page offset + if (EID.bagOfCraftingOffset > 0) then + customDescObj.Description = customDescObj.Description.. prefix .. "...+"..EID.bagOfCraftingOffset.." more" + end + for i=EID.bagOfCraftingOffset+1,EID.bagOfCraftingOffset+EID.Config["BagOfCraftingResults"] do + local v = results[i] + if (not v) then break end + if (not EID.Config["BagOfCraftingDisplayNames"]) then + customDescObj.Description = customDescObj.Description.."# {{Collectible"..v[2].."}} " .. " =" + --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.itemWeightsLookup[v[2]]] .. + string.sub(EID:getObjectName(5, 100, v[2]),1,18).."#" + else + customDescObj.Description = customDescObj.Description.."#" end end - if resultCount > EID.Config["BagOfCraftingResults"]-1 then - break - end + customDescObj.Description = customDescObj.Description..EID:tableToCraftingIconsMerged(v[1]) + prevItem = v[2] end + if (EID.bagOfCraftingOffset + EID.Config["BagOfCraftingResults"] < #results) then + customDescObj.Description = customDescObj.Description.. prefix .. "...+"..(#results-EID.Config["BagOfCraftingResults"]-EID.bagOfCraftingOffset).." more" + end + EID:printDescription(customDescObj) return true end diff --git a/eid_config.lua b/eid_config.lua index 9089de35..8d144fac 100644 --- a/eid_config.lua +++ b/eid_config.lua @@ -170,10 +170,21 @@ EID.UserConfig = { -- Higher numbers can cause more lag! -- Default = 7 ["BagOfCraftingResults"] = 7, + -- Changes the number of ingredients for which every combination is checked + -- Higher numbers will cause lag spikes on new pickup sets! + -- Default = 12 + ["BagOfCraftingCombinationMax"] = 12, + -- Changes the number of random recipes calculated + -- Higher numbers will cause lag spikes on new pickup sets! + -- Default = 500 + ["BagOfCraftingRandomResults"] = 500, -- Set the keybinding to toggle the scroll feature of the bag of crafting descriptions -- look into the AB+ or Repentance documentation for the key names here: https://wofsauge.github.io/IsaacDocs/rep/enums/ButtonAction.html -- Default = ButtonAction.ACTION_MAP ["BagOfCraftingToggleKey"] = ButtonAction.ACTION_MAP, + -- Display craftable item names, moving the recipe to a new line + -- Default = false + ["BagOfCraftingDisplayNames"] = false, ---------Mouse Controls----------- @@ -270,7 +281,10 @@ EID.DefaultConfig = { ["DisplayDiceInfo"] = true, ["DisplayBagOfCrafting"] = "always", ["BagOfCraftingResults"] = 7, + ["BagOfCraftingCombinationMax"] = 12, + ["BagOfCraftingRandomResults"] = 500, ["BagOfCraftingToggleKey"] = ButtonAction.ACTION_MAP, + ["BagOfCraftingDisplayNames"] = false, ["SpindownDiceResults"] = 3, ["EnableMouseControls"] = false, ["ShowCursor"] = false, @@ -280,4 +294,4 @@ EID.DefaultConfig = { ["EnableEntityDescriptions"] = true, ["ShowQuality"] = true, ["ShowObjectID"] = false, -} \ No newline at end of file +} diff --git a/mod_config_menu.lua b/mod_config_menu.lua index e8981ba6..6b71f2ae 100644 --- a/mod_config_menu.lua +++ b/mod_config_menu.lua @@ -648,7 +648,7 @@ if MCMLoaded then ) -- Spindown Dice results - local diceSteps = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + local diceSteps = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} MCM.AddSetting( "EID", "Display", @@ -965,9 +965,71 @@ if MCMLoaded then AnIndexOf(diceSteps, EID.Config["BagOfCraftingResults"]) - 1 .. " " .. EID.Config["BagOfCraftingResults"] end, OnChange = function(currentNum) - EID.Config["BagOfCraftingResults"] = diceSteps[currentNum%#diceSteps + 1] + EID.Config["BagOfCraftingResults"] = diceSteps[currentNum + 1] end, - Info = {"Preview of items currently craftable with Bag of crafting"} + Info = {"Page size for the preview of items currently craftable with Bag of Crafting"} + } + ) + -- Bag of Crafting thorough recipe checks + local combSteps = {8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18} + MCM.AddSetting( + "EID", + "Crafting", + { + Type = ModConfigMenu.OptionType.SCROLL, + CurrentSetting = function() + return AnIndexOf(combSteps, EID.Config["BagOfCraftingCombinationMax"]) - 1 + end, + Display = function() + return "Thorough Calculations: $scroll" .. + AnIndexOf(combSteps, EID.Config["BagOfCraftingCombinationMax"]) - 1 .. " " .. EID.Config["BagOfCraftingCombinationMax"] + end, + OnChange = function(currentNum) + EID.Config["BagOfCraftingCombinationMax"] = combSteps[currentNum + 1] + end, + Info = {"Get every recipe for the X best components; setting this high will cause lag spikes (12 = 500 combinations, 14 = 3,000, 16 = 13,000)"} + } + ) + -- Bag of Crafting random recipe checks + local calcSteps = {200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200} + MCM.AddSetting( + "EID", + "Crafting", + { + Type = ModConfigMenu.OptionType.SCROLL, + CurrentSetting = function() + return AnIndexOf(calcSteps, EID.Config["BagOfCraftingRandomResults"]) - 1 + end, + Display = function() + return "Random Calculations: $scroll" .. + AnIndexOf(calcSteps, EID.Config["BagOfCraftingRandomResults"]) - 1 .. " " .. EID.Config["BagOfCraftingRandomResults"] + end, + OnChange = function(currentNum) + EID.Config["BagOfCraftingRandomResults"] = calcSteps[currentNum + 1] + end, + Info = {"An additional X number of randomly chosen recipes will be checked, changing each pickup spawn/despawn or refresh"} + } + ) + -- Bag of Crafting item names + MCM.AddSetting( + "EID", + "Crafting", + { + Type = ModConfigMenu.OptionType.BOOLEAN, + CurrentSetting = function() + return EID.Config["BagOfCraftingDisplayNames"] + end, + Display = function() + local onOff = "False" + if EID.Config["BagOfCraftingDisplayNames"] then + onOff = "True" + end + return "Show Item Names: " .. onOff + end, + OnChange = function(currentBool) + EID.Config["BagOfCraftingDisplayNames"] = currentBool + end, + Info = {"If on, each result takes two lines; lower your displayed results accordingly"} } )