From 4b1ebfc1c885b93318df72eca58bf61f48185800 Mon Sep 17 00:00:00 2001 From: Wofsauge Date: Sat, 19 Jun 2021 23:47:19 +0200 Subject: [PATCH] Added feature to get descriptions of HUD elements --- eid_api.lua | 40 +++++++++ eid_config.lua | 12 +++ eid_data.lua | 54 ++++++++++- eid_debugging.lua | 2 + main.lua | 44 ++++++++- mod_config_menu.lua | 119 +++++++++++++++++++------ resources/gfx/eid_transform_icons.anm2 | 27 +++++- resources/gfx/eid_transform_icons.png | Bin 30670 -> 30908 bytes 8 files changed, 266 insertions(+), 32 deletions(-) diff --git a/eid_api.lua b/eid_api.lua index 4228f18d..6c5451df 100644 --- a/eid_api.lua +++ b/eid_api.lua @@ -800,6 +800,46 @@ function EID:tableToCraftingIconsMerged(craftTable) end +function EID:handleHUDElement(hudElement) + local alteredHudElement = {} + for k,v in pairs(hudElement) do + alteredHudElement[k] = v + end + local screenSize = EID:GetScreenSize() + for _,v in ipairs(hudElement.anchors) do + if v == "TOP" then + if EID.MCMLoaded then + alteredHudElement.y = hudElement.y + EID.MCMHudOffset * 2 + end + elseif v == "LEFT" then + if EID.MCMLoaded then + alteredHudElement.x = hudElement.x + EID.MCMHudOffset * 2 + end + elseif v == "BOTTOM" then + alteredHudElement.y = screenSize.Y*2 - alteredHudElement.y - alteredHudElement.height + if EID.MCMLoaded then + alteredHudElement.y = alteredHudElement.y - EID.MCMHudOffset * 2 + end + elseif v == "RIGHT" then + alteredHudElement.x = screenSize.X*2 - alteredHudElement.x - alteredHudElement.width + if EID.MCMLoaded then + alteredHudElement.x = alteredHudElement.x - EID.MCMHudOffset * 2 + end + end + end + return alteredHudElement +end + +function EID:GetScreenSize() + local room = game:GetRoom() + local pos = room:WorldToScreenPosition(Vector(0,0)) - room:GetRenderScrollOffset() - Game().ScreenShakeOffset + + local rx = pos.X + 60 * 26 / 40 + local ry = pos.Y + 140 * (26 / 40) + + return Vector(rx*2 + 13*26, ry*2 + 7*26) +end + -- Creates a copy of a KColor object. This prevents overwriting existing function EID:copyKColor(colorObj) return KColor(colorObj.Red, colorObj.Green, colorObj.Blue, colorObj.Alpha) diff --git a/eid_config.lua b/eid_config.lua index 15f147ce..50aa28ae 100644 --- a/eid_config.lua +++ b/eid_config.lua @@ -158,6 +158,16 @@ EID.UserConfig = { -- Default = 7 ["BagOfCraftingResults"] = 7, + ---------Mouse Controls----------- + + -- Toggles HUD descriptions based on the mouse position + -- Default = "false" + ["EnableMouseControls"] = "false", + + -- Toggles the custom mouse cursor + -- Default = "true" + ["ShowCursor"] = "true", + -------------MISC--------------- -- Changes the number of Results shown for the additional Spindown dice description @@ -236,6 +246,8 @@ EID.DefaultConfig = { ["DisplayBagOfCrafting"] = "always", ["BagOfCraftingResults"] = 7, ["SpindownDiceResults"] = 3, + ["UseMouseControls"] = "false", + ["ShowCursor"] = "true", ["ErrorMessage"] = "[Effect not defined]", ["TextColor"] = "ColorEIDText", ["ErrorColor"] = "ColorEIDError", diff --git a/eid_data.lua b/eid_data.lua index f5d4043e..5f62ae35 100644 --- a/eid_data.lua +++ b/eid_data.lua @@ -21,6 +21,57 @@ EID.SpindownDiceSkipIDs = { [718]=true } +EID.HUDElements = { + ["Active1"] = {x = 20,y = 5, width = 65, height = 65, anchors={"TOP","LEFT"}, descriptionObj = function() + local id = EID.player:GetActiveItem(ActiveSlot.SLOT_PRIMARY) + if id == 0 then return nil end + return EID:getDescriptionObj(5, 100, id) + end}, + ["Active2"] = {x = -5,y = 0, width = 35, height = 35, anchors={"TOP","LEFT"}, descriptionObj = function() + local id = EID.player:GetActiveItem(ActiveSlot.SLOT_SECONDARY) + if id == 0 then return nil end + return EID:getDescriptionObj(5, 100, id) + end}, + ["Trinket1"] = {x = 50,y = 0, width = 55, height = 65, anchors={"BOTTOM","LEFT"}, descriptionObj = function() + local id = EID.player:GetTrinket(0) + if id == 0 then return nil end + return EID:getDescriptionObj(5, 350, id) + end}, + ["Trinket2"] = {x = 0,y = 40, width = 55, height = 65, anchors={"BOTTOM","LEFT"}, descriptionObj = function() + local id = EID.player:GetTrinket(1) + if id == 0 then return nil end + return EID:getDescriptionObj(5, 350, id) + end}, + ["Pocket1"] = {x = 0,y = 5, width = 250, height = 40, anchors={"BOTTOM","RIGHT"}, descriptionObj = function() + local id = EID.player:GetCard(0) + if id ~= 0 then return EID:getDescriptionObj(5, 300, id) end + id = EID.player:GetPill(0) + if id ~= 0 then return EID:getDescriptionObj(5, 70, id) end + return nil + end}, + ["Pocket2"] = {x = 0,y = 40, width = 30, height = 30, anchors={"BOTTOM","RIGHT"}, descriptionObj = function() + local id = EID.player:GetCard(1) + if id ~= 0 then return EID:getDescriptionObj(5, 300, id) end + id = EID.player:GetPill(1) + if id ~= 0 then return EID:getDescriptionObj(5, 70, id) end + return nil + end}, + ["Pocket3"] = {x = 0,y = 70, width = 30, height = 25, anchors={"BOTTOM","RIGHT"}, descriptionObj = function() + local id = EID.player:GetCard(2) + if id ~= 0 then return EID:getDescriptionObj(5, 300, id) end + id = EID.player:GetPill(2) + if id ~= 0 then return EID:getDescriptionObj(5, 70, id) end + return nil + end}, + ["Pocket4"] = {x = 0,y = 95, width = 30, height = 20, anchors={"BOTTOM","RIGHT"}, descriptionObj = function() + local id = EID.player:GetCard(3) + if id ~= 0 then return EID:getDescriptionObj(5, 300, id) end + id = EID.player:GetPill(3) + if id ~= 0 then return EID:getDescriptionObj(5, 70, id) end + return nil + end} +} + -- Specific strings that will be replaced with something else. This is used to convert Shortcuts into internal markup. EID.TextReplacementPairs = { {"!!!", "{{Warning}}"}, -- Turn 3 Exclamations into Warning @@ -35,6 +86,7 @@ EID.TextReplacementPairs = { {"\8\189", "{{Bomb}}"}, -- Legacy BOMB {"{{Hashtag}}", "ǂ"}, -- Hashtag {"{{CR}}", "{{ColorReset}}"}, -- Shortcut for Color Resetting + {"{{EthernalHeart}}", "{{EternalHeart}}"} -- fix spelling error } --Format: [SHORTCUT]= {Animationname, Frame, Width, Height, LeftOffset [Default: -1], TopOffset [Default: 0], SpriteObject [Default: EID.InlineIconSprite]} @@ -61,7 +113,7 @@ EID.InlineIcons = { -- Hearts ["Heart"] = {"hearts", 0, 9, 9}, ["HalfHeart"] = {"hearts", 1, 9, 9}, - ["EthernalHeart"] = {"hearts", 2, 9, 9}, + ["EternalHeart"] = {"hearts", 2, 9, 9}, ["EmptyHeart"] = {"hearts", 3, 9, 9}, ["BlendedHeart"] = {"hearts", 4, 9, 9}, ["BoneHeart"] = {"hearts", 5, 9, 9}, diff --git a/eid_debugging.lua b/eid_debugging.lua index 6deb2ef2..684403ae 100644 --- a/eid_debugging.lua +++ b/eid_debugging.lua @@ -102,6 +102,8 @@ EID:addColor("ColorBlackBlink", nil, function(color) local function onDebugRender(t) + EID:renderHUDLocationIndicators() + for i,v in ipairs(Isaac.FindByType(5,10,3,true,false)) do if v:GetData()["EID_Description"] == nil then v:GetData()["EID_Description"] = "Test specific description#Init seed: ".. v.InitSeed diff --git a/main.lua b/main.lua index 658dc404..f110098c 100644 --- a/main.lua +++ b/main.lua @@ -45,6 +45,10 @@ local ArrowSprite = Sprite() ArrowSprite:Load("gfx/eid_transform_icons.anm2", true) ArrowSprite:Play("Arrow", false) +local hudBBSprite = Sprite() +hudBBSprite:Load("gfx/eid_transform_icons.anm2", true) +hudBBSprite:Play("boundingBox") + ------- Load all modules and other stuff ------ require("mod_config_menu") @@ -403,6 +407,33 @@ function EID:renderIndicator(entity) end end +function EID:renderHUDLocationIndicators() + local mousePos = Input.GetMousePosition(false) + Isaac.RenderScaledText("Mouse pos X:"..mousePos.X.." Y:"..mousePos.Y, 100, 10, 0.5, 0.5, 1 ,1 ,1 ,1 ) + Isaac.RenderScaledText("HUD Adjustment Preview!", 200, 10, 1, 1, 1 ,0.25 ,0.25 ,1 ) + for k, v in pairs(EID.HUDElements) do + local hudElement = EID:handleHUDElement(v) + hudBBSprite.Scale = Vector(hudElement.width/2, hudElement.height/2) + hudBBSprite.Color = Color(1, 1, 1, EID.Config["Transparency"], 0, 0, 0) + hudBBSprite:Render(Vector(hudElement.x/2, hudElement.y/2), nullVector, nullVector) + Isaac.RenderScaledText(k, hudElement.x/2, hudElement.y/2, 0.5, 0.5, 1, 1, 1 ,1) + end +end + +function EID:handleHoverHUD() + local mousePos = Input.GetMousePosition(false) + if EID.Config["ShowCursor"] then + EID.IconSprite:Play("Cursor") + EID:renderIcon(EID.IconSprite, mousePos.X/2, mousePos.Y/2) + end + for k, v in pairs(EID.HUDElements) do + local hudElement = EID:handleHUDElement(v) + if hudElement.x <= mousePos.X and (hudElement.x + hudElement.width) >= mousePos.X and hudElement.y <= mousePos.Y and (hudElement.y + hudElement.height) >= mousePos.Y then + return hudElement.descriptionObj() or nil + end + end + return nil +end --------------------------------------------------------------------------- ---------------------------On Update Function------------------------------ @@ -502,8 +533,17 @@ local function onRender(t) EID.lastDescriptionEntity = nil EID.lastDist = 10000 local searchGroups = {} + local sourcePos = EID.player.Position - table.insert(searchGroups, Isaac.FindInRadius(EID.player.Position, tonumber(EID.Config["MaxDistance"])*40, searchPartitions)) + if EID.Config["EnableMouseControls"] then + local hudDescription = EID:handleHoverHUD() + if hudDescription then + EID:printDescription(hudDescription) + return + 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)) end @@ -511,7 +551,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 - local diff = entity.Position:__sub(EID.player.Position) + local diff = entity.Position:__sub(sourcePos) if diff:Length() < EID.lastDist then EID.lastDescriptionEntity = entity EID.lastDist = diff:Length() diff --git a/mod_config_menu.lua b/mod_config_menu.lua index 5e7ddb82..7bc64495 100644 --- a/mod_config_menu.lua +++ b/mod_config_menu.lua @@ -1,42 +1,57 @@ -- MOD CONFIG MENU Compatibility local MCMLoaded, MCM = pcall(require, "scripts.modconfig") -EID.MCMCompat_isDisplayingEIDTab = false +EID.MCMCompat_isDisplayingEIDTab = "" local MCMCompat_isDisplayingDummyMCMObj = false local MCMCompat_oldPermanentObj = false EID.MCMLoaded = MCMLoaded local colorNameArray = {} +EID.MCMHudOffset = ModConfigMenu.Config["General"].HudOffset + +local function renderDummyDesc() + MCMCompat_oldPermanentObj = EID.permanentDisplayTextObj + local demoDescObj = EID:getDescriptionObj(5, 100, 33) + demoDescObj.Name = "Demo Object Name" + demoDescObj.Transformation = "Demo Transformation" + demoDescObj.Description = "A very cool description as a demonstration of the power of EID!#\1 This is also a cool line#This line loves you {{Heart}}" + EID:displayPermanentText(demoDescObj) +end + +local function clearRenderDummyDesc() + if MCMCompat_oldPermanentObj == nil then + EID:hidePermanentText() + else + EID.permanentDisplayTextObj = MCMCompat_oldPermanentObj + end + MCMCompat_oldPermanentObj = nil + MCMCompat_isDisplayingDummyMCMObj = false +end function EID:renderMCMDummyDescription() if MCMLoaded then - local mcmHudOffset = ModConfigMenu.Config["General"].HudOffset - if mcmHudOffset == nil and ScreenHelper then - mcmHudOffset = ScreenHelper.GetOffset() + EID.MCMHudOffset = ModConfigMenu.Config["General"].HudOffset + if EID.MCMHudOffset == nil and ScreenHelper then + EID.MCMHudOffset = ScreenHelper.GetOffset() end - EID:addTextPosModifier("MCM_HudOffset", Vector(mcmHudOffset * 2, mcmHudOffset)) - if MCM.IsVisible and EID.permanentDisplayTextObj == nil and EID.MCMCompat_isDisplayingEIDTab then - MCMCompat_oldPermanentObj = EID.permanentDisplayTextObj - local demoDescObj = EID:getDescriptionObj(5, 100, 33) - demoDescObj.Name = "Demo Object Name" - demoDescObj.Transformation = "Demo Transformation" - demoDescObj.Description = "A very cool description as a demonstration of the power of EID!#\1 This is also a cool line#This line loves you {{Heart}}" - EID:displayPermanentText(demoDescObj) + EID:addTextPosModifier("MCM_HudOffset", Vector(EID.MCMHudOffset * 2, EID.MCMHudOffset)) + if MCM.IsVisible and EID.MCMCompat_isDisplayingEIDTab ~= "" then + if EID.MCMCompat_isDisplayingEIDTab == "Mouse" and EID.Config["EnableMouseControls"] then + clearRenderDummyDesc() + EID:renderHUDLocationIndicators() + elseif EID.MCMCompat_isDisplayingEIDTab == "Visuals" and EID.permanentDisplayTextObj == nil then + renderDummyDesc() + end MCMCompat_isDisplayingDummyMCMObj = true EID:buildColorArray() elseif not MCM.IsVisible and MCMCompat_isDisplayingDummyMCMObj then - if MCMCompat_oldPermanentObj == nil then - EID:hidePermanentText() - else - EID.permanentDisplayTextObj = MCMCompat_oldPermanentObj - end - EID.MCMCompat_isDisplayingEIDTab = false - MCMCompat_oldPermanentObj = nil - MCMCompat_isDisplayingDummyMCMObj = false + clearRenderDummyDesc() + EID.MCMCompat_isDisplayingEIDTab = "" colorNameArray = {} end end end + function EID:buildColorArray() colorNameArray = {} for k,v in pairs(EID.InlineColors) do @@ -85,7 +100,7 @@ if MCMLoaded then Minimum = 1, Maximum = #(EID.Languages), Display = function() - EID.MCMCompat_isDisplayingEIDTab = false; + EID.MCMCompat_isDisplayingEIDTab = ""; return "Language: " .. displayLanguage[AnIndexOf(EID.Languages, EID.Config["Language"])] end, OnChange = function(currentNum) @@ -308,7 +323,7 @@ if MCMLoaded then return EID.Config["DisplayItemInfo"] end, Display = function() - EID.MCMCompat_isDisplayingEIDTab = false; + EID.MCMCompat_isDisplayingEIDTab = ""; local onOff = "False" if EID.Config["DisplayItemInfo"] then onOff = "True" @@ -644,7 +659,7 @@ if MCMLoaded then Minimum = 1, Maximum = #fontTypes, Display = function() - EID.MCMCompat_isDisplayingEIDTab = true; + EID.MCMCompat_isDisplayingEIDTab = "Visuals"; return "Font Type: " .. EID.Config["FontType"] end, OnChange = function(currentNum) @@ -824,9 +839,60 @@ if MCMLoaded then end } ) + --------------------------------------------------------------------------- + -----------------------------Mouse Controls-------------------------------- + MCM.AddText("EID", "Mouse", function() return "! THIS FEATURE IS IN EARLY DEVELOPMENT !" end) + MCM.AddSpace("EID", "Mouse") + MCM.AddText("EID", "Mouse", function() return "MCM -> General -> Hud Offset" end) + MCM.AddText("EID", "Mouse", function() return "to adjust Hud Offset" end) + MCM.AddSpace("EID", "Mouse") + -- Enable mouse controls + MCM.AddSetting( + "EID", + "Mouse", + { + Type = ModConfigMenu.OptionType.BOOLEAN, + CurrentSetting = function() + return EID.Config["EnableMouseControls"] + end, + Display = function() + EID.MCMCompat_isDisplayingEIDTab = "Mouse"; + local onOff = "False" + if EID.Config["EnableMouseControls"] then + onOff = "True" + end + return "Enable Mouse controls: " .. onOff + end, + OnChange = function(currentBool) + EID.Config["EnableMouseControls"] = currentBool + end, + Info = {"If enabled, allows to hover over certain HUD elements to get descriptions"} + } + ) + -- Enable mouse controls + MCM.AddSetting( + "EID", + "Mouse", + { + Type = ModConfigMenu.OptionType.BOOLEAN, + CurrentSetting = function() + return EID.Config["ShowCursor"] + end, + Display = function() + local onOff = "False" + if EID.Config["ShowCursor"] then + onOff = "True" + end + return "Show Cursor: " .. onOff + end, + OnChange = function(currentBool) + EID.Config["ShowCursor"] = currentBool + end, + } + ) --------------------------------------------------------------------------- - ---------------------------------Visuals----------------------------------- + ---------------------------------Colors----------------------------------- -- Text Color MCM.AddSetting( @@ -840,7 +906,7 @@ if MCMLoaded then Minimum = 0, Maximum = 1000, Display = function() - EID.MCMCompat_isDisplayingEIDTab = true; + EID.MCMCompat_isDisplayingEIDTab = "Visuals"; return "Descriptions: " .. string.gsub(EID.Config["TextColor"], "Color", "").. " ("..AnIndexOf(colorNameArray, EID.Config["TextColor"]).."/"..#colorNameArray..")" end, OnChange = function(currentNum) @@ -863,7 +929,6 @@ if MCMLoaded then Minimum = 0, Maximum = 1000, Display = function() - EID.MCMCompat_isDisplayingEIDTab = true; return "Names: " .. string.gsub(EID.Config["ItemNameColor"], "Color", "").. " ("..AnIndexOf(colorNameArray, EID.Config["ItemNameColor"]).."/"..#colorNameArray..")" end, OnChange = function(currentNum) @@ -886,7 +951,6 @@ if MCMLoaded then Minimum = 0, Maximum = 1000, Display = function() - EID.MCMCompat_isDisplayingEIDTab = true; return "Transformations: " .. string.gsub(EID.Config["TransformationColor"], "Color", "").. " ("..AnIndexOf(colorNameArray, EID.Config["TransformationColor"]).."/"..#colorNameArray..")" end, OnChange = function(currentNum) @@ -909,7 +973,6 @@ if MCMLoaded then Minimum = 0, Maximum = 1000, Display = function() - EID.MCMCompat_isDisplayingEIDTab = true; return "Errors: " .. string.gsub(EID.Config["ErrorColor"], "Color", "").. " ("..AnIndexOf(colorNameArray, EID.Config["ErrorColor"]).."/"..#colorNameArray..")" end, OnChange = function(currentNum) diff --git a/resources/gfx/eid_transform_icons.anm2 b/resources/gfx/eid_transform_icons.anm2 index e1198069..4c435e58 100644 --- a/resources/gfx/eid_transform_icons.anm2 +++ b/resources/gfx/eid_transform_icons.anm2 @@ -1,5 +1,5 @@ - + @@ -257,5 +257,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/gfx/eid_transform_icons.png b/resources/gfx/eid_transform_icons.png index 5bb548ec297c97ccba06472d4b536b23b2576859..4de1595177c40066ed32716d54686ab293e17eea 100644 GIT binary patch delta 4126 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 delta 3883 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