From eb257008dc3092a82a4532076a54362a0b1ba71d Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 00:15:59 -0500 Subject: [PATCH 01/23] Co-op overhaul --- eid_bagofcrafting.lua | 50 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index 94499797..4c8792ec 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -84,7 +84,7 @@ local pickupIDLookup = { local function IsTaintedCain() -- 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 + return EID.bagPlayer:GetPlayerType() == 23 end local componentShifts = { @@ -597,15 +597,15 @@ end) local holdCounter = 0 local function trackBagHolding() if not IsTaintedCain() then return end - local isCardHold = Input.IsActionPressed(ButtonAction.ACTION_PILLCARD, EID.player.ControllerIndex) - local animationName = EID.player:GetSprite():GetAnimation() + local isCardHold = Input.IsActionPressed(ButtonAction.ACTION_PILLCARD, EID.bagPlayer.ControllerIndex) + local animationName = EID.bagPlayer:GetSprite():GetAnimation() if isCardHold and string.match(animationName, "PickupWalk") and #EID.BagItems>=8 then holdCounter = holdCounter + 1 if holdCounter < 30 then - icount = EID.player:GetCollectibleCount() + icount = EID.bagPlayer:GetCollectibleCount() end else - if isCardHold and holdCounter >= 30 and (string.match(animationName, "Walk") and not string.match(animationName, "Pickup") or (EID.player:GetCollectibleCount() ~= icount)) then + if isCardHold and holdCounter >= 30 and (string.match(animationName, "Walk") and not string.match(animationName, "Pickup") or (EID.bagPlayer:GetCollectibleCount() ~= icount)) then EID.BagItems = {} else holdCounter = 0 @@ -629,7 +629,7 @@ local function shiftBagContent() 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 + if Input.IsActionTriggered(ButtonAction.ACTION_DROP, EID.bagPlayer.ControllerIndex) and IsTaintedCain() then shiftBagContent() end end @@ -712,7 +712,7 @@ local function getHotkeyString() local hideDesc = EID:getDescriptionEntry("CraftingHideKey") local previewDesc = EID:getDescriptionEntry("CraftingPreviewKey") - local controllerEnabled = EID.player.ControllerIndex > 0 + local controllerEnabled = EID.bagPlayer.ControllerIndex > 0 local hideKey = HotkeyToString[EID.Config["CraftingHideKey"]] local hideButton = controllerEnabled and ControllerToString[EID.Config["CraftingHideButton"]] local previewKey = HotkeyToString[EID.Config["CraftingResultKey"]] @@ -881,11 +881,11 @@ function EID:handleBagOfCraftingRendering() -- 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 + if Input.IsButtonTriggered(EID.Config["CraftingHideKey"], 0) or Input.IsButtonTriggered(EID.Config["CraftingHideButton"], EID.bagPlayer.ControllerIndex) then craftingIsHidden = not craftingIsHidden end - if Input.IsButtonTriggered(EID.Config["CraftingResultKey"], 0) or Input.IsButtonTriggered(EID.Config["CraftingResultButton"], EID.player.ControllerIndex) then + if Input.IsButtonTriggered(EID.Config["CraftingResultKey"], 0) or Input.IsButtonTriggered(EID.Config["CraftingResultButton"], EID.bagPlayer.ControllerIndex) then showCraftingResult = not showCraftingResult end end @@ -901,7 +901,7 @@ function EID:handleBagOfCraftingRendering() if EID.Config["DisplayBagOfCrafting"] == "never" then return false end - if EID.Config["DisplayBagOfCrafting"] == "hold" and not string.find(EID.player:GetSprite():GetAnimation(), "PickupWalk") then + if EID.Config["DisplayBagOfCrafting"] == "hold" and not string.find(EID.bagPlayer:GetSprite():GetAnimation(), "PickupWalk") then return false end if game:GetRoom():GetFrameCount() < 2 then @@ -922,7 +922,7 @@ function EID:handleBagOfCraftingRendering() EID:appendToDescription(descriptionObj,"#" .. backupDesc .. "#{{Collectible" .. backupResult .. "}} " .. backupObjName .. "#{{Blank}} " .. tabText) end - EID:printDescription(descriptionObj) + EID:addDescriptionToPrint(descriptionObj) return true end -- if we're in Preview Only mode, then we have nothing more to do @@ -985,12 +985,12 @@ function EID:handleBagOfCraftingRendering() if EID.Config["BagOfCraftingDisplayMode"] == "Pickups Only" then EID:appendToDescription(customDescObj, getHotkeyString()) EID:appendToDescription(customDescObj, getFloorItemsString(false, roomItems)) - EID:printDescription(customDescObj) + EID:addDescriptionToPrint(customDescObj) return true elseif EID.Config["BagOfCraftingDisplayMode"] == "No Recipes" then if not EID.RefreshBagTextbox and prevDesc ~= "" and not EID.OptionChanged then EID:appendToDescription(customDescObj, prevDesc) - EID:printDescription(customDescObj) + EID:addDescriptionToPrint(customDescObj) return true end @@ -1013,7 +1013,7 @@ function EID:handleBagOfCraftingRendering() if (bestQuality > bagQuality) then prevDesc = prevDesc .. bestQualityDesc .. " " .. bestQuality .. "#{{Blank}} " .. tableToCraftingIconsFunc(self,mostValuableBag, true) .. "#" .. bestResult .. "#" end EID:appendToDescription(customDescObj, prevDesc) - EID:printDescription(customDescObj) + EID:addDescriptionToPrint(customDescObj) return true end @@ -1064,35 +1064,35 @@ function EID:handleBagOfCraftingRendering() end 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 + if Input.IsActionPressed(EID.Config["BagOfCraftingToggleKey"], EID.bagPlayer.ControllerIndex) then + EID.bagPlayer.ControlsCooldown = 2 + if Input.IsActionTriggered(ButtonAction.ACTION_SHOOTDOWN, EID.bagPlayer.ControllerIndex) then bagOfCraftingOffset = math.min(numResults-(numResults%EID.Config["BagOfCraftingResults"]), bagOfCraftingOffset + EID.Config["BagOfCraftingResults"]) downHeld = Isaac.GetTime() - elseif Input.IsActionTriggered(ButtonAction.ACTION_SHOOTUP, EID.player.ControllerIndex) then + elseif Input.IsActionTriggered(ButtonAction.ACTION_SHOOTUP, EID.bagPlayer.ControllerIndex) then bagOfCraftingOffset = math.max(0, bagOfCraftingOffset - EID.Config["BagOfCraftingResults"]) 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 + elseif Input.IsActionTriggered(ButtonAction.ACTION_SHOOTLEFT, EID.bagPlayer.ControllerIndex) then EID.RefreshBagTextbox = true if (lockedResults == nil) then lockedResults = queryString else lockedResults = nil end --refresh the recipes - elseif Input.IsActionTriggered(ButtonAction.ACTION_SHOOTRIGHT, EID.player.ControllerIndex) then + elseif Input.IsActionTriggered(ButtonAction.ACTION_SHOOTRIGHT, EID.bagPlayer.ControllerIndex) then if (lockedResults == nil) then refreshNextTick = true end end --scroll pages quickly if the button is held - if Input.IsActionPressed(ButtonAction.ACTION_SHOOTDOWN, EID.player.ControllerIndex) and Isaac.GetTime() - downHeld > 750 then + if Input.IsActionPressed(ButtonAction.ACTION_SHOOTDOWN, EID.bagPlayer.ControllerIndex) and Isaac.GetTime() - downHeld > 750 then bagOfCraftingOffset = math.min(numResults-(numResults%EID.Config["BagOfCraftingResults"]), bagOfCraftingOffset + EID.Config["BagOfCraftingResults"]) downHeld = Isaac.GetTime() - 700 - elseif Input.IsActionPressed(ButtonAction.ACTION_SHOOTUP, EID.player.ControllerIndex) and Isaac.GetTime() - upHeld > 750 then + elseif Input.IsActionPressed(ButtonAction.ACTION_SHOOTUP, EID.bagPlayer.ControllerIndex) and Isaac.GetTime() - upHeld > 750 then bagOfCraftingOffset = math.max(0, bagOfCraftingOffset - EID.Config["BagOfCraftingResults"]) upHeld = Isaac.GetTime() - 700 end --reset bag contents when holding Use Pill/Card - if Input.IsActionPressed(ButtonAction.ACTION_PILLCARD, EID.player.ControllerIndex) then + if Input.IsActionPressed(ButtonAction.ACTION_PILLCARD, EID.bagPlayer.ControllerIndex) then resetBagCounter = resetBagCounter + 1 if resetBagCounter > 120 then EID.BagItems = {} @@ -1108,7 +1108,7 @@ function EID:handleBagOfCraftingRendering() if not EID.RefreshBagTextbox and prevDesc ~= "" and bagOfCraftingOffset == prevOffset and not EID.OptionChanged then EID:appendToDescription(customDescObj, prevDesc) - EID:printDescription(customDescObj) + EID:addDescriptionToPrint(customDescObj) return true end @@ -1173,6 +1173,6 @@ function EID:handleBagOfCraftingRendering() end EID:appendToDescription(customDescObj, prevDesc) - EID:printDescription(customDescObj) + EID:addDescriptionToPrint(customDescObj) return true end From ed7479865dbf4737f42f9471e8361965e39ca561 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 00:18:43 -0500 Subject: [PATCH 02/23] Fix glitched item mod indicator --- eid_api.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/eid_api.lua b/eid_api.lua index b9a43fb4..a6442814 100644 --- a/eid_api.lua +++ b/eid_api.lua @@ -62,7 +62,10 @@ local dynamicSpriteCache = {} -- used to store sprite objects of collectible ico function EID:addCollectible(id, description, itemName, language) itemName = itemName or nil language = language or "en_us" - EID.descriptions[language].custom["5.100." .. id] = {id, itemName, description, EID._currentMod} + modName = EID._currentMod + -- Glitched Items exception so they don't have a mod name + if id > 4294960000 then modName = nil end + EID.descriptions[language].custom["5.100." .. id] = {id, itemName, description, modName} end -- Adds a description for a trinket. Optional parameters: itemName, language @@ -708,7 +711,7 @@ function EID:renderIcon(spriteObj, posX, posY, callback, animName, animFrame) end if EID.CachingDescription then - table.insert(EID.CachedIcons, {spriteObj, posX, posY, callback, animName, animFrame}) + table.insert(EID.CachedIcons[#EID.CachedIcons], {spriteObj, posX, posY, callback, animName, animFrame}) end spriteObj:Render(Vector(posX, posY), nullVector, nullVector) @@ -901,7 +904,7 @@ function EID:renderString(str, position, scale, kcolor) EID:renderInlineIcons(spriteTable, position.X + offsetX, position.Y) EID.font:DrawStringScaledUTF8(strFiltered, position.X + offsetX, position.Y, scale.X, scale.Y, textPart[2], 0, false) if EID.CachingDescription then - table.insert(EID.CachedStrings, {strFiltered, position.X + offsetX, position.Y, textPart[2], textPart[4], EID.Config["Transparency"]}) + table.insert(EID.CachedStrings[#EID.CachedStrings], {strFiltered, position.X + offsetX, position.Y, textPart[2], textPart[4], EID.Config["Transparency"]}) end offsetX = offsetX + EID:getStrWidth(strFiltered) * scale.X end From 2792e2d284d14eff6bd40fd034762d77d0084222 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 00:19:36 -0500 Subject: [PATCH 03/23] Fix error if item name doesn't convert to an ID --- eid_tmtrainer.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eid_tmtrainer.lua b/eid_tmtrainer.lua index 54155d65..38924879 100644 --- a/eid_tmtrainer.lua +++ b/eid_tmtrainer.lua @@ -75,7 +75,10 @@ local function parseEffectLine(raw) if (i ~= #words) then name = name .. " " end end - replacements[1] = "{{Collectible" .. Isaac.GetItemIdByName(name) .. "}} " .. name + replacements[1] = name + if (Isaac.GetItemIdByName(name) > 0) then + replacements[1] = "{{Collectible" .. Isaac.GetItemIdByName(name) .. "}} " .. name + end elseif effectType == "convert_entities" then replacements[1] = "{{ColorGray}}" .. entityToName(words[6], true) .. "{{CR}}" replacements[2] = "{{ColorGray}}" .. entityToName(words[8]) From 4bfe3a3ecc558fde225071e484c17d3f56da280e Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 00:23:20 -0500 Subject: [PATCH 04/23] Co-op overhaul --- main.lua | 587 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 364 insertions(+), 223 deletions(-) diff --git a/main.lua b/main.lua index ff87edb4..dba8e23e 100644 --- a/main.lua +++ b/main.lua @@ -12,7 +12,12 @@ EID.Config.Version = "3.2" -- note: changing this will reset everyone's settings EID.ModVersion = "4.21" EID.DefaultConfig.Version = EID.Config.Version EID.isHidden = false -EID.player = nil +EID.player = nil -- The primary Player Entity of Player 1 +EID.players = {} -- Both Player Entities of Player 1 if applicable (includes Esau, T.Forgotten) +EID.coopMainPlayers = {} -- The primary Player Entity for each controller being used +EID.coopAllPlayers = {} -- Every Player Entity (includes Esau, T.Forgotten) +EID.controllerIndexes = {} -- A table to map each controller index to their player number for coloring indicators +EID.isMultiplayer = false -- Used to color P1's highlight/outline indicators (single player just uses white) -- general variables EID.PositionModifiers = {} @@ -33,6 +38,7 @@ EID.CollectedItems = {} EID.IgnoredEntities = {} local pathsChecked = {} local altPathItemChecked = {} +local alwaysUseLocalMode = false -- set to true after drawing a non-local mode description this frame EID.GameUpdateCount = 0 EID.GameRenderCount = 0 @@ -434,20 +440,82 @@ end --------------------------------------------------------------------------- ---------------------------Printing Functions------------------------------ -local previousDesc = "" -local previousRenderPos = Vector.Zero +EID.previousDescs = {} EID.CachingDescription = false EID.CachedIcons = {} EID.CachedStrings = {} +EID.CachedRenderPoses = {} +EID.descriptionsToPrint = {} +EID.entitiesToPrint = {} -function EID:printDescription(desc) +function EID:addDescriptionToPrint(desc) + if desc.Entity and EID.entitiesToPrint[GetPtrHash(desc.Entity)] then return end + table.insert(EID.descriptionsToPrint, desc) + if desc.Entity then EID.entitiesToPrint[GetPtrHash(desc.Entity)] = true end +end + +function EID:printDescriptions(useCached) + EID.CachingDescription = false + if not useCached then + -- Test if we should print our cached descriptions or not + -- Did an option change, or it's a different number of descriptions? + if #EID.descriptionsToPrint ~= #EID.previousDescs or EID.OptionChanged or EID.MCM_OptionChanged then + EID:printNewDescriptions() + return + end + -- Do the description entities match, and the description texts match? + for i,newDesc in ipairs(EID.descriptionsToPrint) do + local oldDesc = EID.previousDescs[i] + if newDesc.Description ~= oldDesc.Description or (newDesc.Entity and oldDesc.Entity and GetPtrHash(newDesc.Entity) ~= GetPtrHash(oldDesc.Entity)) then + EID:printNewDescriptions() + return + end + end + end + + -- Print our cached descriptions + for i,newDesc in ipairs(EID.descriptionsToPrint) do + EID:printDescription(newDesc, i) + end +end + +function EID:printNewDescriptions() + EID.CachingDescription = true + EID.CachedIcons = {} + EID.CachedStrings = {} + EID.CachedRenderPoses = {} + EID.previousDescs = {} + + for i,newDesc in ipairs(EID.descriptionsToPrint) do + if newDesc.Description == "UnidentifiedPill" then + if EID:renderUnidentifiedPill(newDesc.Entity) ~= false then + table.insert(EID.previousDescs, newDesc) + end + elseif newDesc.Description == "QuestionMark" then + if EID:renderQuestionMark(newDesc.Entity) ~= false then + table.insert(EID.previousDescs, newDesc) + end + elseif EID:printDescription(newDesc) ~= false then + table.insert(EID.previousDescs, newDesc) + end + end + EID.CachingDescription = false +end + +function EID:printDescription(desc, cachedID) + EID:PositionLocalMode(desc.Entity) + -- Do not print this description if it has to be drawn in the top-left and we've already drawn a top-left desc this frame + if EID.CurrentScaleType == "Size" then + if alwaysUseLocalMode then return false + else alwaysUseLocalMode = true end + end local renderPos = EID:getTextPosition() - EID.CachingDescription = false - if previousDesc == desc.Description and not EID.OptionChanged and not EID.MCM_OptionChanged then - local localModeDiff = renderPos - previousRenderPos + --print(cachedID) + if cachedID then + local localModeDiff = renderPos - EID.CachedRenderPoses[cachedID] -- Drawing our currently saved description - for _,icon in ipairs(EID.CachedIcons) do + for _,icon in ipairs(EID.CachedIcons[cachedID]) do -- set inline icon to specific frame if icon[6] >= 0 then icon[1]:SetFrame(icon[5], icon[6]) @@ -459,7 +527,7 @@ function EID:printDescription(desc) end EID:renderIcon(icon[1], icon[2] + localModeDiff.X, icon[3] + localModeDiff.Y, icon[4]) end - for i,str in ipairs(EID.CachedStrings) do + for _,str in ipairs(EID.CachedStrings[cachedID]) do -- check for func (Rainbow, Blink, Fade), reset the color's Alpha to EID.Config["Transparency"] if func if str[5] then str[4].Alpha = str[6] @@ -469,12 +537,9 @@ function EID:printDescription(desc) end return else - -- Drawing a new description - EID.CachingDescription = true - EID.CachedIcons = {} - EID.CachedStrings = {} - previousDesc = desc.Description - previousRenderPos = Vector(renderPos.X, renderPos.Y) + table.insert(EID.CachedIcons, {}) + table.insert(EID.CachedStrings, {}) + table.insert(EID.CachedRenderPoses, Vector(renderPos.X, renderPos.Y)) end local textScale = Vector(EID.Scale, EID.Scale) @@ -667,13 +732,49 @@ end --------------------------------------------------------------------------- ---------------------------Handle Rendering-------------------------------- -function EID:renderQuestionMark() +function EID:renderQuestionMark(entity) + EID:PositionLocalMode(entity) + if EID.CurrentScaleType == "Size" then + if alwaysUseLocalMode then return false + else alwaysUseLocalMode = true end + end EID.IconSprite:Play("CurseOfBlind") local pos = EID:getTextPosition() + if EID.CachingDescription then + table.insert(EID.CachedStrings, {}) + table.insert(EID.CachedIcons, {}) + table.insert(EID.CachedRenderPoses, Vector(pos.X, pos.Y)) + end EID:renderIcon(EID.IconSprite, pos.X + 5 * EID.Scale, pos.Y + 5 * EID.Scale, nil, "CurseOfBlind", 0) end -function EID:renderIndicator(entity) +function EID:renderUnidentifiedPill(entity) + EID:PositionLocalMode(entity) + if EID.CurrentScaleType == "Size" then + if alwaysUseLocalMode then return false + else alwaysUseLocalMode = true end + end + local pillColor = entity.SubType + if pillColor >= 2049 then + pillColor = pillColor - 2048 + end + local pos = EID:getTextPosition() + if EID.CachingDescription then + table.insert(EID.CachedStrings, {}) + table.insert(EID.CachedIcons, {}) + table.insert(EID.CachedRenderPoses, Vector(pos.X, pos.Y)) + end + EID:renderString( + "{{Pill"..pillColor.."}} "..EID:getDescriptionEntry("unidentifiedPill"), + pos, + Vector(EID.Scale, EID.Scale), + EID:getErrorColor() + ) +end + +-- RGB colors for each player's highlights (Red, Blue, Green, Yellow) +local playerRGB = { {1,0.6,0.6}, {0.5,0.75,1}, {0.5,1,0.75}, {0.9, 0.9, 0.5} } +function EID:renderIndicator(entity, playerNum) if EID.Config["Indicator"] == "none" then return end @@ -708,20 +809,21 @@ function EID:renderIndicator(entity) -- Don't apply sprite.Color changes to Effects (Dice Floors, Card Reading Portals), use Arrow instead if EID.Config["Indicator"] == "arrow" or entity.Type == 1000 then - ArrowSprite:Update() - ArrowSprite:Render(arrowPos, nullVector, nullVector) + ArrowSprite:RenderLayer(playerNum-1, arrowPos, nullVector, nullVector) else + local colorMult = {1,1,1} + if EID.isMultiplayer then colorMult = playerRGB[playerNum] end if EID.Config["Indicator"] == "blink" then local c = 255 - math.floor(255 * ((entity.FrameCount % 40) / 40)) - sprite.Color = Color(1, 1, 1, 1, c/repDiv, c/repDiv, c/repDiv) + sprite.Color = Color(1, 1, 1, 1, c/repDiv * colorMult[1], c/repDiv * colorMult[2], c/repDiv * colorMult[3]) EID:renderEntity(entity, sprite, entityPos) sprite.Color = Color(1, 1, 1, 1, 0, 0, 0) else if EID.Config["Indicator"] == "border" then local c = 255 - math.floor(255 * ((entity.FrameCount % 40) / 40)) - sprite.Color = Color(1, 1, 1, 1, c/repDiv, c/repDiv, c/repDiv) + sprite.Color = Color(1, 1, 1, 1, c/repDiv * colorMult[1], c/repDiv * colorMult[2], c/repDiv * colorMult[3]) elseif EID.Config["Indicator"] == "highlight" then - sprite.Color = Color(1, 1, 1, 1, 255/repDiv, 255/repDiv, 255/repDiv) + sprite.Color = Color(1, 1, 1, 1, 255/repDiv* colorMult[1], 255/repDiv* colorMult[2], 255/repDiv* colorMult[3]) end EID:renderEntity(entity, sprite, entityPos + Vector(0, 1)) EID:renderEntity(entity, sprite, entityPos + Vector(0, -1)) @@ -738,7 +840,7 @@ end function EID:PositionLocalMode(entity) -- don't use Local Mode for descriptions without an entity (or dice floors) - if EID.Config["DisplayMode"] == "local" and entity and entity.Variant ~= EffectVariant.DICE_FLOOR then + if (EID.Config["DisplayMode"] == "local" or alwaysUseLocalMode) and entity and entity.Variant ~= EffectVariant.DICE_FLOOR then EID.Scale = EID.Config["LocalModeSize"] EID.CurrentScaleType = "LocalModeSize" local textBoxWidth = EID.Config["LocalModeCentered"] and tonumber(EID.Config["TextboxWidth"])/2 * EID.Scale or -30 @@ -809,12 +911,35 @@ function EID:handleHoverHUD() end function EID:setPlayer() - local p = Isaac.GetPlayer(0) - if REPENTANCE and p:GetPlayerType() == PlayerType.PLAYER_THEFORGOTTEN_B then - EID.player = p:GetOtherTwin() or p - else - EID.player = p + -- Get our primary player, our list of all players, and a list of the primary player for each controller + -- make this run less than 60 times a second? + EID.coopAllPlayers = {} -- all player characters in order + EID.coopMainPlayers = {} -- the primary player character from each controller + EID.controllerIndexes = {} -- simple table to map each controller index to P1/P2/P3/P4 (since index 0 could be P2) + local currentPlayerNum = 1 + for i = 0, game:GetNumPlayers() - 1 do + local player = Isaac.GetPlayer(i) + -- Don't include player entities with a parent (Strawman / Soul of Jacob and Esau) + if player.Parent == nil then + local newIndex = not EID.controllerIndexes[player.ControllerIndex] + -- Tainted Soul is treated as the primary player for Tainted Forgotten, so swap them + if REPENTANCE and (player:GetPlayerType() == PlayerType.PLAYER_THEFORGOTTEN_B + or player:GetPlayerType() == PlayerType.PLAYER_THESOUL_B) then + player = player:GetOtherTwin() or player + end + + table.insert(EID.coopAllPlayers, player) + if newIndex then + table.insert(EID.coopMainPlayers, player) + EID.controllerIndexes[player.ControllerIndex] = currentPlayerNum + currentPlayerNum = currentPlayerNum + 1 + end + end end + EID.player = EID.coopAllPlayers[1] + -- second entry will be nil if no twin or AB+ (REPENTANCE = nil) + EID.players = { EID.player, REPENTANCE and EID.player:GetOtherTwin() } + EID.isMultiplayer = #EID.coopMainPlayers > 1 end --------------------------------------------------------------------------- @@ -990,6 +1115,8 @@ local searchPartitions = EntityPartition.FAMILIAR + EntityPartition.ENEMY + Enti EID.lastDescriptionEntity = nil EID.lastDist = 0 EID.OptionChanged = false +local currentPlayer = 1 +EID.bagPlayer = nil local function onRender(t) -- Increases by 60 per second, ignores pauses @@ -997,9 +1124,14 @@ local function onRender(t) EID.OptionChanged = EID.MCM_OptionChanged EID.MCM_OptionChanged = false EID:resumeCoroutines() + ArrowSprite:Update() EID.isDisplaying = false EID:setPlayer() + EID.bagPlayer = EID.player + EID.descriptionsToPrint = {} + EID.entitiesToPrint = {} + alwaysUseLocalMode = false -- Do not check our hotkeys while a tab that can modify the hotkey is open if EID.MCMCompat_isDisplayingEIDTab ~= "Visuals" then -- scale key must be handled before resetting to non-local mode @@ -1008,7 +1140,6 @@ local function onRender(t) EID.isHidden = not EID.isHidden end end - EID:PositionLocalMode() -- default to non-local mode to fix MCM / Bag errors EID.TabPreviewID = 0 -- If MCM is open, don't show anything unless we're in a tab labeled as "Visuals" or "Crafting" @@ -1016,6 +1147,7 @@ local function onRender(t) return end + -- Handle descriptions that display regardless of player position checkStartOfRunWarnings() checkPosModifiers() EID:renderMCMDummyDescription() @@ -1034,222 +1166,231 @@ local function onRender(t) return end - if REPENTANCE and EID.player:HasCollectible(710) then - local success = EID:handleBagOfCraftingRendering() - -- If the Bag of Crafting did rendering, don't display any other description - if success then - return - end - end - -- If we're in the Crafting options tab, the only rendering we want to happen is the Bag of Crafting - if EID.MCMCompat_isDisplayingEIDTab == "Crafting" then return end - - EID.lastDescriptionEntity = nil - EID.lastDist = 10000 - local searchGroups = {} - local sourcePos = EID.player.Position - if EID.Config["EnableMouseControls"] then local hudDescription = EID:handleHoverHUD() if hudDescription then - EID:printDescription(hudDescription) - return + EID:addDescriptionToPrint(hudDescription) end end - table.insert(searchGroups, Isaac.FindInRadius(sourcePos, tonumber(EID.Config["MaxDistance"])*40, searchPartitions)) - for k,_ in pairs(EID.effectList) do - table.insert(searchGroups, Isaac.FindByType(EntityType.ENTITY_EFFECT, k, -1, true, false)) + -- Decide how many player entities we're checking + -- Primary P1 only, P1 + Esau, primary all controllers, or all controllers + Esaus + local playerSearch = { EID.player } + if EID.Config["CoopDescriptions"] then + if EID.Config["PairedPlayerDescriptions"] then playerSearch = EID.coopAllPlayers + else playerSearch = EID.coopMainPlayers end + elseif EID.Config["PairedPlayerDescriptions"] then + playerSearch = EID.players end - for _, entitySearch in ipairs(searchGroups) do - for i, entity in ipairs(entitySearch) do - if EID:hasDescription(entity) and entity.FrameCount > 0 then - local diff = entity.Position:__sub(sourcePos) - -- break ties with the render offset (for Mega Chest double collectibles) - if diff:Length() == EID.lastDist and entity:GetData()["EID_RenderOffset"] then - diff = diff + entity:GetData()["EID_RenderOffset"] + -- Check for descriptions to print per player + for i,player in ipairs(playerSearch) do + local displayedDesc = false + + -- Check if this player has the Bag of Crafting + local craftingSuccess = false + if REPENTANCE and player:HasCollectible(710) then + EID.bagPlayer = player + craftingSuccess = EID:handleBagOfCraftingRendering() + if craftingSuccess then + displayedDesc = true + end + end + -- If we're in the Crafting options tab, the only rendering we want to happen is the Bag of Crafting preview + if EID.MCMCompat_isDisplayingEIDTab == "Crafting" then + if craftingSuccess then EID:printDescription(descsToDisplay[#descsToDisplay]) end + return + end + + if not displayedDesc or EID.Config["DisplayAllNearby"] then + -- Searching for the closest describable entity to this player + EID.lastDescriptionEntity = nil + EID.lastDist = 10000 + local searchGroups = {} + local sourcePos = player.Position + local inRangeEntities = {} + + table.insert(searchGroups, Isaac.FindInRadius(sourcePos, tonumber(EID.Config["MaxDistance"])*40, searchPartitions)) + for k,_ in pairs(EID.effectList) do + table.insert(searchGroups, Isaac.FindByType(EntityType.ENTITY_EFFECT, k, -1, true, false)) + end + + for _, entitySearch in ipairs(searchGroups) do + for i, entity in ipairs(entitySearch) do + if EID:hasDescription(entity) and entity.FrameCount > 0 then + table.insert(inRangeEntities, entity) + local diff = entity.Position:__sub(sourcePos) + -- break ties with the render offset (for Mega Chest double collectibles) + if diff:Length() == EID.lastDist and entity:GetData()["EID_RenderOffset"] then + diff = diff + entity:GetData()["EID_RenderOffset"] + end + if diff:Length() < EID.lastDist then + EID.lastDescriptionEntity = entity + EID.lastDist = diff:Length() + end + end end - if diff:Length() < EID.lastDist then - EID.lastDescriptionEntity = entity - EID.lastDist = diff:Length() + end + + if EID.Config["DisplayAllNearby"] then + -- make the closest entity the first to get drawn + table.insert(inRangeEntities, 1, EID.lastDescriptionEntity) + else + -- we only want to describe the closest entity + inRangeEntities = { EID.lastDescriptionEntity } + end + --inRangeEntities = { EID.lastDescriptionEntity } + table.insert(inRangeEntities, 1, EID.lastDescriptionEntity) + -- Only display the indicator for the primary (closest / crafting) description + if EID.lastDescriptionEntity and not displayedCrafting then + EID:renderIndicator(EID.lastDescriptionEntity, EID.controllerIndexes[player.ControllerIndex]) + end + + for _,closest in ipairs(inRangeEntities) do + + --Handle GetData Entities (specific) + if EID.Config["EnableEntityDescriptions"] and EID:getEntityData(closest, "EID_Description") then + local desc = EID:getEntityData(closest, "EID_Description") + local origDesc = EID:getDescriptionObjByEntity(closest) + if type(desc) == "table" then + origDesc.Description = desc.Description or origDesc.Description + origDesc.Name = desc.Name or origDesc.Name + origDesc.Transformation = desc.Transformation or origDesc.Transformation + else + origDesc.Description = desc + end + EID:addDescriptionToPrint(origDesc) + + -- Handle Glitched Items + elseif closest.Type == 5 and closest.Variant == 100 and closest.SubType > 4294960000 then + local glitchedObj = EID:getDescriptionObj(closest.Type, closest.Variant, closest.SubType, closest) + local glitchedDesc = EID:getXMLDescription(closest.Type, closest.Variant, closest.SubType) + + -- force the default glitchy description if option is off + if not EID.Config["DisplayGlitchedItemInfo"] then + glitchedObj.Description = glitchedDesc + -- grab the Item Config info if eid_tmtrainer.lua hasn't taken care of it, and it hasn't been done before + elseif not debug and glitchedObj.Description == glitchedDesc then + EID:addCollectible(closest.SubType, EID:CheckGlitchedItemConfig(closest.SubType) .. glitchedDesc) + glitchedObj = EID:getDescriptionObj(closest.Type, closest.Variant, closest.SubType, closest) + end + + EID:addDescriptionToPrint(glitchedObj) + -- Handle Dice Room Floor + elseif closest.Type == 1000 and closest.Variant == 76 then + EID:addDescriptionToPrint(EID:getDescriptionObj(closest.Type, closest.Variant, closest.SubType+1, closest)) + -- Handle Card Reading Portals + elseif closest.Type == 1000 and closest.Variant == 161 and closest.SubType <= 2 then + local subtypeToCard = {18, 5, 19} + -- Reuse the descriptions of The Emperor/Stars/Moon, so no localization needed + local descriptionObj = EID:getDescriptionObj(5, 300, subtypeToCard[closest.SubType+1], closest) + -- Card Reading's name + descriptionObj.Name = EID:getObjectName(5, 100, 660) + EID:addDescriptionToPrint(descriptionObj) + -- Handle Crane Game + elseif closest.Type == 6 and closest.Variant == 16 then + if EID.CraneItemType[tostring(closest.InitSeed)] then + if EID:getEntityData(closest, "EID_DontHide") ~= true then + if (EID:hasCurseBlind() and EID.Config["DisableOnCurse"]) or (game.Challenge == Challenge.CHALLENGE_APRILS_FOOL and EID.Config["DisableOnAprilFoolsChallenge"]) then + EID:addDescriptionToPrint({ Description = "QuestionMark", Entity = closest}) + end + end + local collectibleID = EID.CraneItemType[tostring(closest.InitSeed)] + local descriptionObj = EID:getDescriptionObj(5, 100, collectibleID, closest) + + EID:addDescriptionToPrint(descriptionObj) + end + + elseif closest.Variant == 110 then + --Handle Broken Shovel + local descriptionObj = EID:getDescriptionObj(5, 100, 550, closest) + EID:addDescriptionToPrint(descriptionObj) + + elseif closest.Variant == PickupVariant.PICKUP_TRINKET then + --Handle Trinkets + local descriptionObj = EID:getDescriptionObjByEntity(closest) + EID:addDescriptionToPrint(descriptionObj) + + elseif closest.Variant == PickupVariant.PICKUP_COLLECTIBLE then + --Handle Collectibles + if EID:getEntityData(closest, "EID_DontHide") ~= true then + if (EID:hasCurseBlind() and not closest:ToPickup().Touched and EID.Config["DisableOnCurse"] and not isDeathCertRoom) or (EID.Config["DisableOnAltPath"] and not closest:ToPickup().Touched and EID:IsAltChoice(closest)) or (game.Challenge == Challenge.CHALLENGE_APRILS_FOOL and EID.Config["DisableOnAprilFoolsChallenge"]) then + EID:addDescriptionToPrint({ Description = "QuestionMark", Entity = closest}) + end + end + local descriptionObj = EID:getDescriptionObjByEntity(closest) + EID:addDescriptionToPrint(descriptionObj) + + elseif closest.Variant == PickupVariant.PICKUP_TAROTCARD then + --Handle Cards & Runes + if (not EID.Config["DisplayObstructedCardInfo"] or not EID.Config["DisplayObstructedSoulstoneInfo"]) and closest.FrameCount < 3 then + -- small delay when having obstruction enabled & entering the room to prevent spoilers + --return + end + if EID:getEntityData(closest, "EID_DontHide") ~= true then + local isSoulstone = closest.SubType >= 81 and closest.SubType <= 97 + local hideinShop = closest:ToPickup():IsShopItem() and ((not isSoulstone and not EID.Config["DisplayCardInfoShop"]) or (isSoulstone and not EID.Config["DisplaySoulstoneInfoShop"])) + local isOptionsSpawn = REPENTANCE and not EID.Config["DisplayCardInfoOptions?"] and closest:ToPickup().OptionsPickupIndex > 0 + local obstructed = ((not isSoulstone and not EID.Config["DisplayObstructedCardInfo"]) or + (not EID.Config["DisplayObstructedSoulstoneInfo"] and isSoulstone)) and + (not pathsChecked[closest.InitSeed] and not attemptPathfind(closest)) + if isOptionsSpawn or hideinShop or obstructed or (REPENTANCE and game.Challenge == Challenge.CHALLENGE_CANTRIPPED) then + EID:addDescriptionToPrint({ Description = "QuestionMark", Entity = closest}) + end + end + local descriptionObj = EID:getDescriptionObjByEntity(closest) + EID:addDescriptionToPrint(descriptionObj) + + elseif closest.Variant == PickupVariant.PICKUP_PILL then + --Handle Pills + if not EID.Config["DisplayObstructedPillInfo"] and closest.FrameCount < 3 then + -- small delay when having obstruction enabled & entering the room to prevent spoilers + --return + end + if EID:getEntityData(closest, "EID_DontHide") ~= true then + local hideinShop = closest:ToPickup():IsShopItem() and not EID.Config["DisplayPillInfoShop"] + local isOptionsSpawn = REPENTANCE and not EID.Config["DisplayPillInfoOptions?"] and closest:ToPickup().OptionsPickupIndex > 0 + local obstructed = not EID.Config["DisplayObstructedPillInfo"] and + (not pathsChecked[closest.InitSeed] and not attemptPathfind(closest)) + if isOptionsSpawn or hideinShop or obstructed then + EID:addDescriptionToPrint({ Description = "QuestionMark", Entity = closest}) + end + end + + local pillColor = closest.SubType + local pool = game:GetItemPool() + local identified = pool:IsPillIdentified(pillColor) + if REPENTANCE and pillColor % PillColor.PILL_GIANT_FLAG == PillColor.PILL_GOLD then identified = true end + if (identified or EID.Config["ShowUnidentifiedPillDescriptions"]) then + local descEntry = EID:getDescriptionObj(closest.Type, closest.Variant, pillColor, closest) + EID:addDescriptionToPrint(descEntry) + else + EID:addDescriptionToPrint({ Description = "UnidentifiedPill", Entity = closest}) + end + elseif EID.Config["EnableEntityDescriptions"] then + --Handle Entities (omni) + local descriptionEntry = EID:getDescriptionObjByEntity(closest) + if descriptionEntry~=nil then + EID:addDescriptionToPrint(descriptionEntry) + end end end end end - - - local closest = EID.lastDescriptionEntity - -- if no entity in range, display Sacrifice Room information - if EID.lastDist / 40 > tonumber(EID.Config["MaxDistance"]) then + -- if no entities to display, display Sacrifice Room information + -- it will be last priority for the main spot if DisplayAllNearby is on + if #EID.descriptionsToPrint == 0 or EID.Config["DisplayAllNearby"] then if game:GetRoom():GetType() == RoomType.ROOM_SACRIFICE and EID.Config["DisplaySacrificeInfo"] then local curRoomIndex = game:GetLevel():GetCurrentRoomIndex() local curCounter = EID.sacrificeCounter[curRoomIndex] or 1 local sacrificeDesc = EID:getDescriptionObj(-999, -1, curCounter) sacrificeDesc.Name = sacrificeDesc.Name.." ("..curCounter.."/12)" - EID:printDescription(sacrificeDesc) - end - return - end - - if closest == nil then - return - end - - EID.isDisplaying = true - - --Handle Indicators - EID:renderIndicator(closest) - - --Position the description under the entity in Local Mode - EID:PositionLocalMode(closest) - - --Handle GetData Entities (specific) - if EID.Config["EnableEntityDescriptions"] and EID:getEntityData(closest, "EID_Description") then - local desc = EID:getEntityData(closest, "EID_Description") - local origDesc = EID:getDescriptionObjByEntity(closest) - if type(desc) == "table" then - origDesc.Description = desc.Description or origDesc.Description - origDesc.Name = desc.Name or origDesc.Name - origDesc.Transformation = desc.Transformation or origDesc.Transformation - else - origDesc.Description = desc - end - EID:printDescription(origDesc) - return - end - - -- Handle Glitched Items - if closest.Type == 5 and closest.Variant == 100 and closest.SubType > 4294960000 then - local glitchedObj = EID:getDescriptionObj(closest.Type, closest.Variant, closest.SubType) - local glitchedDesc = EID:getXMLDescription(closest.Type, closest.Variant, closest.SubType) - - -- force the default glitchy description if option is off - if not EID.Config["DisplayGlitchedItemInfo"] then - glitchedObj.Description = glitchedDesc - -- grab the Item Config info if eid_tmtrainer.lua hasn't taken care of it, and it hasn't been done before - elseif not debug and glitchedObj.Description == glitchedDesc then - EID:addCollectible(closest.SubType, EID:CheckGlitchedItemConfig(closest.SubType) .. glitchedDesc) - glitchedObj = EID:getDescriptionObj(closest.Type, closest.Variant, closest.SubType) - end - - EID:printDescription(glitchedObj) - return - -- Handle Dice Room Floor - elseif closest.Type == 1000 and closest.Variant == 76 then - EID:printDescription(EID:getDescriptionObj(closest.Type, closest.Variant, closest.SubType+1, closest)) - return - -- Handle Card Reading Portals - elseif closest.Type == 1000 and closest.Variant == 161 and closest.SubType <= 2 then - local subtypeToCard = {18, 5, 19} - -- Reuse the descriptions of The Emperor/Stars/Moon, so no localization needed - local descriptionObj = EID:getDescriptionObj(5, 300, subtypeToCard[closest.SubType+1], closest) - -- Card Reading's name - descriptionObj.Name = EID:getObjectName(5, 100, 660) - EID:printDescription(descriptionObj) - return - -- Handle Crane Game - elseif closest.Type == 6 and closest.Variant == 16 then - if EID.CraneItemType[tostring(closest.InitSeed)] then - if EID:getEntityData(closest, "EID_DontHide") ~= true then - if (EID:hasCurseBlind() and EID.Config["DisableOnCurse"]) or (game.Challenge == Challenge.CHALLENGE_APRILS_FOOL and EID.Config["DisableOnAprilFoolsChallenge"]) then - EID:renderQuestionMark() - return - end - end - local collectibleID = EID.CraneItemType[tostring(closest.InitSeed)] - local descriptionObj = EID:getDescriptionObj(5, 100, collectibleID, closest) - - EID:printDescription(descriptionObj) - return + EID:addDescriptionToPrint(sacrificeDesc) end end - if closest.Variant == 110 then - --Handle Broken Shovel - local descriptionObj = EID:getDescriptionObj(5, 100, 550, closest) - EID:printDescription(descriptionObj) - elseif closest.Variant == PickupVariant.PICKUP_TRINKET then - --Handle Trinkets - local descriptionObj = EID:getDescriptionObjByEntity(closest) - EID:printDescription(descriptionObj) - - elseif closest.Variant == PickupVariant.PICKUP_COLLECTIBLE then - --Handle Collectibles - if EID:getEntityData(closest, "EID_DontHide") ~= true then - if (EID:hasCurseBlind() and not closest:ToPickup().Touched and EID.Config["DisableOnCurse"] and not isDeathCertRoom) or (EID.Config["DisableOnAltPath"] and not closest:ToPickup().Touched and EID:IsAltChoice(closest)) or (game.Challenge == Challenge.CHALLENGE_APRILS_FOOL and EID.Config["DisableOnAprilFoolsChallenge"]) then - EID:renderQuestionMark() - return - end - end - local descriptionObj = EID:getDescriptionObjByEntity(closest) - EID:printDescription(descriptionObj) - - elseif closest.Variant == PickupVariant.PICKUP_TAROTCARD then - --Handle Cards & Runes - if (not EID.Config["DisplayObstructedCardInfo"] or not EID.Config["DisplayObstructedSoulstoneInfo"]) and closest.FrameCount < 3 then - -- small delay when having obstruction enabled & entering the room to prevent spoilers - return - end - if EID:getEntityData(closest, "EID_DontHide") ~= true then - local isSoulstone = closest.SubType >= 81 and closest.SubType <= 97 - local hideinShop = closest:ToPickup():IsShopItem() and ((not isSoulstone and not EID.Config["DisplayCardInfoShop"]) or (isSoulstone and not EID.Config["DisplaySoulstoneInfoShop"])) - local isOptionsSpawn = REPENTANCE and not EID.Config["DisplayCardInfoOptions?"] and closest:ToPickup().OptionsPickupIndex > 0 - local obstructed = ((not isSoulstone and not EID.Config["DisplayObstructedCardInfo"]) or - (not EID.Config["DisplayObstructedSoulstoneInfo"] and isSoulstone)) and - (not pathsChecked[closest.InitSeed] and not attemptPathfind(closest)) - if isOptionsSpawn or hideinShop or obstructed or (REPENTANCE and game.Challenge == Challenge.CHALLENGE_CANTRIPPED) then - EID:renderQuestionMark() - return - end - end - local descriptionObj = EID:getDescriptionObjByEntity(closest) - EID:printDescription(descriptionObj) - - elseif closest.Variant == PickupVariant.PICKUP_PILL then - --Handle Pills - if not EID.Config["DisplayObstructedPillInfo"] and closest.FrameCount < 3 then - -- small delay when having obstruction enabled & entering the room to prevent spoilers - return - end - if EID:getEntityData(closest, "EID_DontHide") ~= true then - local hideinShop = closest:ToPickup():IsShopItem() and not EID.Config["DisplayPillInfoShop"] - local isOptionsSpawn = REPENTANCE and not EID.Config["DisplayPillInfoOptions?"] and closest:ToPickup().OptionsPickupIndex > 0 - local obstructed = not EID.Config["DisplayObstructedPillInfo"] and - (not pathsChecked[closest.InitSeed] and not attemptPathfind(closest)) - if isOptionsSpawn or hideinShop or obstructed then - EID:renderQuestionMark() - return - end - end - - local pillColor = closest.SubType - local pool = game:GetItemPool() - local identified = pool:IsPillIdentified(pillColor) - if REPENTANCE and pillColor % PillColor.PILL_GIANT_FLAG == PillColor.PILL_GOLD then identified = true end - if (identified or EID.Config["ShowUnidentifiedPillDescriptions"]) then - local descEntry = EID:getDescriptionObj(closest.Type, closest.Variant, pillColor, closest) - EID:printDescription(descEntry) - else - if pillColor >= 2049 then - pillColor = pillColor - 2048 - end - EID:renderString( - "{{Pill"..pillColor.."}} "..EID:getDescriptionEntry("unidentifiedPill"), - EID:getTextPosition(), - Vector(EID.Scale, EID.Scale), - EID:getErrorColor() - ) - end - elseif EID.Config["EnableEntityDescriptions"] then - --Handle Entities (omni) - local descriptionEntry = EID:getDescriptionObjByEntity(closest) - if descriptionEntry~=nil then - EID:printDescription(descriptionEntry) - return - end - end + EID:printDescriptions() end EID:AddCallback(ModCallbacks.MC_POST_RENDER, onRender) From c5fd064356054a3d46c355d05a80bccc7e937cdf Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 00:24:28 -0500 Subject: [PATCH 05/23] New co-op options, glitched = false default --- eid_config.lua | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/eid_config.lua b/eid_config.lua index 3254e510..2e1f591d 100644 --- a/eid_config.lua +++ b/eid_config.lua @@ -83,6 +83,19 @@ EID.UserConfig = { -- Current warnings are: Achievements are locked, outdated game version, and potentially having inaccurate modded Bag of Crafting recipes -- Default = false ["DisableStartOfRunWarnings"] = false, + + ---------- Co-op / Multiple Descriptions ----------- + + -- Allow P2/P3/P4 to display descriptions when in Repentance's Co-op mode + -- Default = true + ["CoopDescriptions"] = true, + -- Allow the paired player of certain characters (Esau, Tainted Forgotten's bone pile) to display descriptions + -- Default = false + ["PairedPlayerDescriptions"] = false, + -- Display a description for every object within range of you, instead of just the closest + -- Default = false + ["DisplayAllNearby"] = false, + -- Any descriptions displayed beyond the first one will use the "Local" display mode ---------- Display Modes ----------- @@ -209,8 +222,8 @@ EID.UserConfig = { -- Note: The --luadebug launch option is required for more detailed glitched item descriptions -- This option allows mods to have access to your files and should be turned on at your own risk! -- Without --luadebug, you still can see the effect the item will have on your Hearts, and what stats it might modify - -- Default = true - ["DisplayGlitchedItemInfo"] = true, + -- Default = false + ["DisplayGlitchedItemInfo"] = false, ---------- Sacrifice Room ---------- @@ -304,7 +317,7 @@ EID.UserConfig = { ["CraftingResultButton"] = -1, - ---------- Mouse Controls ------------ + ---------- Save Game Options ------------ -- Savegame which should be used for savegame specific features (for example: Item Collection page progress) -- Possible values: 0,1,2,3 @@ -400,6 +413,9 @@ EID.DefaultConfig = { ["HideInBattle"] = false, ["DisableObstructionOnFlight"] = true, ["DisableStartOfRunWarnings"] = false, + ["CoopDescriptions"] = true, + ["PairedPlayerDescriptions"] = false, + ["DisplayAllNearby"] = false, ["DisableOnCurse"] = true, ["DisableOnAltPath"] = true, ["DisableOnAprilFoolsChallenge"] = true, @@ -427,7 +443,7 @@ EID.DefaultConfig = { ["DisplayPillInfoOptions?"] = true, ["DisplayObstructedPillInfo"] = false, ["ShowUnidentifiedPillDescriptions"] = false, - ["DisplayGlitchedItemInfo"] = true, + ["DisplayGlitchedItemInfo"] = false, ["DisplaySacrificeInfo"] = true, ["DisplayDiceInfo"] = true, ["DisplayBagOfCrafting"] = "always", From 5923bee9b6cf1e90bc35162fd2677d83547952c9 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 00:24:49 -0500 Subject: [PATCH 06/23] Add new co-op options --- mod_config_menu.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mod_config_menu.lua b/mod_config_menu.lua index ade5bc9c..75b32b95 100644 --- a/mod_config_menu.lua +++ b/mod_config_menu.lua @@ -356,7 +356,9 @@ if MCMLoaded then -- maxDistance local distances = {1, 1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10} EID:AddScrollSetting("General", "MaxDistance", "Max Distance", distances, {label = " Grids", - infoText = "Distance to the object until descriptions are displayed."}) + infoText = "Distance to the object until descriptions are displayed"}) + EID:AddBooleanSetting("General", "DisplayAllNearby", "Display All Objects In Range", + { infoText = "Descriptions besides the closest one will be shown using the Local display mode" }) --------Reset to default MCM.AddSpace("EID", "General") @@ -383,6 +385,11 @@ if MCMLoaded then --------------------------------------------------------------------------- ---------------------------------Display----------------------------------- + EID:AddBooleanSetting("Display", "CoopDescriptions", "Co-op Player Descriptions", {repOnly = true, + infoText = "Allow Players 2, 3, and 4 to display descriptions in Repentance's True Co-op"}) + EID:AddBooleanSetting("Display", "PairedPlayerDescriptions", "Paired Player Descriptions", {repOnly = true, + infoText = "Allow paired characters like Esau and Tainted Forgotten's bone pile to display descriptions"}) + -- Simple toggles of what descriptions the user wants displayed EID:AddBooleanSetting("Display", "DisplayItemInfo", "Collectible Infos", {displayingTab = ""}) EID:AddBooleanSetting("Display", "DisplayTrinketInfo", "Trinket Infos") @@ -406,7 +413,7 @@ if MCMLoaded then EID:AddBooleanSetting("Display", "SpindownDiceDisplayID", "Display IDs", {repOnly = true, infoText = "Display IDs for Spindown Dice results"}) -- Display names for Spindown Dice results - EID:AddBooleanSetting("Display", "SpindownDiceDisplayName", "Display names", + EID:AddBooleanSetting("Display", "SpindownDiceDisplayName", "Display Names", {repOnly = true, infoText = "Display names for Spindown Dice results"}) -- Spindown Dice skip locked items EID:AddBooleanSetting("Display", "SpindownDiceSkipLocked", "Skip Locked Items", From c382c23f5079867727c469713376403f73918bd6 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 00:25:14 -0500 Subject: [PATCH 07/23] Colored arrows! --- resources/gfx/eid_transform_icons.anm2 | 30 +++++++++++++++++++++++++ resources/gfx/eid_transform_icons.png | Bin 30908 -> 7075 bytes 2 files changed, 30 insertions(+) diff --git a/resources/gfx/eid_transform_icons.anm2 b/resources/gfx/eid_transform_icons.anm2 index 4c435e58..3e312250 100644 --- a/resources/gfx/eid_transform_icons.anm2 +++ b/resources/gfx/eid_transform_icons.anm2 @@ -6,6 +6,9 @@ + + + @@ -217,6 +220,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/gfx/eid_transform_icons.png b/resources/gfx/eid_transform_icons.png index 4de1595177c40066ed32716d54686ab293e17eea..e6282c12628f26a111d900bbbaa4670d7fd30a8d 100644 GIT binary patch delta 4371 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 N07*p#PDHLkV1l9Ns7C+* literal 30908 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 From 6cc039413362a55b563abce5dbe2644a4e614c5d Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 03:36:44 -0500 Subject: [PATCH 08/23] Fix AB+ indicator colors --- main.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/main.lua b/main.lua index dba8e23e..f57a449b 100644 --- a/main.lua +++ b/main.lua @@ -815,15 +815,19 @@ function EID:renderIndicator(entity, playerNum) if EID.isMultiplayer then colorMult = playerRGB[playerNum] end if EID.Config["Indicator"] == "blink" then local c = 255 - math.floor(255 * ((entity.FrameCount % 40) / 40)) - sprite.Color = Color(1, 1, 1, 1, c/repDiv * colorMult[1], c/repDiv * colorMult[2], c/repDiv * colorMult[3]) + local r, g, b = math.floor(c*colorMult[1]), math.floor(c*colorMult[2]), math.floor(c*colorMult[3]) + sprite.Color = Color(1, 1, 1, 1, r/repDiv, g/repDiv, b/repDiv) EID:renderEntity(entity, sprite, entityPos) sprite.Color = Color(1, 1, 1, 1, 0, 0, 0) else - if EID.Config["Indicator"] == "border" then + if EID.Config["Indicator"] == "highlight" then local c = 255 - math.floor(255 * ((entity.FrameCount % 40) / 40)) - sprite.Color = Color(1, 1, 1, 1, c/repDiv * colorMult[1], c/repDiv * colorMult[2], c/repDiv * colorMult[3]) - elseif EID.Config["Indicator"] == "highlight" then - sprite.Color = Color(1, 1, 1, 1, 255/repDiv* colorMult[1], 255/repDiv* colorMult[2], 255/repDiv* colorMult[3]) + local r, g, b = math.floor(c*colorMult[1]), math.floor(c*colorMult[2]), math.floor(c*colorMult[3]) + sprite.Color = Color(1, 1, 1, 1, r/repDiv, g/repDiv, b/repDiv) + elseif EID.Config["Indicator"] == "border" then + local c = 255 + local r, g, b = math.floor(c*colorMult[1]), math.floor(c*colorMult[2]), math.floor(c*colorMult[3]) + sprite.Color = Color(1, 1, 1, 1, r/repDiv, g/repDiv, b/repDiv) end EID:renderEntity(entity, sprite, entityPos + Vector(0, 1)) EID:renderEntity(entity, sprite, entityPos + Vector(0, -1)) From 030ae4154989c0036f430cc04d02861dd4a0cf19 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 03:37:11 -0500 Subject: [PATCH 09/23] Co-op descriptions works in AB+! --- eid_config.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eid_config.lua b/eid_config.lua index 2e1f591d..e1a15da7 100644 --- a/eid_config.lua +++ b/eid_config.lua @@ -86,10 +86,10 @@ EID.UserConfig = { ---------- Co-op / Multiple Descriptions ----------- - -- Allow P2/P3/P4 to display descriptions when in Repentance's Co-op mode + -- Allow P2/P3/P4 to display descriptions when in Co-op mode -- Default = true ["CoopDescriptions"] = true, - -- Allow the paired player of certain characters (Esau, Tainted Forgotten's bone pile) to display descriptions + -- Allow the paired player of certain characters in Repentance (Esau, Tainted Forgotten's bone pile) to display descriptions -- Default = false ["PairedPlayerDescriptions"] = false, -- Display a description for every object within range of you, instead of just the closest From ff24a46ccef4835d189d4ad379e37b7434b616d9 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 03:37:35 -0500 Subject: [PATCH 10/23] Co-op descriptions works in AB+! --- mod_config_menu.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod_config_menu.lua b/mod_config_menu.lua index 75b32b95..3ba66532 100644 --- a/mod_config_menu.lua +++ b/mod_config_menu.lua @@ -385,8 +385,8 @@ if MCMLoaded then --------------------------------------------------------------------------- ---------------------------------Display----------------------------------- - EID:AddBooleanSetting("Display", "CoopDescriptions", "Co-op Player Descriptions", {repOnly = true, - infoText = "Allow Players 2, 3, and 4 to display descriptions in Repentance's True Co-op"}) + EID:AddBooleanSetting("Display", "CoopDescriptions", "Co-op Player Descriptions", + {infoText = "Allow Players 2, 3, and 4 to display descriptions in Co-op"}) EID:AddBooleanSetting("Display", "PairedPlayerDescriptions", "Paired Player Descriptions", {repOnly = true, infoText = "Allow paired characters like Esau and Tainted Forgotten's bone pile to display descriptions"}) From 2076885e6582b55f4f3e1471116026bd372e51d8 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Wed, 2 Mar 2022 04:41:31 -0500 Subject: [PATCH 11/23] Add a space --- mod_config_menu.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/mod_config_menu.lua b/mod_config_menu.lua index 3ba66532..95ea9322 100644 --- a/mod_config_menu.lua +++ b/mod_config_menu.lua @@ -389,6 +389,7 @@ if MCMLoaded then {infoText = "Allow Players 2, 3, and 4 to display descriptions in Co-op"}) EID:AddBooleanSetting("Display", "PairedPlayerDescriptions", "Paired Player Descriptions", {repOnly = true, infoText = "Allow paired characters like Esau and Tainted Forgotten's bone pile to display descriptions"}) + MCM.AddSpace("EID", "Display") -- Simple toggles of what descriptions the user wants displayed EID:AddBooleanSetting("Display", "DisplayItemInfo", "Collectible Infos", {displayingTab = ""}) From 8370baf02dc2eff6309b43611e7aa18bdf724d79 Mon Sep 17 00:00:00 2001 From: squigz Date: Thu, 3 Mar 2022 08:26:31 -0500 Subject: [PATCH 12/23] secs > seconds For consistency! --- descriptions/ab+/en_us.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/descriptions/ab+/en_us.lua b/descriptions/ab+/en_us.lua index 22c77fc8..73c9713e 100644 --- a/descriptions/ab+/en_us.lua +++ b/descriptions/ab+/en_us.lua @@ -58,9 +58,9 @@ EID.descriptions[languageCode].collectibles={ {"36", "The Poop", "Spawns one poop and knocks back enemies#Can be placed next to a pit and destroyed with a bomb to make a bridge"}, {"37", "Mr. Boom", "Drops a large bomb below the player which deals 110 damage"}, {"38", "Tammy's Head", "Fires 10 tears in a circle around Isaac"}, - {"39", "Mom's Bra", "Freeze all enemies in the current room for 4 secs"}, + {"39", "Mom's Bra", "Freeze all enemies in the current room for 4 seconds"}, {"40", "Kamikaze!", "Causes an explosion at the players location#Deals 40 damage"}, - {"41", "Mom's Pad", "Fears all enemies in the current room for 5 secs"}, + {"41", "Mom's Pad", "Fears all enemies in the current room for 5 seconds"}, {"42", "Bob's Rotten Head", "Throwable poison bomb"}, {"43", "", ""}, {"44", "Teleport!", "Teleports Isaac into a random room except I AM ERROR rooms"}, @@ -77,7 +77,7 @@ EID.descriptions[languageCode].collectibles={ {"55", "Mom's Eye", "50% chance to shoot a tear backwards#100% chance at 2 Luck"}, {"56", "Lemon Mishap", "Drop a pool of lemonade#Deals 66 contact damage per second"}, {"57", "Distant Admiration", "Mid-range fly orbital#Deals 75 contact damage per second"}, - {"58", "Book of Shadows", "Invincibility for 10 secs"}, + {"58", "Book of Shadows", "Invincibility for 10 seconds"}, {"59", "", ""}, {"60", "The Ladder", "Walk over small gaps"}, {"61", "", ""}, @@ -85,7 +85,7 @@ EID.descriptions[languageCode].collectibles={ {"63", "The Battery", "Active items can now be charged up twice"}, {"64", "Steam Sale", "-50% on shop items#Getting this item two times will make everything free"}, {"65", "Anarchist Cookbook", "Spawn 6 troll bombs near the center of the room"}, - {"66", "The Hourglass", "Slow down enemies for 8 secs"}, + {"66", "The Hourglass", "Slow down enemies for 8 seconds"}, {"67", "Sister Maggy", "Normal tear familiar#Deals 3.5 damage per tear"}, {"68", "Technology", "Tears are now lasers"}, {"69", "Chocolate Milk", "Chargeable tears#↑ Up to 400% Damage#↓ Tears down"}, From 6ecf094755436b9f6a302a7a0598e2cc65747a37 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sat, 5 Mar 2022 05:08:26 -0500 Subject: [PATCH 13/23] Add refresh rate option --- eid_api.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/eid_api.lua b/eid_api.lua index a6442814..e61c055b 100644 --- a/eid_api.lua +++ b/eid_api.lua @@ -962,7 +962,7 @@ function EID:PlayersHaveCollectible(collectibleID) for i = 0, game:GetNumPlayers() - 1 do local player = Isaac.GetPlayer(i) if player:HasCollectible(collectibleID) then - return true + return true, player end end return false @@ -1410,3 +1410,10 @@ function EID:removeIgnoredEntity(entityType, entityVariant, entitySubType) EID.IgnoredEntities[entityType.."."..entityVariant.."."..entitySubType] = nil end end + +function EID:RefreshThisFrame() + if EID.GameRenderCount % (60 / EID.Config["RefreshRate"]) == 0 then + return true + end + return false +end From 53305073c12b1eecd0734e2659bae9fe9beb6738 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sat, 5 Mar 2022 05:10:45 -0500 Subject: [PATCH 14/23] Refresh rate option --- eid_bagofcrafting.lua | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/eid_bagofcrafting.lua b/eid_bagofcrafting.lua index 4c8792ec..6deee238 100644 --- a/eid_bagofcrafting.lua +++ b/eid_bagofcrafting.lua @@ -858,7 +858,8 @@ local function RecipeCrunchCoroutine() EID.RefreshBagTextbox = true end -function EID:handleBagOfCraftingRendering() +-- Called 60 times a second so we can read input properly +function EID:handleBagOfCraftingUpdating() -- reset our calculated recipes when the game seed changes local curSeed = game:GetSeeds():GetStartSeed() if (curSeed ~= lastSeedUsed) then @@ -875,10 +876,6 @@ function EID:handleBagOfCraftingRendering() 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 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.bagPlayer.ControllerIndex) then @@ -889,22 +886,24 @@ function EID:handleBagOfCraftingRendering() showCraftingResult = not showCraftingResult end end - +end + +-- Called when needed based on EID.Config["RefreshRate"] +function EID:handleBagOfCraftingRendering() -- Determine if we should display anything at all if ((EID.isHidden or craftingIsHidden) and EID.MCMCompat_isDisplayingEIDTab ~= "Crafting") or game.Challenge == Challenge.CHALLENGE_CANTRIPPED then - return + return false + elseif not EID:RefreshThisFrame() then + return false elseif EID.Config["BagOfCraftingHideInBattle"] then if Isaac.CountBosses() > 0 or Isaac.CountEnemies() > 0 then - return + return false end - end - if EID.Config["DisplayBagOfCrafting"] == "never" then + elseif EID.Config["DisplayBagOfCrafting"] == "never" then return false - end - if EID.Config["DisplayBagOfCrafting"] == "hold" and not string.find(EID.bagPlayer:GetSprite():GetAnimation(), "PickupWalk") then + elseif EID.Config["DisplayBagOfCrafting"] == "hold" and not string.find(EID.bagPlayer:GetSprite():GetAnimation(), "PickupWalk") then return false - end - if game:GetRoom():GetFrameCount() < 2 then + elseif game:GetRoom():GetFrameCount() < 2 then return false end @@ -981,6 +980,10 @@ function EID:handleBagOfCraftingRendering() local customDescObj = EID:getDescriptionObj(5, 100, 710) customDescObj.Description = "" + -- 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 + -- Pickups Only / No Recipes Mode display if EID.Config["BagOfCraftingDisplayMode"] == "Pickups Only" then EID:appendToDescription(customDescObj, getHotkeyString()) From 060e64ebb14d6411d49cbe17e1578b704f5a08f3 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sat, 5 Mar 2022 05:11:06 -0500 Subject: [PATCH 15/23] Add refresh rate option --- eid_config.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/eid_config.lua b/eid_config.lua index e1a15da7..8e2fdbf1 100644 --- a/eid_config.lua +++ b/eid_config.lua @@ -76,6 +76,11 @@ EID.UserConfig = { -- Hide the descriptions when in battle -- Default = false ["HideInBattle"] = false, + -- How many times a second that EID checks for what objects are close to the player + -- Lowering this number can help if you experience frame drops while descriptions are on-screen + -- Please use a factor of 60! (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60) + -- Default: 30 + ["RefreshRate"] = 30, -- Toggle obstruction based hiding, when the player has flight -- Default = true ["DisableObstructionOnFlight"] = true, @@ -411,6 +416,7 @@ EID.DefaultConfig = { ["HideButton"] = -1, ["InitiallyHidden"] = false, ["HideInBattle"] = false, + ["RefreshRate"] = 30, ["DisableObstructionOnFlight"] = true, ["DisableStartOfRunWarnings"] = false, ["CoopDescriptions"] = true, From 581d3e8cf4da6bfdae4b944a9faed56616b37de2 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sat, 5 Mar 2022 05:14:27 -0500 Subject: [PATCH 16/23] Refresh rate option and co-op fixes --- main.lua | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/main.lua b/main.lua index f57a449b..02563eb1 100644 --- a/main.lua +++ b/main.lua @@ -444,10 +444,12 @@ EID.previousDescs = {} EID.CachingDescription = false EID.CachedIcons = {} EID.CachedStrings = {} +EID.CachedIndicators = {} EID.CachedRenderPoses = {} EID.descriptionsToPrint = {} EID.entitiesToPrint = {} + function EID:addDescriptionToPrint(desc) if desc.Entity and EID.entitiesToPrint[GetPtrHash(desc.Entity)] then return end table.insert(EID.descriptionsToPrint, desc) @@ -474,8 +476,11 @@ function EID:printDescriptions(useCached) end -- Print our cached descriptions - for i,newDesc in ipairs(EID.descriptionsToPrint) do - EID:printDescription(newDesc, i) + for i,indicator in ipairs(EID.CachedIndicators) do + EID:renderIndicator(indicator[1], indicator[2]) + end + for i,oldDesc in ipairs(EID.previousDescs) do + EID:printDescription(oldDesc, i) end end @@ -982,7 +987,7 @@ function EID:onGameUpdate() end -- Remove Crane Game item data if it's giving the prize out - if REPENTANCE and EID.GameUpdateCount % 15 == 0 then + if REPENTANCE and EID.GameUpdateCount % 10 == 0 then for _, crane in ipairs(Isaac.FindByType(6, 16, -1, true, false)) do if EID.CraneItemType[tostring(crane.InitSeed)] then if crane:GetSprite():IsPlaying("Prize") then @@ -1129,6 +1134,13 @@ local function onRender(t) EID.MCM_OptionChanged = false EID:resumeCoroutines() ArrowSprite:Update() + if REPENTANCE then + local hasBag, bagPlayer = EID:PlayersHaveCollectible(710) + if hasBag then + EID.bagPlayer = bagPlayer + EID:handleBagOfCraftingUpdating() + end + end EID.isDisplaying = false EID:setPlayer() @@ -1170,6 +1182,14 @@ local function onRender(t) return end + -- This is not a frame we should check for new descriptions; just print our cached ones + if not EID:RefreshThisFrame() then + EID:printDescriptions(true) + return + end + + EID.CachedIndicators = {} + if EID.Config["EnableMouseControls"] then local hudDescription = EID:handleHoverHUD() if hudDescription then @@ -1194,7 +1214,6 @@ local function onRender(t) -- Check if this player has the Bag of Crafting local craftingSuccess = false if REPENTANCE and player:HasCollectible(710) then - EID.bagPlayer = player craftingSuccess = EID:handleBagOfCraftingRendering() if craftingSuccess then displayedDesc = true @@ -1202,8 +1221,11 @@ local function onRender(t) end -- If we're in the Crafting options tab, the only rendering we want to happen is the Bag of Crafting preview if EID.MCMCompat_isDisplayingEIDTab == "Crafting" then - if craftingSuccess then EID:printDescription(descsToDisplay[#descsToDisplay]) end - return + if craftingSuccess then + EID:printDescription(EID.descriptionsToPrint[#EID.descriptionsToPrint]) + return + end + if i == #playerSearch then return end end if not displayedDesc or EID.Config["DisplayAllNearby"] then @@ -1243,11 +1265,10 @@ local function onRender(t) -- we only want to describe the closest entity inRangeEntities = { EID.lastDescriptionEntity } end - --inRangeEntities = { EID.lastDescriptionEntity } - table.insert(inRangeEntities, 1, EID.lastDescriptionEntity) -- Only display the indicator for the primary (closest / crafting) description if EID.lastDescriptionEntity and not displayedCrafting then EID:renderIndicator(EID.lastDescriptionEntity, EID.controllerIndexes[player.ControllerIndex]) + table.insert(EID.CachedIndicators, {EID.lastDescriptionEntity, EID.controllerIndexes[player.ControllerIndex]}) end for _,closest in ipairs(inRangeEntities) do From 2ddb700614b75ecb53a6fac8e604ff1872966037 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sat, 5 Mar 2022 05:14:40 -0500 Subject: [PATCH 17/23] Add refresh rate option --- mod_config_menu.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mod_config_menu.lua b/mod_config_menu.lua index 95ea9322..a059b268 100644 --- a/mod_config_menu.lua +++ b/mod_config_menu.lua @@ -385,6 +385,9 @@ if MCMLoaded then --------------------------------------------------------------------------- ---------------------------------Display----------------------------------- + local refreshRateSteps = {1, 2, 3, 4, 6, 10, 12, 15, 20, 30, 60} + EID:AddScrollSetting("Display", "RefreshRate", "Description Refresh Rate", refreshRateSteps, + {infoText = "How many times per second that EID updates what description to show"}) EID:AddBooleanSetting("Display", "CoopDescriptions", "Co-op Player Descriptions", {infoText = "Allow Players 2, 3, and 4 to display descriptions in Co-op"}) EID:AddBooleanSetting("Display", "PairedPlayerDescriptions", "Paired Player Descriptions", {repOnly = true, From afbc51bd886cb2b06dea1784602070be42ef5929 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sun, 6 Mar 2022 04:21:23 -0500 Subject: [PATCH 18/23] Co-op mode Hold Tab support --- eid_modifiers.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/eid_modifiers.lua b/eid_modifiers.lua index 1c1659ae..d7159b08 100644 --- a/eid_modifiers.lua +++ b/eid_modifiers.lua @@ -65,6 +65,8 @@ local function TabCallback(descObj) EID.inModifierPreview = true local descEntry = EID:getDescriptionObj(5, 100, EID.TabPreviewID) EID.inModifierPreview = false + descEntry.Entity = descObj.Entity + EID.TabPreviewID = 0 return descEntry end @@ -498,9 +500,10 @@ local function EIDConditionsAB(descObj) end EID:addDescriptionModifier("EID Afterbirth+", EIDConditionsAB, nil) --- should this be done differently so that mods can add tab previews? +-- 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(descObj) - if EID.player and Input.IsActionPressed(ButtonAction.ACTION_MAP, EID.player.ControllerIndex) and not EID.inModifierPreview then return true end + if EID:PlayersActionPressed(ButtonAction.ACTION_MAP) and not EID.inModifierPreview then return true end + EID.TabPreviewID = 0 return false end From ffe3e03947d4421c516f7f3e5333f41727078303 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sun, 6 Mar 2022 04:22:17 -0500 Subject: [PATCH 19/23] add PlayersActionPressed --- eid_api.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/eid_api.lua b/eid_api.lua index e61c055b..8397a190 100644 --- a/eid_api.lua +++ b/eid_api.lua @@ -1411,9 +1411,19 @@ function EID:removeIgnoredEntity(entityType, entityVariant, entitySubType) end end +-- Returns if this is a frame we should refresh our descriptions function EID:RefreshThisFrame() if EID.GameRenderCount % (60 / EID.Config["RefreshRate"]) == 0 then return true end return false end + +-- Returns true if any player is pressing the given button (you can also specify any of the input functions) +function EID:PlayersActionPressed(button, inputFunc) + inputFunc = inputFunc or Input.IsActionPressed + for k,_ in pairs(EID.controllerIndexes) do + if inputFunc(button, k) then return true end + end + return false +end From 1e323bbb86d02278f078fb3fab5a413ee6746ea8 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sun, 6 Mar 2022 04:24:25 -0500 Subject: [PATCH 20/23] Improve speed of single player setPlayer --- main.lua | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/main.lua b/main.lua index 02563eb1..ef373b23 100644 --- a/main.lua +++ b/main.lua @@ -920,13 +920,28 @@ function EID:handleHoverHUD() end function EID:setPlayer() + local numPlayers = game:GetNumPlayers() + -- Old simple setPlayer, to reduce runtime in single player + if numPlayers == 1 or not EID.Config["CoopDescriptions"] then + local p = Isaac.GetPlayer(0) + if REPENTANCE and p:GetPlayerType() == PlayerType.PLAYER_THEFORGOTTEN_B then + EID.player = p:GetOtherTwin() or p + else + EID.player = p + end + EID.players = { EID.player, REPENTANCE and EID.player:GetOtherTwin() } + EID.coopMainPlayers = { EID.player } + EID.coopAllPlayers = EID.players + EID.controllerIndexes[p.ControllerIndex] = 1 + return + end + -- Get our primary player, our list of all players, and a list of the primary player for each controller - -- make this run less than 60 times a second? EID.coopAllPlayers = {} -- all player characters in order EID.coopMainPlayers = {} -- the primary player character from each controller EID.controllerIndexes = {} -- simple table to map each controller index to P1/P2/P3/P4 (since index 0 could be P2) local currentPlayerNum = 1 - for i = 0, game:GetNumPlayers() - 1 do + for i = 0, numPlayers - 1 do local player = Isaac.GetPlayer(i) -- Don't include player entities with a parent (Strawman / Soul of Jacob and Esau) if player.Parent == nil then @@ -1124,7 +1139,6 @@ local searchPartitions = EntityPartition.FAMILIAR + EntityPartition.ENEMY + Enti EID.lastDescriptionEntity = nil EID.lastDist = 0 EID.OptionChanged = false -local currentPlayer = 1 EID.bagPlayer = nil local function onRender(t) @@ -1134,6 +1148,7 @@ local function onRender(t) EID.MCM_OptionChanged = false EID:resumeCoroutines() ArrowSprite:Update() + EID:setPlayer() if REPENTANCE then local hasBag, bagPlayer = EID:PlayersHaveCollectible(710) if hasBag then @@ -1143,8 +1158,6 @@ local function onRender(t) end EID.isDisplaying = false - EID:setPlayer() - EID.bagPlayer = EID.player EID.descriptionsToPrint = {} EID.entitiesToPrint = {} alwaysUseLocalMode = false @@ -1243,7 +1256,7 @@ local function onRender(t) for _, entitySearch in ipairs(searchGroups) do for i, entity in ipairs(entitySearch) do - if EID:hasDescription(entity) and entity.FrameCount > 0 then + if EID:hasDescription(entity) and entity.FrameCount > 0 and not EID.entitiesToPrint[GetPtrHash(entity)] then table.insert(inRangeEntities, entity) local diff = entity.Position:__sub(sourcePos) -- break ties with the render offset (for Mega Chest double collectibles) From 45bfa51c2d8a4ec7e0d4d98d40a196784291e9d2 Mon Sep 17 00:00:00 2001 From: Buurazu Date: Sun, 6 Mar 2022 04:30:09 -0500 Subject: [PATCH 21/23] prevent possible indicator error --- main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.lua b/main.lua index ef373b23..48353c44 100644 --- a/main.lua +++ b/main.lua @@ -1280,7 +1280,7 @@ local function onRender(t) end -- Only display the indicator for the primary (closest / crafting) description if EID.lastDescriptionEntity and not displayedCrafting then - EID:renderIndicator(EID.lastDescriptionEntity, EID.controllerIndexes[player.ControllerIndex]) + EID:renderIndicator(EID.lastDescriptionEntity, EID.controllerIndexes[player.ControllerIndex] or 1) table.insert(EID.CachedIndicators, {EID.lastDescriptionEntity, EID.controllerIndexes[player.ControllerIndex]}) end From 639db4b27ae61c0b08e2d2954a8908010fa764de Mon Sep 17 00:00:00 2001 From: Wofsauge Date: Sun, 6 Mar 2022 16:07:48 +0100 Subject: [PATCH 22/23] Updated Workshop Mod --- main.lua | 2 +- metadata.xml | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/main.lua b/main.lua index 48353c44..cc38e84b 100644 --- a/main.lua +++ b/main.lua @@ -9,7 +9,7 @@ local game = Game() require("eid_config") EID.Config = EID.UserConfig EID.Config.Version = "3.2" -- note: changing this will reset everyone's settings to default! -EID.ModVersion = "4.21" +EID.ModVersion = "4.22" EID.DefaultConfig.Version = EID.Config.Version EID.isHidden = false EID.player = nil -- The primary Player Entity of Player 1 diff --git a/metadata.xml b/metadata.xml index ccbda6da..eaae37a3 100644 --- a/metadata.xml +++ b/metadata.xml @@ -56,7 +56,6 @@ This mod displays detailed information about all items, trinkets, cards, runes a [th]KOREAN[/th] [th]CHINESE[/th] [th]JAPANESE[/th] -[th]CZECH[/th] [/tr] [tr] [td]by Jake[/td] @@ -66,6 +65,11 @@ This mod displays detailed information about all items, trinkets, cards, runes a [td]by Blackcreamtea, 미카[/td] [td]by Xheepey87, frto027[/td] [td]by prefab[/td] +[/tr] +[tr] +[th]CZECH[/th] +[/tr] +[tr] [td]by domcizzz[/td] [/tr] [/table] @@ -109,7 +113,7 @@ https://moddingofisaac.com/mod/1079/external-item-descriptions - prefab for the incredible [b]Japanese[/b] language pack - domcizzz for the cool [b]Czech[/b] language pack - [url=https://steamcommunity.com/id/whytefang/]adc[/url] for creating an awesome new Languagepack and providing ideas for new functionalities - 4.21 + 4.22 Public From 22defd36d658c69e13f202d2ec4aebfa7993416d Mon Sep 17 00:00:00 2001 From: Wofsauge Date: Sun, 6 Mar 2022 22:38:59 +0100 Subject: [PATCH 23/23] Added overhauled icons by biobak --- resources/gfx/eid_inline_icons.png | Bin 16693 -> 17525 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/gfx/eid_inline_icons.png b/resources/gfx/eid_inline_icons.png index bca433ec1e26c18f19749e0dc3c66f8439c899f6..b964ac34b3a439712f0712c98450743c9a5b86d4 100644 GIT binary patch literal 17525 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 be}1cwYWt5|8$JWi5n=Fj^>bP0l+XkKzUb7A literal 16693 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