From 9d8fe6a5aad83a26a811bdbc66994e73fe115d11 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Fri, 28 Jan 2022 16:45:05 -0500 Subject: [PATCH 01/10] just in case --- eid_modifiers.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eid_modifiers.lua b/eid_modifiers.lua index 52a0b1cf..b10c17e7 100644 --- a/eid_modifiers.lua +++ b/eid_modifiers.lua @@ -419,7 +419,7 @@ if REPENTANCE then if collectiblesOwned[711] and EID:getEntityData(descObj.Entity, "EID_FlipItemID") then table.insert(callbacks, FlipCallback) end if collectiblesOwned[723] or (EID.absorbedItems[723] and collectiblesOwned[477]) then table.insert(callbacks, SpindownDiceCallback) end -- currently, only Repentance collectible modifiers have Tab previews so put it here - if Input.IsActionPressed(ButtonAction.ACTION_MAP, EID.player.ControllerIndex) and not inPreview then table.insert(callbacks, TabCallback) end + if EID.player and Input.IsActionPressed(ButtonAction.ACTION_MAP, EID.player.ControllerIndex) and not inPreview then table.insert(callbacks, TabCallback) end -- Card / Rune Callbacks elseif descObj.ObjVariant == PickupVariant.PICKUP_TAROTCARD then From 4eb48c24d20b6cd690bc4891aeee7ec004fb8b06 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Fri, 28 Jan 2022 16:47:28 -0500 Subject: [PATCH 02/10] add Coroutine manager --- eid_api.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/eid_api.lua b/eid_api.lua index d0fbe402..e7691001 100644 --- a/eid_api.lua +++ b/eid_api.lua @@ -1228,3 +1228,20 @@ function EID:setModIndicatorIcon(iconMarkup, override) if EID.ModIndicator[EID._currentMod].Icon ~= nil and override == false then return end EID.ModIndicator[EID._currentMod].Icon = iconMarkup end + +EID.Coroutines = {} +-- Add a coroutine to be ran 60 times a second +function EID:addCoroutine(name, func, overwrite) + if overwrite or EID.Coroutines[name] == nil then EID.Coroutines[name] = coroutine.create(func) end +end + +function EID:removeCoroutine(name) + EID.Coroutines[name] = nil +end + +-- ran 60 times a second in main game render +function EID:resumeCoroutines() + for k,v in pairs(EID.Coroutines) do + if coroutine.resume(v) == false then EID:removeCoroutine(k) end + end +end From a24ac7877d2ee60474a617895106034b706cfd1a Mon Sep 17 00:00:00 2001 From: Buurazu Date: Fri, 28 Jan 2022 16:48:22 -0500 Subject: [PATCH 03/10] Pickup collision overhaul --- eid_bagofcrafting.lua | 90 +++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index 09d374ee..8e456399 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -191,16 +191,18 @@ local sortedIDs = {} local function sortAllItems() sortedIDs = {} + local objectNames = {} for i = 1, CraftingMaxItemID do if CraftingItemQualities[i] ~= nil then table.insert(sortedIDs, i) + objectNames[i] = EID:getObjectName(5, 100, i) 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)) + return (objectNames[a] < objectNames[b]) else return (CraftingItemQualities[a] > CraftingItemQualities[b]) end @@ -226,8 +228,9 @@ local function nextFloat() return RNGNext() * multi; end +-- Convert a pickup's ID into what ingredient it counts as function EID:getBagOfCraftingID(Variant, SubType) - local entry = pickupIDLookup[""..Variant.."."..SubType] + local entry = pickupIDLookup[Variant.."."..SubType] if entry ~= nil then return entry elseif Variant == 300 then @@ -242,6 +245,7 @@ function EID:getBagOfCraftingID(Variant, SubType) return nil end +-- NO RECIPES MODE: Get percentages of what quality / item pool a given set of 8 ingredients can yield function EID:simulateBagOfCrafting(componentsTable) local components = componentsTable local compTotalWeight = 0 @@ -533,64 +537,37 @@ EID:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, GameStartCrafting) ------------------------------------------ ------------------------------------------ - -local pickupsOnInit = {} +local pickupsCollected = {} -- table of collected pickup indexes, reset each room +local pickupsJustTouched = {} -- one-frame flags of pickups a player/pickup-collector has touched, so the bag doesn't think it collected it EID:AddCallback(ModCallbacks.MC_PRE_PICKUP_COLLISION, function(_, pickup,collider,_) - if collider.Type ~= EntityType.ENTITY_PLAYER then - return + if collider.Type == EntityType.ENTITY_PLAYER or collider.Type == EntityType.ENTITY_FAMILIAR or + collider.Type == EntityType.ENTITY_BUMBINO or collider.Type == EntityType.ENTITY_ULTRA_GREED then + pickupsJustTouched[pickup.Index] = true end - -- Remove pickups picked up normally - local pickupsOnInitCorrected = {} - for _,e in ipairs (pickupsOnInit) do - if GetPtrHash(pickup) ~= GetPtrHash(e) then - table.insert(pickupsOnInitCorrected, e) - end - end - pickupsOnInit = pickupsOnInitCorrected end) -EID:AddCallback(ModCallbacks.MC_POST_KNIFE_INIT, function(_, entity) - if entity.Variant ~= 4 then - return - end - pickupsOnInit = {} - for _,e in ipairs (Isaac.FindByType(EntityType.ENTITY_PICKUP, -1, -1, false, false)) do - if e:GetSprite():GetAnimation() ~= "Collect" then - table.insert(pickupsOnInit, e) - end - end -end, 4) - -EID:AddCallback(ModCallbacks.MC_POST_ENTITY_REMOVE, function(_, bag) - if bag.Variant ~= 4 or bag.SubType ~= 4 then - return - end - - table.sort(pickupsOnInit, function (a,b) - return - a:GetSprite():GetFrame() > b:GetSprite():GetFrame() or - (a:GetSprite():GetFrame() == b:GetSprite():GetFrame() and a.Index < b.Index) - end) - - for _,e in ipairs (pickupsOnInit) do - if e:GetSprite():GetAnimation() == "Collect" then - local craftingIDs = EID:getBagOfCraftingID(e.Variant, e.SubType) +EID:AddCallback(ModCallbacks.MC_POST_PICKUP_UPDATE, function(_, pickup) + if pickup:GetSprite():GetAnimation() == "Collect" and not pickupsCollected[pickup.Index] then + pickupsCollected[pickup.Index] = true + if not pickupsJustTouched[pickup.Index] then + print(pickup.Index .. "," .. pickup:GetSprite():GetFrame() .. "," .. Isaac.GetTime()) + local craftingIDs = EID:getBagOfCraftingID(pickup.Variant, pickup.SubType) if craftingIDs ~= nil then for _,v in ipairs(craftingIDs) do - if #EID.BagItems >= 8 then - local newContent = {} - for i=2,#EID.BagItems do - table.insert(newContent, EID.BagItems[i]) - end - EID.BagItems = newContent - end + if #EID.BagItems >= 8 then table.remove(EID.BagItems, 1) end table.insert(EID.BagItems, v) end end end end -end, EntityType.ENTITY_KNIFE) + pickupsJustTouched[pickup.Index] = nil +end) + +EID:AddCallback(ModCallbacks.MC_POST_NEW_ROOM, function(_) + -- We're using the pickup indexes for quick checking, which reset on each new room + pickupsCollected = {} +end) --Tainted Cain "hold to craft" check local holdCounter = 0 @@ -612,14 +589,11 @@ local function trackBagHolding() end end -local function trackBagActivated() - if IsTaintedCain() then return end - if Input.IsActionTriggered(ButtonAction.ACTION_ITEM, EID.player.ControllerIndex) and #EID.BagItems >= 8 then - EID.BagItems = {} - -- SCHOOLBAG BREAKS THIS!!! but it's better than nothing - -- and, technically, if the active item bag has 8 items, it's impossible to fill it without first emptying it, and then we're synced again - end -end +--Active slot "press to craft" check +EID:AddCallback(ModCallbacks.MC_PRE_USE_ITEM, function(_, _, _, _, _, slot) + if slot ~= 0 or #EID.BagItems < 8 then return end + EID.BagItems = {} +end, CollectibleType.COLLECTIBLE_BAG_OF_CRAFTING) local function shiftBagContent() local newContent = {} @@ -629,7 +603,6 @@ local function shiftBagContent() table.insert(newContent, EID.BagItems[1]) EID.BagItems = newContent end - -- only Tainted Cain's consumable slot bag can have its ingredients shifted local function detectBagContentShift() if Input.IsActionTriggered(ButtonAction.ACTION_DROP, EID.player.ControllerIndex) and IsTaintedCain() then @@ -791,7 +764,6 @@ function EID:handleBagOfCraftingRendering() lastSeedUsed = curSeed trackBagHolding() - trackBagActivated() detectBagContentShift() local tableToCraftingIconsFunc = EID.tableToCraftingIconsMerged @@ -847,6 +819,7 @@ function EID:handleBagOfCraftingRendering() --if we're in Preview Only mode, then we have nothing more to do if (EID.Config["BagOfCraftingDisplayMode"] == "Preview Only") then return false end + -- check what pickups are available in this room local curRoomIndex = game:GetLevel():GetCurrentRoomDesc().SafeGridIndex local results = {} @@ -872,6 +845,7 @@ function EID:handleBagOfCraftingRendering() local itemQuery = {} local itemCount = {} + -- Merge our list of the floor's pickups and our bag's pickups --max 8 copies of a single item in our list, to avoid repeat recipes for _, v in ipairs(EID.bagOfCraftingFloorQuery) do if (not itemCount[v] or itemCount[v] < 8) then From b665a77ba300cb23ab97baacd95b4661cfaff170 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Fri, 28 Jan 2022 16:53:47 -0500 Subject: [PATCH 04/10] fix no recipes mode --- eid_bagofcrafting.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index 8e456399..048035c3 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -269,10 +269,10 @@ function EID:simulateBagOfCrafting(componentsTable) {idx = 7, weight = compCounts[30] * 10, totalWeight = 0}, {idx = 8, weight = compCounts[6] * 10, totalWeight = 0}, {idx = 9, weight = compCounts[26] * 10, totalWeight = 0}, - {idx = 12, weight = compCounts[8] * 10}, totalWeight = 0, + {idx = 12, weight = compCounts[8] * 10, totalWeight = 0}, } if compCounts[9] + compCounts[2] + compCounts[13] + compCounts[16] == 0 then - table.insert(poolWeights, {idx = 26, weight = compCounts[24] * 10}) + table.insert(poolWeights, {idx = 26, weight = compCounts[24] * 10, totalWeight = 0}) end local totalWeight = 0 @@ -751,6 +751,8 @@ local function getFloorItemsString(showPreviews, roomItems) return floorString end +--local function + function EID:handleBagOfCraftingRendering() local curSeed = game:GetSeeds():GetStartSeed() --reset our calculated recipes when the game seed changes From fed980ded14869ea695551c623c73ceb1673237e Mon Sep 17 00:00:00 2001 From: Buurazu Date: Fri, 28 Jan 2022 20:57:10 -0500 Subject: [PATCH 05/10] Add held Card/Pill support, etc. comments --- eid_bagofcrafting.lua | 117 +++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 42 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index 048035c3..752ec419 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -83,8 +83,7 @@ local pickupIDLookup = { } local function IsTaintedCain() - --no clue how the display works for co-op, as far as I can tell we're only caring about player 1 - --this check is necessary for some ingredient tracking now that Bag of Crafting is usable by everyone + -- this check is necessary for tracking Bag usage since Tainted Cain's pocket bag works differently than everyone else's return EID.player:GetPlayerType() == 23 end @@ -171,6 +170,7 @@ local componentShifts = { {0x00000011, 0x0000000F, 0x0000001A} } +-- The icon each item pool will use in the No Recipes display local poolToIcon = { [0]="{{TreasureRoom}}",[1]="{{Shop}}",[2]="{{BossRoom}}",[3]="{{DevilRoom}}",[4]="{{AngelRoom}}", [5]="{{SecretRoom}}",[7]="{{PoopRoomIcon}}",[8]="{{GoldenChestRoomIcon}}",[9]="{{RedChestRoomIcon}}",[12]="{{CursedRoom}}",[26]="{{Planetarium}}" } @@ -210,6 +210,7 @@ local function sortAllItems() end -- delay the initial sort until needed, as it's a tad slow local sortNeeded = true +local recheckPickups = false local customRNGSeed = 0x77777770 local customRNGShift = {0,0,0} @@ -234,13 +235,19 @@ function EID:getBagOfCraftingID(Variant, SubType) if entry ~= nil then return entry elseif Variant == 300 then - if SubType > 80 or (SubType >= 32 and SubType <= 41) or SubType == 55 then -- runes + if SubType == 0 then -- player:GetCard() returned 0 + return nil + elseif SubType > 80 or (SubType >= 32 and SubType <= 41) or SubType == 55 then -- runes return {23} else -- cards return {21} end elseif Variant == 70 then -- pills - return {22} + if SubType == 0 then -- player:GetPill() returned 0 + return nil + else + return {22} + end end return nil end @@ -342,6 +349,7 @@ function EID:simulateBagOfCrafting(componentsTable) return compTotalWeight, poolString end +-- The main function that takes 8 ingredients and tells you what collectible you will get in return function EID:calculateBagOfCrafting(componentsTable) local components = {table.unpack(componentsTable)} if components == nil or #components ~= 8 then @@ -473,7 +481,7 @@ function EID:calculateBagOfCrafting(componentsTable) end end --- simple table of tables copy function +-- simple table of tables copy function, for modded recipe support (to make a local copy of the XML data tables so we can then add on to them) local function deepcopy(orig) local orig_type = type(orig) local copy @@ -547,6 +555,8 @@ EID:AddCallback(ModCallbacks.MC_PRE_PICKUP_COLLISION, function(_, pickup,collide end end) +-- Formerly a MC_POST_PICKUP_UPDATE, but moved to this so that it's only called when we own a bag +--move it EID:AddCallback(ModCallbacks.MC_POST_PICKUP_UPDATE, function(_, pickup) if pickup:GetSprite():GetAnimation() == "Collect" and not pickupsCollected[pickup.Index] then pickupsCollected[pickup.Index] = true @@ -554,6 +564,7 @@ EID:AddCallback(ModCallbacks.MC_POST_PICKUP_UPDATE, function(_, pickup) print(pickup.Index .. "," .. pickup:GetSprite():GetFrame() .. "," .. Isaac.GetTime()) local craftingIDs = EID:getBagOfCraftingID(pickup.Variant, pickup.SubType) if craftingIDs ~= nil then + recheckPickups = true for _,v in ipairs(craftingIDs) do if #EID.BagItems >= 8 then table.remove(EID.BagItems, 1) end table.insert(EID.BagItems, v) @@ -623,6 +634,7 @@ local numResults = 0 --these aren't local so that they can be saved and reloaded, or cleared in the Mod Config Menu EID.bagOfCraftingCurPickupCount = -1 EID.bagOfCraftingRoomQueries = {} +EID.bagOfCraftingInventoryQuery = {} EID.bagOfCraftingFloorQuery = {} EID.BagItems = {} @@ -638,6 +650,19 @@ local resetBagCounter = 0 local craftingIsHidden = false local showCraftingResult = false +local function calcHeldItems() + EID.bagOfCraftingInventoryQuery = {} + for i = 0, game:GetNumPlayers() - 1 do + local player = Isaac.GetPlayer(i) + for j = 0, 3 do + local card = EID:getBagOfCraftingID(300, player:GetCard(j)) + local pill = EID:getBagOfCraftingID(70, player:GetPill(j)) + -- assume the card/pill is only 1 ingredient + if card then table.insert(EID.bagOfCraftingInventoryQuery, card[1]) end + if pill then table.insert(EID.bagOfCraftingInventoryQuery, pill[1]) end + end + end +end local function calcFloorItems() EID.bagOfCraftingFloorQuery = {} for _,v in pairs(EID.bagOfCraftingRoomQueries) do @@ -746,16 +771,24 @@ local function getFloorItemsString(showPreviews, roomItems) floorString = floorString .. "{{Collectible"..recipe.."}} " end local floorDesc = EID:getDescriptionEntry("CraftingFloorContent") - floorString = floorString .. floorDesc..EID:tableToCraftingIconsMerged(EID.bagOfCraftingFloorQuery).."#" + floorString = floorString .. floorDesc..EID:tableToCraftingIconsMerged(EID.bagOfCraftingFloorQuery) + end + if #EID.bagOfCraftingInventoryQuery > 0 then + floorString = floorString .. "(+" .. EID:tableToCraftingIconsMerged(EID.bagOfCraftingInventoryQuery) .. ")#" + else + floorString = floorString .. "#" end return floorString end ---local function + + +local function RecipeCrunchCoroutine() +end function EID:handleBagOfCraftingRendering() + -- reset our calculated recipes when the game seed changes local curSeed = game:GetSeeds():GetStartSeed() - --reset our calculated recipes when the game seed changes if (curSeed ~= lastSeedUsed) then calculatedRecipes = {} lockedRecipes = {} @@ -765,13 +798,15 @@ function EID:handleBagOfCraftingRendering() end lastSeedUsed = curSeed + -- watch for holding the Craft button, and pressing the ingredient shift button trackBagHolding() detectBagContentShift() + -- load the function we need for Show Recipes as Groups / 8 Icons local tableToCraftingIconsFunc = EID.tableToCraftingIconsMerged if EID.Config["BagOfCraftingDisplayIcons"] then tableToCraftingIconsFunc = EID.tableToCraftingIconsFull end - --prevent our hotkeys from triggering as they're set + -- Check for Hide/Preview hotkeys; prevent them from triggering as they're set in MCM if not ModConfigMenu or not ModConfigMenu.IsVisible then if Input.IsButtonTriggered(EID.Config["CraftingHideKey"], 0) or Input.IsButtonTriggered(EID.Config["CraftingHideButton"], EID.player.ControllerIndex) then craftingIsHidden = not craftingIsHidden @@ -782,7 +817,7 @@ function EID:handleBagOfCraftingRendering() end end - --determine if we should display anything at all, display item preview if applicable, and figure out our room/floor's pickup contents + -- Determine if we should display anything at all if EID.isHidden or craftingIsHidden or game.Challenge == Challenge.CHALLENGE_CANTRIPPED then return elseif EID.Config["BagOfCraftingHideInBattle"] then @@ -790,7 +825,6 @@ function EID:handleBagOfCraftingRendering() return end end - if EID.Config["DisplayBagOfCrafting"] == "never" then return false end @@ -801,12 +835,12 @@ function EID:handleBagOfCraftingRendering() return false end - --Display the result of the 8 items in our bag + -- Display the result of the 8 items in our bag if applicable if (showCraftingResult or EID.Config["BagOfCraftingDisplayMode"] == "Preview Only") and #EID.BagItems >= 8 then local craftingResult, backupResult = EID:calculateBagOfCrafting(EID.BagItems) if (backupResult ~= craftingResult) then EID.TabPreviewID = backupResult end local descriptionObj = EID:getDescriptionObj(5, 100, craftingResult) - --prepend the Hide/Preview hotkeys to the description + -- prepend the Hide/Preview hotkeys to the description descriptionObj.Description = getHotkeyString() .. descriptionObj.Description if (backupResult ~= craftingResult and descriptionObj.ObjSubType == craftingResult) then local backupDesc = EID:getDescriptionEntry("CraftingPreviewBackup") @@ -818,20 +852,19 @@ function EID:handleBagOfCraftingRendering() EID:printDescription(descriptionObj) return true end - --if we're in Preview Only mode, then we have nothing more to do + -- if we're in Preview Only mode, then we have nothing more to do if (EID.Config["BagOfCraftingDisplayMode"] == "Preview Only") then return false end - -- check what pickups are available in this room + -- Check what pickups are available in this room local curRoomIndex = game:GetLevel():GetCurrentRoomDesc().SafeGridIndex - - local results = {} local roomItems = {} local pickups = Isaac.FindByType(5, -1, -1, true, false) - if EID.bagOfCraftingCurPickupCount ~= #pickups then + if EID.bagOfCraftingCurPickupCount ~= #pickups or recheckPickups then + recheckPickups = false for _, entity in ipairs(pickups) do local craftingIDs = EID:getBagOfCraftingID(entity.Variant, entity.SubType) - if craftingIDs ~= nil and not entity:ToPickup():IsShopItem() then + if craftingIDs ~= nil and not entity:ToPickup():IsShopItem() and entity:GetSprite():GetAnimation() ~= "Collect" then for _,v in ipairs(craftingIDs) do table.insert(roomItems, v) end @@ -839,6 +872,7 @@ function EID:handleBagOfCraftingRendering() end EID.bagOfCraftingRoomQueries[curRoomIndex..""] = roomItems EID.bagOfCraftingCurPickupCount = #pickups + calcHeldItems() calcFloorItems() else roomItems = EID.bagOfCraftingRoomQueries[curRoomIndex..""] or {} @@ -847,36 +881,32 @@ function EID:handleBagOfCraftingRendering() local itemQuery = {} local itemCount = {} - -- Merge our list of the floor's pickups and our bag's pickups - --max 8 copies of a single item in our list, to avoid repeat recipes - for _, v in ipairs(EID.bagOfCraftingFloorQuery) do - 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 + -- Merge our list of the floor's pickups, held cards/pills, and our bag's pickups + -- max 8 copies of a single item in our list, to avoid repeat recipes + local tablesToMerge = { EID.bagOfCraftingFloorQuery, EID.bagOfCraftingInventoryQuery, EID.BagItems } + for _, tbl in ipairs(tablesToMerge) do + for _, v in ipairs(tbl) do + 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 end - for _, v in ipairs(EID.BagItems) do - 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 + -- Nothing to do if there's less than 8 pickups detected if #itemQuery < 8 then return false end - - --sort by ingredient quality + -- sort by ingredient quality, as high quality recipes are more important to check table.sort(itemQuery, qualitySort) + ---------------------------------------------- + local customDescObj = EID:getDescriptionObj(5, 100, 710) customDescObj.Description = "" - --No Recipes Mode display + -- Pickups Only / No Recipes Mode display if EID.Config["BagOfCraftingDisplayMode"] == "Pickups Only" then EID:appendToDescription(customDescObj, getHotkeyString()) EID:appendToDescription(customDescObj, getFloorItemsString(false, roomItems)) @@ -903,11 +933,14 @@ function EID:handleBagOfCraftingRendering() return true end + -- Recipe List display if sortNeeded then sortAllItems() sortNeeded = false end - + + -- keep the results between frames, it shouldn't be local here + local results = {} local queryString = table.concat(itemQuery,",") if lockedResults ~= nil then results = calcResultCache[lockedResults] @@ -916,9 +949,6 @@ function EID:handleBagOfCraftingRendering() local randResults = randResultCache[queryString] or {} local newResults = {} 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 = {} --shift our thorough check forward one ingredient each refresh (it will find duplicates, but spamming refresh will get a lot of variety) @@ -940,6 +970,9 @@ function EID:handleBagOfCraftingRendering() end end + --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 if (#mostValuable >= 8) then combinations(mostValuable, nil, nil, nil, randResults, newResults) end --do random pulls for some more recipe choices From e6f0f7bb03e3076ab916919383cf5ee88ae4831d Mon Sep 17 00:00:00 2001 From: Buurazu Date: Fri, 28 Jan 2022 21:28:37 -0500 Subject: [PATCH 06/10] about to switch to coroutine --- eid_bagofcrafting.lua | 60 +++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index 752ec419..da0d1d4c 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -546,7 +546,7 @@ EID:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, GameStartCrafting) ------------------------------------------ local pickupsCollected = {} -- table of collected pickup indexes, reset each room -local pickupsJustTouched = {} -- one-frame flags of pickups a player/pickup-collector has touched, so the bag doesn't think it collected it +local pickupsJustTouched = {} -- flags of pickups a player/pickup-collector has touched, so the bag doesn't think it collected it EID:AddCallback(ModCallbacks.MC_PRE_PICKUP_COLLISION, function(_, pickup,collider,_) if collider.Type == EntityType.ENTITY_PLAYER or collider.Type == EntityType.ENTITY_FAMILIAR or @@ -556,24 +556,25 @@ EID:AddCallback(ModCallbacks.MC_PRE_PICKUP_COLLISION, function(_, pickup,collide end) -- Formerly a MC_POST_PICKUP_UPDATE, but moved to this so that it's only called when we own a bag ---move it -EID:AddCallback(ModCallbacks.MC_POST_PICKUP_UPDATE, function(_, pickup) - if pickup:GetSprite():GetAnimation() == "Collect" and not pickupsCollected[pickup.Index] then - pickupsCollected[pickup.Index] = true - if not pickupsJustTouched[pickup.Index] then - print(pickup.Index .. "," .. pickup:GetSprite():GetFrame() .. "," .. Isaac.GetTime()) - local craftingIDs = EID:getBagOfCraftingID(pickup.Variant, pickup.SubType) - if craftingIDs ~= nil then - recheckPickups = true - for _,v in ipairs(craftingIDs) do - if #EID.BagItems >= 8 then table.remove(EID.BagItems, 1) end - table.insert(EID.BagItems, v) +local function checkForPickups() + for _,pickup in ipairs(Isaac.FindByType(EntityType.ENTITY_PICKUP, -1, -1, false, false)) do + if pickup:GetSprite():GetAnimation() == "Collect" and not pickupsCollected[pickup.Index] then + pickupsCollected[pickup.Index] = true + if not pickupsJustTouched[pickup.Index] then + print(pickup.Index .. "," .. pickup:GetSprite():GetFrame() .. "," .. Isaac.GetTime()) + local craftingIDs = EID:getBagOfCraftingID(pickup.Variant, pickup.SubType) + if craftingIDs ~= nil then + recheckPickups = true + for _,v in ipairs(craftingIDs) do + if #EID.BagItems >= 8 then table.remove(EID.BagItems, 1) end + table.insert(EID.BagItems, v) + end end end end + pickupsJustTouched[pickup.Index] = nil end - pickupsJustTouched[pickup.Index] = nil -end) +end EID:AddCallback(ModCallbacks.MC_POST_NEW_ROOM, function(_) -- We're using the pickup indexes for quick checking, which reset on each new room @@ -781,9 +782,18 @@ local function getFloorItemsString(showPreviews, roomItems) return floorString end - +-- This list will be modified once the coroutine finishes; until then it will have the last finished list +local currentRecipesList = {} local function RecipeCrunchCoroutine() + local timer = Isaac.GetTime() + + + + if Isaac.GetTime() > timer + 1 then + coroutine.yield() + timer = Isaac.GetTime() + end end function EID:handleBagOfCraftingRendering() @@ -801,12 +811,13 @@ function EID:handleBagOfCraftingRendering() -- watch for holding the Craft button, and pressing the ingredient shift button trackBagHolding() detectBagContentShift() + if EID.GameRenderCount % 2 == 0 then checkForPickups() end -- load the function we need for Show Recipes as Groups / 8 Icons local tableToCraftingIconsFunc = EID.tableToCraftingIconsMerged if EID.Config["BagOfCraftingDisplayIcons"] then tableToCraftingIconsFunc = EID.tableToCraftingIconsFull end - -- Check for Hide/Preview hotkeys; prevent them from triggering as they're set in MCM + -- Check for Hide/Preview hotkeys; prevent them from triggering while in MCM if not ModConfigMenu or not ModConfigMenu.IsVisible then if Input.IsButtonTriggered(EID.Config["CraftingHideKey"], 0) or Input.IsButtonTriggered(EID.Config["CraftingHideButton"], EID.player.ControllerIndex) then craftingIsHidden = not craftingIsHidden @@ -940,10 +951,9 @@ function EID:handleBagOfCraftingRendering() end -- keep the results between frames, it shouldn't be local here - local results = {} local queryString = table.concat(itemQuery,",") if lockedResults ~= nil then - results = calcResultCache[lockedResults] + currentRecipesList = calcResultCache[lockedResults] elseif calcResultCache[queryString] == nil or refreshNextTick then --build on top of our previous recipe lists, if possible local randResults = randResultCache[queryString] or {} @@ -1013,7 +1023,7 @@ function EID:handleBagOfCraftingRendering() end calcResultCache[queryString] = sortedResults randResultCache[queryString] = randResults - results = sortedResults + currentRecipesList = sortedResults numResults = 0 for _,v in ipairs(sortedIDs) do @@ -1021,7 +1031,7 @@ function EID:handleBagOfCraftingRendering() --jump to the item we were looking at before, so you can more easily refresh for variants of recipes bagOfCraftingOffset = numResults end - numResults = numResults + #results[v] + numResults = numResults + #currentRecipesList[v] end if not refreshNextTick then @@ -1030,7 +1040,7 @@ function EID:handleBagOfCraftingRendering() end refreshNextTick = false else - results = calcResultCache[queryString] + currentRecipesList = calcResultCache[queryString] end if numResults == 0 then @@ -1091,17 +1101,17 @@ function EID:handleBagOfCraftingRendering() prefix = "#{{Trinket159}} " end - --results is now a table of tables for each item, so we have to iterate over the table using sortedIDs + --currentRecipesList is now a table of tables for each item, so we have to iterate over the table using sortedIDs if (bagOfCraftingOffset > 0) then customDescObj.Description = customDescObj.Description.. prefix .. "...+"..bagOfCraftingOffset.." more" end local curOffset = 0 refreshPosition = -1 for _,id in ipairs(sortedIDs) do - if (curOffset + #results[id] <= bagOfCraftingOffset) then curOffset = curOffset + #results[id] + if (curOffset + #currentRecipesList[id] <= bagOfCraftingOffset) then curOffset = curOffset + #currentRecipesList[id] else if (refreshPosition == -1) then refreshPosition = id end - for _, v in ipairs(results[id]) do + for _, v in ipairs(currentRecipesList[id]) do curOffset = curOffset + 1 if (curOffset > bagOfCraftingOffset+EID.Config["BagOfCraftingResults"]) then break end if not v then break end From b7efb9565f2e5482ebcd619ebd9667a254c9aee6 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Fri, 28 Jan 2022 23:40:06 -0500 Subject: [PATCH 07/10] Coroutine resuming in render --- main.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/main.lua b/main.lua index e8c252d5..fdbcb4d8 100644 --- a/main.lua +++ b/main.lua @@ -944,6 +944,7 @@ EID.lastDist = 0 local function onRender(t) -- Increases by 60 per second, ignores pauses EID.GameRenderCount = EID.GameRenderCount + 1 + EID:resumeCoroutines() EID.isDisplaying = false EID:setPlayer() From f19c630b8a41cfec6714a868ea16f8e48f9e8695 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Fri, 28 Jan 2022 23:44:28 -0500 Subject: [PATCH 08/10] Coroutine 1.0 --- eid_bagofcrafting.lua | 260 +++++++++++++++++++++++------------------- 1 file changed, 143 insertions(+), 117 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index da0d1d4c..c3a65aee 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -481,6 +481,35 @@ function EID:calculateBagOfCrafting(componentsTable) end end +local function calcHeldItems() + EID.bagOfCraftingInventoryQuery = {} + for i = 0, game:GetNumPlayers() - 1 do + local player = Isaac.GetPlayer(i) + for j = 0, 3 do + local card = EID:getBagOfCraftingID(300, player:GetCard(j)) + local pill = EID:getBagOfCraftingID(70, player:GetPill(j)) + -- assume the card/pill is only 1 ingredient + if card then table.insert(EID.bagOfCraftingInventoryQuery, card[1]) end + if pill then table.insert(EID.bagOfCraftingInventoryQuery, pill[1]) end + end + end +end +local function calcFloorItems() + EID.bagOfCraftingFloorQuery = {} + for _,v in pairs(EID.bagOfCraftingRoomQueries) do + for _,v1 in ipairs(v) do + table.insert(EID.bagOfCraftingFloorQuery, v1) + end + end +end +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 + -- simple table of tables copy function, for modded recipe support (to make a local copy of the XML data tables so we can then add on to them) local function deepcopy(orig) local orig_type = type(orig) @@ -561,7 +590,6 @@ local function checkForPickups() if pickup:GetSprite():GetAnimation() == "Collect" and not pickupsCollected[pickup.Index] then pickupsCollected[pickup.Index] = true if not pickupsJustTouched[pickup.Index] then - print(pickup.Index .. "," .. pickup:GetSprite():GetFrame() .. "," .. Isaac.GetTime()) local craftingIDs = EID:getBagOfCraftingID(pickup.Variant, pickup.SubType) if craftingIDs ~= nil then recheckPickups = true @@ -581,6 +609,15 @@ EID:AddCallback(ModCallbacks.MC_POST_NEW_ROOM, function(_) pickupsCollected = {} end) +-- Using a Card/Pill will change our inventory craftable items, so force a refresh then +-- (Note: Items that directly add a card/pill to you, i.e. Bottle of Pills, also need a refresh, but aren't tracked for performance) +EID:AddCallback(ModCallbacks.MC_USE_CARD, function(_) + recheckPickups = true +end) +EID:AddCallback(ModCallbacks.MC_USE_PILL, function(_) + recheckPickups = true +end) + --Tainted Cain "hold to craft" check local holdCounter = 0 local function trackBagHolding() @@ -651,55 +688,32 @@ local resetBagCounter = 0 local craftingIsHidden = false local showCraftingResult = false -local function calcHeldItems() - EID.bagOfCraftingInventoryQuery = {} - for i = 0, game:GetNumPlayers() - 1 do - local player = Isaac.GetPlayer(i) - for j = 0, 3 do - local card = EID:getBagOfCraftingID(300, player:GetCard(j)) - local pill = EID:getBagOfCraftingID(70, player:GetPill(j)) - -- assume the card/pill is only 1 ingredient - if card then table.insert(EID.bagOfCraftingInventoryQuery, card[1]) end - if pill then table.insert(EID.bagOfCraftingInventoryQuery, pill[1]) end - end - end -end -local function calcFloorItems() - EID.bagOfCraftingFloorQuery = {} - for _,v in pairs(EID.bagOfCraftingRoomQueries) do - for _,v1 in ipairs(v) do - table.insert(EID.bagOfCraftingFloorQuery, v1) - end - end -end - -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 - --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 coTimer = 0 +-- number of milliseconds we allow these to run without yielding (1/60th of a second = 16.66667 milliseconds) +local coTimerLength = 5 local function combinations(arr, length, startPos, tempResult, randResults, newResults) - local length = length or 8 - local startPos = startPos or 1 - local tempResult = tempResult or {} - if (length == 0) then - local resultString = table.concat(tempResult,",") - if (randResults[resultString] == nil) then - randResults[resultString] = {table.unpack(tempResult)} - newResults[resultString] = {table.unpack(tempResult)} + if Isaac.GetTime() > coTimer + coTimerLength then + coroutine.yield() + coTimer = Isaac.GetTime() + end + local length = length or 8 + local startPos = startPos or 1 + local tempResult = tempResult or {} + if (length == 0) then + local resultString = table.concat(tempResult,",") + if (randResults[resultString] == nil) then + randResults[resultString] = {table.unpack(tempResult)} + newResults[resultString] = {table.unpack(tempResult)} + end + return + end + for i = startPos, #arr-length+1 do + tempResult[8-length+1] = arr[i] + combinations(arr,length-1, i+1, tempResult, randResults, newResults) end - return - end - for i = startPos, #arr-length+1 do - tempResult[8-length+1] = arr[i] - combinations(arr,length-1, i+1, tempResult, randResults, newResults) - end end --code from InputHelper in MCM local HotkeyToString = {} @@ -785,15 +799,84 @@ end -- This list will be modified once the coroutine finishes; until then it will have the last finished list local currentRecipesList = {} +local itemQuery = {} +local mostValuable = {} +local randResults = {} +local newResults = {} +local skipRandom = false +local isRefresh = false +local queryString = "" + local function RecipeCrunchCoroutine() - local timer = Isaac.GetTime() + coTimer = Isaac.GetTime() + -- Fill randResults/newResults with every possible combination of our most valuable ingredients + -- The number is has an option to limit it in the config, since the number of total combinations quickly grows (nCr): + -- 12 = 495, 13 = 1287, 14 = 3003, 15 = 6435, 16 = 12870 + combinations(mostValuable, nil, nil, nil, randResults, newResults) - - if Isaac.GetTime() > timer + 1 then - coroutine.yield() - timer = Isaac.GetTime() + --do random pulls for some more recipe choices + if (not skipRandom) then + for i = 0, EID.Config["BagOfCraftingRandomResults"] do + if Isaac.GetTime() > coTimer + coTimerLength then + coroutine.yield() + coTimer = Isaac.GetTime() + end + 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) + local resultString = table.concat(newTable,",") + if (randResults[resultString] == nil) then + randResults[resultString] = {table.unpack(newTable)} + newResults[resultString] = {table.unpack(newTable)} + end + end end + + local sortedResults = {} + if (calcResultCache[queryString]) then + sortedResults = calcResultCache[queryString] + else + for _, v in ipairs(sortedIDs) do + sortedResults[v] = {} + end + end + + for _, v in pairs(newResults) do + if Isaac.GetTime() > coTimer + coTimerLength then + coroutine.yield() + coTimer = Isaac.GetTime() + end + local resultID, lockedAchievementID = EID:calculateBagOfCrafting(v) + if resultID ~= lockedAchievementID then + table.insert(sortedResults[resultID], {v, resultID, lockedAchievementID}) + else + table.insert(sortedResults[resultID], {v, resultID}) + end + end + calcResultCache[queryString] = sortedResults + randResultCache[queryString] = randResults + currentRecipesList = sortedResults + + numResults = 0 + for _,v in ipairs(sortedIDs) do + if (isRefresh and bagOfCraftingOffset > 0 and v == refreshPosition) then + --jump to the item we were looking at before, so you can more easily refresh for variants of recipes + bagOfCraftingOffset = numResults + end + numResults = numResults + #currentRecipesList[v] + end + + if not isRefresh then + bagOfCraftingOffset = 0 + bagOfCraftingRefreshes = 0 + end + isRefresh = false end function EID:handleBagOfCraftingRendering() @@ -889,7 +972,7 @@ function EID:handleBagOfCraftingRendering() roomItems = EID.bagOfCraftingRoomQueries[curRoomIndex..""] or {} end - local itemQuery = {} + itemQuery = {} local itemCount = {} -- Merge our list of the floor's pickups, held cards/pills, and our bag's pickups @@ -950,16 +1033,16 @@ function EID:handleBagOfCraftingRendering() sortNeeded = false end - -- keep the results between frames, it shouldn't be local here - local queryString = table.concat(itemQuery,",") + queryString = table.concat(itemQuery,",") if lockedResults ~= nil then currentRecipesList = calcResultCache[lockedResults] - elseif calcResultCache[queryString] == nil or refreshNextTick then + elseif (calcResultCache[queryString] == nil or refreshNextTick) and EID.Coroutines["RecipeCrunch"] == nil then + isRefresh = refreshNextTick --build on top of our previous recipe lists, if possible - local randResults = randResultCache[queryString] or {} - local newResults = {} - local skipRandom = false - local mostValuable = {} + randResults = randResultCache[queryString] or {} + newResults = {} + skipRandom = false + mostValuable = {} --shift our thorough check forward one ingredient each refresh (it will find duplicates, but spamming refresh will get a lot of variety) if (refreshNextTick) then @@ -980,67 +1063,10 @@ function EID:handleBagOfCraftingRendering() end end - --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 - if (#mostValuable >= 8) then combinations(mostValuable, nil, nil, nil, randResults, newResults) end - - --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) - local resultString = table.concat(newTable,",") - if (randResults[resultString] == nil) then - randResults[resultString] = {table.unpack(newTable)} - newResults[resultString] = {table.unpack(newTable)} - end - end - end - - local sortedResults = {} - if (calcResultCache[queryString]) then - sortedResults = calcResultCache[queryString] - else - for _, v in ipairs(sortedIDs) do - sortedResults[v] = {} - end - end - - for _, v in pairs(newResults) do - local resultID, lockedAchievementID = EID:calculateBagOfCrafting(v) - if resultID ~= lockedAchievementID then - table.insert(sortedResults[resultID], {v, resultID, lockedAchievementID}) - else - table.insert(sortedResults[resultID], {v, resultID}) - end - end - calcResultCache[queryString] = sortedResults - randResultCache[queryString] = randResults - currentRecipesList = sortedResults - - numResults = 0 - for _,v in ipairs(sortedIDs) do - if (refreshNextTick and bagOfCraftingOffset > 0 and v == refreshPosition) then - --jump to the item we were looking at before, so you can more easily refresh for variants of recipes - bagOfCraftingOffset = numResults - end - numResults = numResults + #currentRecipesList[v] - end - - if not refreshNextTick then - bagOfCraftingOffset = 0 - bagOfCraftingRefreshes = 0 - end + EID:addCoroutine("RecipeCrunch", RecipeCrunchCoroutine) refreshNextTick = false else - currentRecipesList = calcResultCache[queryString] + currentRecipesList = calcResultCache[queryString] or currentRecipesList end if numResults == 0 then From 711f348706ba26e1d3ae6a7cf3387ef615ee2675 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sat, 29 Jan 2022 04:48:56 -0500 Subject: [PATCH 09/10] Make our most recent desc text cached --- eid_bagofcrafting.lua | 67 +++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index c3a65aee..ce6ba741 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -688,6 +688,10 @@ local resetBagCounter = 0 local craftingIsHidden = false local showCraftingResult = false +local prevSimplifiedDesc = "" +local prevListDesc = "" +local refreshTextbox = false + --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 @@ -877,6 +881,7 @@ local function RecipeCrunchCoroutine() bagOfCraftingRefreshes = 0 end isRefresh = false + refreshTextbox = true end function EID:handleBagOfCraftingRendering() @@ -968,6 +973,7 @@ function EID:handleBagOfCraftingRendering() EID.bagOfCraftingCurPickupCount = #pickups calcHeldItems() calcFloorItems() + refreshTextbox = true else roomItems = EID.bagOfCraftingRoomQueries[curRoomIndex..""] or {} end @@ -994,6 +1000,7 @@ function EID:handleBagOfCraftingRendering() end -- sort by ingredient quality, as high quality recipes are more important to check table.sort(itemQuery, qualitySort) + queryString = table.concat(itemQuery,",") ---------------------------------------------- @@ -1010,19 +1017,30 @@ function EID:handleBagOfCraftingRendering() EID:appendToDescription(customDescObj, getHotkeyString()) EID:appendToDescription(customDescObj, getFloorItemsString(false, roomItems)) + prevListDesc = "" + if not refreshTextbox and prevSimplifiedDesc ~= "" then + EID:appendToDescription(customDescObj, prevSimplifiedDesc) + EID:printDescription(customDescObj) + return true + end + + -- The floor item text can change without our total item query string changing, so only cache what comes after that + prevSimplifiedDesc = "" + refreshTextbox = false + local mostValuableBag = {} for i=1,8 do mostValuableBag[i] = itemQuery[i] end - local bagQuality, bagResult = EID:simulateBagOfCrafting(EID.BagItems) local bestQuality, bestResult = EID:simulateBagOfCrafting(mostValuableBag) local bagQualityDesc = EID:getDescriptionEntry("CraftingBagQuality") local bestQualityDesc = EID:getDescriptionEntry("CraftingBestQuality") - if (#EID.BagItems > 0) then EID:appendToDescription(customDescObj, bagQualityDesc .. " " .. bagQuality .. "#" .. bagResult .. "#") end - if (bestQuality > bagQuality) then EID:appendToDescription(customDescObj, bestQualityDesc .. " " .. bestQuality .. "#{{Blank}} " .. tableToCraftingIconsFunc(self,mostValuableBag, true) .. "#" .. bestResult .. "#") end + if (#EID.BagItems > 0) then prevSimplifiedDesc = prevSimplifiedDesc .. bagQualityDesc .. " " .. bagQuality .. "#" .. bagResult .. "#" end + if (bestQuality > bagQuality) then prevSimplifiedDesc = prevSimplifiedDesc .. bestQualityDesc .. " " .. bestQuality .. "#{{Blank}} " .. tableToCraftingIconsFunc(self,mostValuableBag, true) .. "#" .. bestResult .. "#" end + EID:appendToDescription(customDescObj, prevSimplifiedDesc) EID:printDescription(customDescObj) return true end @@ -1033,7 +1051,6 @@ function EID:handleBagOfCraftingRendering() sortNeeded = false end - queryString = table.concat(itemQuery,",") if lockedResults ~= nil then currentRecipesList = calcResultCache[lockedResults] elseif (calcResultCache[queryString] == nil or refreshNextTick) and EID.Coroutines["RecipeCrunch"] == nil then @@ -1074,11 +1091,7 @@ function EID:handleBagOfCraftingRendering() return false end - EID:appendToDescription(customDescObj, getHotkeyString()) - EID:appendToDescription(customDescObj, getFloorItemsString(true, roomItems)) - - local resultDesc = EID:getDescriptionEntry("CraftingResults") - EID:appendToDescription(customDescObj, resultDesc) + local prevOffset = bagOfCraftingOffset if Input.IsActionPressed(EID.Config["BagOfCraftingToggleKey"], EID.player.ControllerIndex) then EID.player.ControlsCooldown = 2 if Input.IsActionTriggered(ButtonAction.ACTION_SHOOTDOWN, EID.player.ControllerIndex) then @@ -1119,6 +1132,23 @@ function EID:handleBagOfCraftingRendering() --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 (bagOfCraftingOffset >= numResults) then bagOfCraftingOffset = bagOfCraftingOffset - EID.Config["BagOfCraftingResults"] end + EID:appendToDescription(customDescObj, getHotkeyString()) + EID:appendToDescription(customDescObj, getFloorItemsString(true, roomItems)) + + prevSimplifiedDesc = "" + if not refreshTextbox and prevListDesc ~= "" and bagOfCraftingOffset == prevOffset then + EID:appendToDescription(customDescObj, prevListDesc) + EID:printDescription(customDescObj) + return true + end + + -- The floor item text can change without our total item query string changing, so only cache what comes after that + prevListDesc = "" + refreshTextbox = false + + local resultDesc = EID:getDescriptionEntry("CraftingResults") + prevListDesc = prevListDesc .. resultDesc + local prevItem = 0 local qualities = { [0] = "{{ColorSilver}}", "{{ColorLime}}", "{{ColorPastelBlue}}", "{{ColorLavender}}", "{{ColorLightOrange}}" } @@ -1129,7 +1159,7 @@ function EID:handleBagOfCraftingRendering() --currentRecipesList is now a table of tables for each item, so we have to iterate over the table using sortedIDs if (bagOfCraftingOffset > 0) then - customDescObj.Description = customDescObj.Description.. prefix .. "...+"..bagOfCraftingOffset.." more" + prevListDesc = prevListDesc .. prefix .. "...+"..bagOfCraftingOffset.." more" end local curOffset = 0 refreshPosition = -1 @@ -1143,34 +1173,35 @@ function EID:handleBagOfCraftingRendering() if not v then break end if (curOffset > bagOfCraftingOffset) then if not EID.Config["BagOfCraftingDisplayNames"] then - customDescObj.Description = customDescObj.Description.."#{{Collectible"..v[2].."}} " + prevListDesc = prevListDesc .."#{{Collectible"..v[2].."}} " --tack on the secondary recipe image to achievement-locked recipes - if v[3] then customDescObj.Description = customDescObj.Description.."({{Collectible" .. v[3] .. "}})" end + if v[3] then prevListDesc = prevListDesc .."({{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[CraftingItemQualities[v[2]]] .. "={{CR}}" + prevListDesc = prevListDesc .. 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[CraftingItemQualities[v[2]]] .. + prevListDesc = prevListDesc .."#{{Collectible"..v[2].."}} ".. qualities[CraftingItemQualities[v[2]]] .. string.sub(EID:getObjectName(5, 100, v[2]),1,18).."#" else - customDescObj.Description = customDescObj.Description.."#" + prevListDesc = prevListDesc .."#" end --replace recipe bulletpoint with the secondary recipe on achievement-locked recipes - if v[3] then customDescObj.Description = customDescObj.Description.."{{Collectible" .. v[3] .. "}} " end + if v[3] then prevListDesc = prevListDesc .."{{Collectible" .. v[3] .. "}} " end end - customDescObj.Description = customDescObj.Description..tableToCraftingIconsFunc(self, v[1], true) + prevListDesc = prevListDesc .. tableToCraftingIconsFunc(self, v[1], true) prevItem = v[2] end end end end if (bagOfCraftingOffset + EID.Config["BagOfCraftingResults"] < numResults) then - customDescObj.Description = customDescObj.Description.. prefix .. "...+"..(numResults-EID.Config["BagOfCraftingResults"]-bagOfCraftingOffset).." more" + prevListDesc = prevListDesc .. prefix .. "...+"..(numResults-EID.Config["BagOfCraftingResults"]-bagOfCraftingOffset).." more" end + EID:appendToDescription(customDescObj, prevListDesc) EID:printDescription(customDescObj) return true end From b664bda3ca1b1f3e1e9eea5fcec60b95ffcb2298 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sat, 29 Jan 2022 05:00:13 -0500 Subject: [PATCH 10/10] Cache even more, refresh after hotkeys --- eid_bagofcrafting.lua | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index ce6ba741..3b264229 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -1014,20 +1014,19 @@ function EID:handleBagOfCraftingRendering() EID:printDescription(customDescObj) return true elseif EID.Config["BagOfCraftingDisplayMode"] == "No Recipes" then - EID:appendToDescription(customDescObj, getHotkeyString()) - EID:appendToDescription(customDescObj, getFloorItemsString(false, roomItems)) - prevListDesc = "" if not refreshTextbox and prevSimplifiedDesc ~= "" then EID:appendToDescription(customDescObj, prevSimplifiedDesc) EID:printDescription(customDescObj) return true end - - -- The floor item text can change without our total item query string changing, so only cache what comes after that + prevSimplifiedDesc = "" refreshTextbox = false + prevSimplifiedDesc = prevSimplifiedDesc .. getHotkeyString() + prevSimplifiedDesc = prevSimplifiedDesc .. getFloorItemsString(false, roomItems) + local mostValuableBag = {} for i=1,8 do mostValuableBag[i] = itemQuery[i] @@ -1102,6 +1101,7 @@ function EID:handleBagOfCraftingRendering() 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 + refreshTextbox = true if (lockedResults == nil) then lockedResults = queryString else lockedResults = nil end --refresh the recipes @@ -1123,6 +1123,7 @@ function EID:handleBagOfCraftingRendering() resetBagCounter = resetBagCounter + 1 if resetBagCounter > 120 then EID.BagItems = {} + recheckPickups = true resetBagCounter = 0 end else @@ -1132,9 +1133,6 @@ function EID:handleBagOfCraftingRendering() --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 (bagOfCraftingOffset >= numResults) then bagOfCraftingOffset = bagOfCraftingOffset - EID.Config["BagOfCraftingResults"] end - EID:appendToDescription(customDescObj, getHotkeyString()) - EID:appendToDescription(customDescObj, getFloorItemsString(true, roomItems)) - prevSimplifiedDesc = "" if not refreshTextbox and prevListDesc ~= "" and bagOfCraftingOffset == prevOffset then EID:appendToDescription(customDescObj, prevListDesc) @@ -1142,10 +1140,11 @@ function EID:handleBagOfCraftingRendering() return true end - -- The floor item text can change without our total item query string changing, so only cache what comes after that prevListDesc = "" refreshTextbox = false + prevListDesc = prevListDesc .. getHotkeyString() + prevListDesc = prevListDesc .. getFloorItemsString(true, roomItems) local resultDesc = EID:getDescriptionEntry("CraftingResults") prevListDesc = prevListDesc .. resultDesc