├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── BestiaryUI.cs ├── CraftUI.cs ├── HelpUI.cs ├── Images ├── CanCraftBackground.png ├── CanCraftExtendedBackground.png ├── FavoritedOverlay.png ├── Help │ ├── CraftIcon.png │ ├── Inventory_Back8.png │ ├── Item_2430.png │ ├── Item_531.png │ ├── Item_54.png │ ├── mpFavorited.png │ ├── sortsExplanation.psd │ ├── sortsExplanation_en-US.png │ ├── sortsExplanation_fr-FR.png │ ├── sortsExplanation_pt-BR.png │ ├── sortsExplanation_zh-Hans.png │ └── uniqueTile.png ├── SelectedOverlay.png ├── bugNet.png ├── categoryArmorSets.png ├── duplicateOff.png ├── duplicateOn.png ├── filterMod.png ├── filterModColorable.png ├── filterModColorable_ru-RU.png ├── filterMod_ru-RU.png ├── sortAZ.png ├── sortAmmo.png ├── sortAxe.png ├── sortBait.png ├── sortDamage.png ├── sortDefense.png ├── sortFish.png ├── sortFood.png ├── sortHammer.png ├── sortItemID.png ├── sortPick.png ├── sortValue.png ├── spacer.png └── uniqueTile.png ├── ItemCatalogueUI.cs ├── Localization ├── TranslationsNeeded.txt ├── UpdateLocalizationFiles.py ├── de-DE.hjson ├── en-US.hjson ├── es-ES.hjson ├── fr-FR.hjson ├── it-IT.hjson ├── pl-PL.hjson ├── pt-BR.hjson ├── ru-RU.hjson └── zh-Hans.hjson ├── LootCache.cs ├── LootUnifiedRandom.cs ├── Patches.cs ├── Properties └── launchSettings.json ├── RecipeBrowser.cs ├── RecipeBrowser.csproj ├── RecipeBrowser.sln ├── RecipeBrowserClientConfig.cs ├── RecipeBrowserGlobalItem.cs ├── RecipeBrowserPlayer.cs ├── RecipeBrowserTool.cs ├── RecipeBrowserUI.cs ├── RecipeCatalogueUI.cs ├── RecipePath.cs ├── SharedUI.cs ├── TagHandlers ├── ImageHandler.cs ├── ItemHoverFixHandler.cs ├── LinkHandler.cs └── NPCHandler.cs ├── Tool.cs ├── UIElements ├── FixedUIScrollBar.cs ├── MoreDown.png ├── MoreLeft.png ├── MoreRight.png ├── MoreUp.png ├── NewUITextBox.cs ├── PrettyUITextBox.cs ├── ScrollbarHorizontal.png ├── ScrollbarInnerHorizontal.png ├── TickOnOff.png ├── UIArmorSetCatalogueItemSlot.cs ├── UIBestiaryQueryItemSlot.cs ├── UIBottomlessPanel.cs ├── UICheckbox.cs ├── UICraftButton.cs ├── UICraftQueryItemSlot.cs ├── UICycleImage.cs ├── UIDragableElement.cs ├── UIDragablePanel.cs ├── UIGrid.cs ├── UIHorizontalGrid.cs ├── UIHorizontalScrollbar.cs ├── UIHoverImageButton.cs ├── UIHoverImageButtonMod.cs ├── UIIngredientSlot.cs ├── UIItemSlot.cs ├── UIJourneyDuplicateButton.cs ├── UIMockRecipeSlot.cs ├── UIModState.cs ├── UINPCSlot.cs ├── UIQueryItemSlot.cs ├── UIRadioButton.cs ├── UIRadioButtonGroup.cs ├── UIRecipeCatalogueQueryItemSlot.cs ├── UIRecipeInfo.cs ├── UIRecipeInfoRightAligned.cs ├── UIRecipePath.cs ├── UIRecipeProgress.cs ├── UIRecipeSlot.cs ├── UISilentImageButton.cs ├── UITabControl.cs ├── UITextSnippet.cs ├── UITileSlot.cs ├── UITrackIngredientSlot.cs ├── checkBox.png ├── checkMark.png └── closeButton.png ├── UISystem.cs ├── Utilities.cs ├── build.txt ├── description.txt ├── description_workshop.txt ├── icon.png ├── icon_workshop.png └── unused ├── ButtonMinus.png ├── ButtonPlus.png ├── Inventory_Back2.png ├── Reforge_0.png ├── Reforge_1.png ├── UISlideWindow.cs ├── URLHandler.cs ├── checkX.png ├── eyedropper.png ├── littleGreenCheckMark.png └── tileIcon.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: JavidPack 4 | patreon: jopojelly 5 | custom: "https://www.paypal.me/JavidPack" 6 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Push on Master jobs 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | automaticpublish: 9 | name: Automatic Mod Browser Publish Integration 10 | runs-on: windows-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | # Mod Browser integration 15 | - uses: JavidPack/tModLoaderPublishIntegration@v0.11.8.3 16 | with: 17 | STEAMID64: ${{secrets.STEAMID64}} 18 | MODBROWSERPASSPHRASE: ${{secrets.MODBROWSERPASSPHRASE}} 19 | MODNAME: RecipeBrowser 20 | 21 | # Upload artifact for all builds 22 | - uses: actions/upload-artifact@v2 23 | with: 24 | name: .tmod file 25 | path: ~/tModLoader/ModLoader/Mods/RecipeBrowser.tmod 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | *.user 4 | App.config 5 | .vs/ -------------------------------------------------------------------------------- /Images/CanCraftBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/CanCraftBackground.png -------------------------------------------------------------------------------- /Images/CanCraftExtendedBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/CanCraftExtendedBackground.png -------------------------------------------------------------------------------- /Images/FavoritedOverlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/FavoritedOverlay.png -------------------------------------------------------------------------------- /Images/Help/CraftIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/CraftIcon.png -------------------------------------------------------------------------------- /Images/Help/Inventory_Back8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/Inventory_Back8.png -------------------------------------------------------------------------------- /Images/Help/Item_2430.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/Item_2430.png -------------------------------------------------------------------------------- /Images/Help/Item_531.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/Item_531.png -------------------------------------------------------------------------------- /Images/Help/Item_54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/Item_54.png -------------------------------------------------------------------------------- /Images/Help/mpFavorited.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/mpFavorited.png -------------------------------------------------------------------------------- /Images/Help/sortsExplanation.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/sortsExplanation.psd -------------------------------------------------------------------------------- /Images/Help/sortsExplanation_en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/sortsExplanation_en-US.png -------------------------------------------------------------------------------- /Images/Help/sortsExplanation_fr-FR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/sortsExplanation_fr-FR.png -------------------------------------------------------------------------------- /Images/Help/sortsExplanation_pt-BR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/sortsExplanation_pt-BR.png -------------------------------------------------------------------------------- /Images/Help/sortsExplanation_zh-Hans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/sortsExplanation_zh-Hans.png -------------------------------------------------------------------------------- /Images/Help/uniqueTile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/Help/uniqueTile.png -------------------------------------------------------------------------------- /Images/SelectedOverlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/SelectedOverlay.png -------------------------------------------------------------------------------- /Images/bugNet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/bugNet.png -------------------------------------------------------------------------------- /Images/categoryArmorSets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/categoryArmorSets.png -------------------------------------------------------------------------------- /Images/duplicateOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/duplicateOff.png -------------------------------------------------------------------------------- /Images/duplicateOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/duplicateOn.png -------------------------------------------------------------------------------- /Images/filterMod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/filterMod.png -------------------------------------------------------------------------------- /Images/filterModColorable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/filterModColorable.png -------------------------------------------------------------------------------- /Images/filterModColorable_ru-RU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/filterModColorable_ru-RU.png -------------------------------------------------------------------------------- /Images/filterMod_ru-RU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/filterMod_ru-RU.png -------------------------------------------------------------------------------- /Images/sortAZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortAZ.png -------------------------------------------------------------------------------- /Images/sortAmmo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortAmmo.png -------------------------------------------------------------------------------- /Images/sortAxe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortAxe.png -------------------------------------------------------------------------------- /Images/sortBait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortBait.png -------------------------------------------------------------------------------- /Images/sortDamage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortDamage.png -------------------------------------------------------------------------------- /Images/sortDefense.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortDefense.png -------------------------------------------------------------------------------- /Images/sortFish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortFish.png -------------------------------------------------------------------------------- /Images/sortFood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortFood.png -------------------------------------------------------------------------------- /Images/sortHammer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortHammer.png -------------------------------------------------------------------------------- /Images/sortItemID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortItemID.png -------------------------------------------------------------------------------- /Images/sortPick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortPick.png -------------------------------------------------------------------------------- /Images/sortValue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/sortValue.png -------------------------------------------------------------------------------- /Images/spacer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/spacer.png -------------------------------------------------------------------------------- /Images/uniqueTile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/Images/uniqueTile.png -------------------------------------------------------------------------------- /Localization/TranslationsNeeded.txt: -------------------------------------------------------------------------------- 1 | en-US, 225/225, 100%, missing 0 2 | de-DE, 28/225, 12%, missing 197 3 | it-IT, 28/225, 12%, missing 197 4 | fr-FR, 95/225, 42%, missing 130 5 | es-ES, 49/225, 22%, missing 176 6 | ru-RU, 205/225, 91%, missing 20 7 | zh-Hans, 225/225, 100%, missing 0 8 | pt-BR, 95/225, 42%, missing 130 9 | pl-PL, 80/225, 36%, missing 145 10 | -------------------------------------------------------------------------------- /Localization/UpdateLocalizationFiles.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | # tModLoader .lang file updater by jopojelly. (v1.1) 4 | # Run this script after updating en-US.lang with new keys. python 3. 5 | # Also make sure the file encodings are UTF-8 not UTF-8-BOM. 6 | # You can use this script for your own mod if you credit me. 7 | 8 | filename = './{0}.lang' 9 | 10 | languages = ['zh-Hans', 'ru-RU', 'pt-BR', 'pl-PL', 'it-IT', 'fr-FR', 'es-ES', 'de-DE'] 11 | missings = [] 12 | for language in languages: 13 | if not os.path.isfile(filename.format(language)): 14 | with open(filename.format(language), 'w', encoding='utf-8') as output: 15 | output.write("# Translation by: \n\nmissing=missing") 16 | #continue 17 | #language = 'zh-Hans' 18 | otherLanguage = '' 19 | missing = 0 20 | #print("Updating:",language) 21 | with open(filename.format('en-US'), 'r', encoding='utf-8') as english, open(filename.format(language), 'r', encoding='utf-8') as other: 22 | enLines = english.readlines() 23 | 24 | # Preserve Credits (comment lines on first few lines) 25 | otherLinesAll = other.readlines() 26 | 27 | for otherLine in otherLinesAll: 28 | if otherLine.startswith('#'): 29 | otherLanguage += otherLine 30 | else: 31 | break 32 | 33 | # Skip empty whitespace and comment lines to end up with only json lines. 34 | otherLines = [x for x in otherLinesAll if not (x.startswith("#") or len(x.strip()) == 0)] 35 | 36 | if len(otherLines) == 0: 37 | otherLines.append('TrashEntry') # just to prevent Index out of Range 38 | otherIndex = 0 39 | engComments = True 40 | for englishIndex, englishLine in enumerate(enLines): 41 | if englishLine.startswith('#') and engComments: 42 | continue 43 | if len(englishLine.strip()) == 0 and engComments: 44 | engComments = False 45 | 46 | # For lines with key values pairs, copy translation or add commented translation placeholder. 47 | if englishLine.find("=") != -1: 48 | if len(otherLines) > otherIndex and otherLines[otherIndex].startswith(englishLine[:englishLine.find("=")]): 49 | otherLanguage += otherLines[otherIndex] 50 | otherIndex += 1 51 | else: 52 | otherLanguage += "# " + englishLine.strip() + '\n' 53 | missing += 1 54 | # Add English Comments back in 55 | elif englishLine.strip().startswith('#'): 56 | otherLanguage += englishLine 57 | # Add other json lines in. Also add in whitespace lines. 58 | else: 59 | otherLanguage += englishLine 60 | if len(englishLine.strip()) > 0: 61 | otherIndex += 1 62 | #print(otherLanguage) 63 | # Save changes. 64 | if missing > 0: 65 | missings.append( (language, missing) ) 66 | print(language,missing) 67 | with open(filename.format(language), 'w', encoding='utf-8') as output: 68 | output.write(otherLanguage) 69 | 70 | with open('./TranslationsNeeded.txt', 'w', encoding='utf-8') as output: 71 | if len(missings) == 0: 72 | output.write('All Translations up-to-date!') 73 | else: 74 | for entry in missings: 75 | output.write(str(entry[0]) + " " + str(entry[1]) + "\n") 76 | #print("Make sure to run Diff.") 77 | #input("Press Enter to continue...") -------------------------------------------------------------------------------- /LootCache.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using ReLogic.Reflection; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Diagnostics; 8 | using System.Globalization; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using Terraria; 13 | using Terraria.GameContent.ItemDropRules; 14 | using Terraria.ID; 15 | using Terraria.ModLoader; 16 | using Terraria.Utilities; 17 | 18 | namespace RecipeBrowser 19 | { 20 | public class LootCache 21 | { 22 | public static LootCache instance; 23 | 24 | // indexed by itemid, value is list of npcs. 25 | public Dictionary> lootInfos; 26 | 27 | public LootCache() 28 | { 29 | lootInfos = new Dictionary>(); 30 | } 31 | } 32 | 33 | internal static class LootCacheManager 34 | { 35 | internal static void Setup(Mod recipeBrowserMod) 36 | { 37 | LootCache.instance = new LootCache(); 38 | for (int i = -65; i < NPCLoader.NPCCount; i++) { 39 | // Is npcid 0 a problem? 40 | // Here we ignore global rules 41 | List dropRules = Main.ItemDropsDB.GetRulesForNPCID(i, false); 42 | 43 | List list = new List(); 44 | DropRateInfoChainFeed ratesInfo = new DropRateInfoChainFeed(1f); 45 | foreach (IItemDropRule item in dropRules) { 46 | item.ReportDroprates(list, ratesInfo); 47 | } 48 | 49 | foreach (DropRateInfo dropRateInfo in list) { 50 | List npcThatDropThisItem; 51 | if (!LootCache.instance.lootInfos.TryGetValue(dropRateInfo.itemId, out npcThatDropThisItem)) 52 | LootCache.instance.lootInfos.Add(dropRateInfo.itemId, npcThatDropThisItem = new List()); 53 | if (!npcThatDropThisItem.Contains(i)) { 54 | npcThatDropThisItem.Add(i); // Maybe change this to HashSet. 55 | } 56 | } 57 | } 58 | 59 | // TODO: What should we do with globals? Maybe add an NPC icon indicating any NPC can drop the item? 60 | // var globals = new GlobalLoot(Main.ItemDropsDB).Get(); 61 | 62 | return; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /LootUnifiedRandom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Terraria; 3 | using Terraria.Utilities; 4 | 5 | namespace RecipeBrowser 6 | { 7 | internal class LootUnifiedRandom : UnifiedRandom 8 | { 9 | //internal static int entropy; 10 | internal static int loop; 11 | 12 | //internal static bool zero; 13 | private int[] returns; 14 | 15 | // realRandom avoids deadlock caused by re-rolling prefixes 16 | internal UnifiedRandom realRandom; 17 | 18 | public LootUnifiedRandom() 19 | { 20 | returns = new int[5000]; 21 | } 22 | 23 | public override int Next(int maxValue) 24 | { 25 | if (loop == 0 && !realRandom.NextBool(100)) return 0; 26 | int index = Math.Abs(maxValue) % 5000; 27 | returns[index]++; 28 | return (maxValue + returns[index]) % maxValue; 29 | } 30 | 31 | public override int Next(int minValue, int maxValue) 32 | { 33 | if (loop == 0) return minValue; 34 | if (minValue == maxValue) return minValue; 35 | int index = Math.Abs(maxValue) % 5000; 36 | returns[index]++; 37 | return minValue + ((maxValue + returns[index]) % (maxValue - minValue)); 38 | } 39 | 40 | public override double NextDouble() 41 | { 42 | if (loop == 0) return 0.0; 43 | return base.NextDouble(); 44 | } 45 | 46 | // public override int Next(int maxValue) 47 | // { 48 | //if (loop == 0) return 0; 49 | //entropy += 257; 50 | //if (loop % 7 == 0 && maxValue > 20 && entropy % 13 == 0) return 0; 51 | //entropy += loop + maxValue; 52 | //entropy = entropy < 0 ? 0 : entropy; 53 | // return (maxValue + entropy) % maxValue; 54 | // } 55 | 56 | // public override int Next(int minValue, int maxValue) 57 | // { 58 | //if (loop == 0) return minValue; 59 | //entropy += loop + maxValue; 60 | // return minValue + ((maxValue + entropy) % (maxValue - minValue)); 61 | // } 62 | } 63 | } -------------------------------------------------------------------------------- /Patches.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using Terraria; 5 | 6 | namespace RecipeBrowser 7 | { 8 | public static class Patches 9 | { 10 | private static bool AdjTilesActive = false; 11 | public static void Apply() 12 | { 13 | // Patches are automatically unapplied on unload by TerrariaHooks. -ade 14 | 15 | // This patch Invalidates the precomputed extended craft unless the reason for FindRecipes was just AdjTiles, since we ignore AdjTiles changes. 16 | if(!Main.dedServ) 17 | Terraria.On_Recipe.FindRecipes += (orig, canDelayCheck) => 18 | { 19 | orig(canDelayCheck); 20 | 21 | if(!AdjTilesActive) 22 | //if (!new StackTrace().GetFrames().Any(x => x.GetMethod().Name.StartsWith("AdjTiles"))) 23 | { 24 | RecipeCatalogueUI.instance.InvalidateExtendedCraft(); 25 | //Main.NewText("FindRecipes postfix: InvalidateExtendedCraft"); 26 | } 27 | else 28 | { 29 | //Main.NewText("FindRecipes postfix: skipped"); 30 | } 31 | }; 32 | 33 | // This patch will call FindRecipes even if the player inventory is closed, keeping Craft tool buttons accurate. 34 | Terraria.On_Player.AdjTiles += (orig, player) => { 35 | AdjTilesActive = true; 36 | orig(player); 37 | 38 | // AdjTiles does the opposite. This way recipes will be calculated 39 | if (!Main.playerInventory && RecipeBrowserUI.instance.ShowRecipeBrowser) // Inverted condition. 40 | { 41 | bool flag = false; 42 | for (int l = 0; l < Main.LocalPlayer.adjTile.Length; l++) { 43 | if (Main.LocalPlayer.oldAdjTile[l] != Main.LocalPlayer.adjTile[l]) { 44 | flag = true; 45 | break; 46 | } 47 | } 48 | if (Main.LocalPlayer.adjWater != Main.LocalPlayer.oldAdjWater) { 49 | flag = true; 50 | } 51 | if (Main.LocalPlayer.adjHoney != Main.LocalPlayer.oldAdjHoney) { 52 | flag = true; 53 | } 54 | if (Main.LocalPlayer.adjLava != Main.LocalPlayer.oldAdjLava) { 55 | flag = true; 56 | } 57 | if (flag) { 58 | Recipe.FindRecipes(); 59 | } 60 | } 61 | AdjTilesActive = false; 62 | }; 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Terraria": { 4 | "commandName": "Executable", 5 | "executablePath": "dotnet", 6 | "commandLineArgs": "$(tMLPath)", 7 | "workingDirectory": "$(tMLSteamPath)" 8 | }, 9 | "TerrariaFast": { 10 | "commandName": "Executable", 11 | "executablePath": "dotnet", 12 | "commandLineArgs": "$(tMLPath) -skipselect", 13 | "workingDirectory": "$(tMLSteamPath)" 14 | }, 15 | "TerrariaServer": { 16 | "commandName": "Executable", 17 | "executablePath": "dotnet", 18 | "commandLineArgs": "$(tMLServerPath)", 19 | "workingDirectory": "$(tMLSteamPath)" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /RecipeBrowser.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RecipeBrowser 6 | latest 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /RecipeBrowser.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34309.116 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RecipeBrowser", "RecipeBrowser.csproj", "{BC097F40-BCE9-4987-8A5F-5317678992A8}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {BC097F40-BCE9-4987-8A5F-5317678992A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {BC097F40-BCE9-4987-8A5F-5317678992A8}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {BC097F40-BCE9-4987-8A5F-5317678992A8}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {BC097F40-BCE9-4987-8A5F-5317678992A8}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {1FFEFA6F-7B08-401A-8B2B-ACBDB2A73223} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /RecipeBrowserClientConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | using Terraria.ModLoader; 5 | using Terraria.ModLoader.Config; 6 | 7 | namespace RecipeBrowser 8 | { 9 | class RecipeBrowserClientConfig : ModConfig 10 | { 11 | public override ConfigScope Mode => ConfigScope.ClientSide; 12 | 13 | [DefaultValue(true)] 14 | public bool ShowRecipeModSource { get; set; } 15 | 16 | [DefaultValue(true)] 17 | public bool ShowItemModSource { get; set; } 18 | 19 | [DefaultValue(true)] 20 | public bool ShowNPCModSource { get; set; } 21 | 22 | [Header("AutomaticSettings")] 23 | // non-player specific stuff: 24 | 25 | [DefaultValue(typeof(Vector2), "475, 350")] 26 | [Range(0f, 1920f)] 27 | public Vector2 RecipeBrowserSize { get; set; } 28 | 29 | [DefaultValue(typeof(Vector2), "400, 400")] 30 | [Range(0f, 1920f)] 31 | public Vector2 RecipeBrowserPosition { get; set; } 32 | 33 | [DefaultValue(typeof(Vector2), "-310, 90")] 34 | [Range(-1920f, 0f)] 35 | public Vector2 FavoritedRecipePanelPosition { get; set; } 36 | 37 | [DefaultValue(true)] 38 | public bool OnlyShowFavoritedWhileInInventory { get; set; } 39 | 40 | internal static void SaveConfig() { 41 | // in-game ModConfig saving from mod code is not supported yet in tmodloader, and subject to change, so we need to be extra careful. 42 | // This code only supports client configs, and doesn't call onchanged. It also doesn't support ReloadRequired or anything else. 43 | MethodInfo saveMethodInfo = typeof(ConfigManager).GetMethod("Save", BindingFlags.Static | BindingFlags.NonPublic); 44 | if (saveMethodInfo != null) 45 | saveMethodInfo.Invoke(null, new object[] { ModContent.GetInstance< RecipeBrowserClientConfig>() }); 46 | else 47 | RecipeBrowser.instance.Logger.Warn("In-game SaveConfig failed, code update required"); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RecipeBrowserGlobalItem.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using RecipeBrowser.UIElements; 3 | using System.Collections.Generic; 4 | using Terraria; 5 | using Terraria.DataStructures; 6 | using Terraria.Localization; 7 | using Terraria.ModLoader; 8 | 9 | namespace RecipeBrowser 10 | { 11 | internal class RecipeBrowserGlobalItem : GlobalItem 12 | { 13 | internal static string RBText(string key, string category = "RecipeBrowserUI") => RecipeBrowser.RBText(category, key); 14 | 15 | // OnPickup only called on LocalPlayer: I think 16 | public override void OnCreated(Item item, ItemCreationContext context) 17 | { 18 | ItemReceived(item); 19 | } 20 | 21 | // OnPickup only called on LocalPlayer: i == Main.myPlayer 22 | public override bool OnPickup(Item item, Player player) 23 | { 24 | ItemReceived(item); 25 | return true; 26 | } 27 | 28 | internal void ItemReceived(Item item) 29 | { 30 | RecipeBrowserUI.instance.ItemReceived(item); 31 | } 32 | 33 | public override void ModifyTooltips(Item item, List tooltips) { 34 | if (Main.npcShop <= 0 && item == UIItemSlot.hoveredItem && SharedUI.instance.SelectedSort?.button.hoverText == "Value") { 35 | int storeValue = Main.HoverItem.value; 36 | string text = ""; 37 | int plat = 0; 38 | int gold = 0; 39 | int silver = 0; 40 | int copper = 0; 41 | int total = storeValue * Main.HoverItem.stack; 42 | 43 | // convert to sell price 44 | total = storeValue / 5; 45 | if (total < 1 && storeValue > 0) { 46 | total = 1; 47 | } 48 | total *= Main.HoverItem.stack; 49 | 50 | if (total < 1) { 51 | total = 1; 52 | } 53 | if (total >= 1000000) { 54 | plat = total / 1000000; 55 | total -= plat * 1000000; 56 | } 57 | if (total >= 10000) { 58 | gold = total / 10000; 59 | total -= gold * 10000; 60 | } 61 | if (total >= 100) { 62 | silver = total / 100; 63 | total -= silver * 100; 64 | } 65 | if (total >= 1) { 66 | copper = total; 67 | } 68 | if (plat > 0) { 69 | text = string.Concat(text, plat, " ", Lang.inter[15].Value, " "); 70 | } 71 | if (gold > 0) { 72 | text = string.Concat(text, gold, " ", Lang.inter[16].Value, " "); 73 | } 74 | if (silver > 0) { 75 | text = string.Concat(text, silver, " ", Lang.inter[17].Value, " "); 76 | } 77 | if (copper > 0) { 78 | text = string.Concat(text, copper, " ", Lang.inter[18].Value, " "); 79 | } 80 | // array[num4] = Lang.tip[49].Value + " " + text; 81 | //array[num4] = Lang.tip[50].Value + " " + text; 82 | 83 | Color color = Color.White; 84 | float mouseFade = Main.mouseTextColor / 255f; 85 | if (plat > 0) { 86 | color = new Color((byte)(220f * mouseFade), (byte)(220f * mouseFade), (byte)(198f * mouseFade), Main.mouseTextColor); 87 | } 88 | else if (gold > 0) { 89 | color = new Color((byte)(224f * mouseFade), (byte)(201f * mouseFade), (byte)(92f * mouseFade), Main.mouseTextColor); 90 | } 91 | else if (silver > 0) { 92 | color = new Color((byte)(181f * mouseFade), (byte)(192f * mouseFade), (byte)(193f * mouseFade), Main.mouseTextColor); 93 | } 94 | else if (copper > 0) { 95 | color = new Color((byte)(246f * mouseFade), (byte)(138f * mouseFade), (byte)(96f * mouseFade), Main.mouseTextColor); 96 | } 97 | var valueTooltip = new TooltipLine(Mod, "RecipeBrowserValue", Lang.tip[49].Value + " " + text); 98 | if (storeValue == 0) { 99 | valueTooltip = new TooltipLine(Mod, "RecipeBrowserValue", Lang.tip[51].Value); 100 | color = new Color((byte)(120f * mouseFade), (byte)(120f * mouseFade), (byte)(120f * mouseFade), Main.mouseTextColor); 101 | } 102 | valueTooltip.OverrideColor = color; 103 | 104 | tooltips.Add(valueTooltip); 105 | } 106 | 107 | 108 | // TODO: Config option to show always 109 | if (RecipeCatalogueUI.instance.hoveredIndex < 0) return; 110 | 111 | var selectedModRecipe = Main.recipe[RecipeCatalogueUI.instance.hoveredIndex]; 112 | if (selectedModRecipe.Mod != null && ModContent.GetInstance().ShowRecipeModSource && item.type == selectedModRecipe.createItem.type) 113 | { 114 | var line = new TooltipLine(Mod, "RecipeBrowser:RecipeOriginHint", Language.GetTextValue("Mods.RecipeBrowser.RecipeBrowserUI.RecipeAddedBy", selectedModRecipe.Mod.DisplayName)) 115 | { 116 | OverrideColor = Color.Goldenrod 117 | }; 118 | int index = tooltips.FindIndex(x => x.Name == "ItemName"); 119 | if (index == -1) 120 | tooltips.Add(line); 121 | else 122 | tooltips.Insert(index + 1, line); 123 | } 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /RecipeBrowserTool.cs: -------------------------------------------------------------------------------- 1 | namespace RecipeBrowser 2 | { 3 | internal class RecipeBrowserTool : Tool 4 | { 5 | public RecipeBrowserTool() : base(typeof(RecipeBrowserUI)) 6 | { 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /TagHandlers/ImageHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Terraria.UI.Chat; 6 | using Newtonsoft.Json.Linq; 7 | using Terraria; 8 | using ReLogic.Graphics; 9 | using System.Net; 10 | using System.IO; 11 | using System; 12 | using ReLogic.Content; 13 | using Terraria.ModLoader; 14 | using System.Globalization; 15 | using Terraria.ModLoader.UI; 16 | using Terraria.Localization; 17 | 18 | namespace RecipeBrowser.TagHandlers 19 | { 20 | public class ImageTagHandler : ITagHandler 21 | { 22 | private class ImageSnippet : TextSnippet 23 | { 24 | Asset texture; 25 | Asset Texture 26 | { 27 | get 28 | { 29 | if (texture == null) 30 | { 31 | texture = ModContent.Request(texturePath); 32 | } 33 | return texture; 34 | } 35 | } 36 | private string tooltip; 37 | public int vOffset; 38 | private float otherScale; 39 | 40 | string texturePath; 41 | public ImageSnippet(string texturePath, string tooltip = null, float scale = 1f, float otherScale = 1f) : base("", Color.White, scale) 42 | { 43 | DeleteWhole = true; 44 | this.texturePath = texturePath; 45 | this.tooltip = tooltip; 46 | this.otherScale = otherScale; 47 | } 48 | 49 | public override void OnHover() 50 | { 51 | if (!string.IsNullOrEmpty(tooltip)) 52 | { 53 | // Main.hoverItemName = tooltip; 54 | UICommon.TooltipMouseText(tooltip); 55 | } 56 | } 57 | 58 | public override Color GetVisibleColor() 59 | { 60 | return Color; 61 | } 62 | 63 | public override bool UniqueDraw(bool justCheckingString, out Vector2 size, SpriteBatch spriteBatch, Vector2 position = default(Vector2), Color color = default(Color), float scale = 1f) 64 | { 65 | size = Texture.Size() * new Vector2(otherScale) + new Vector2(0, vOffset); 66 | if (!justCheckingString && color != Color.Black) 67 | { 68 | //size = Texture.Size() * new Vector2(1, scale) + new Vector2(0, vOffset); // TODO: Why was `new Vector2( 1, scale)`?? 69 | //spriteBatch.Draw(Texture, position, color); 70 | spriteBatch.Draw(Texture.Value, position + new Vector2(0, vOffset), null, color, 0, Vector2.Zero, otherScale, SpriteEffects.None, 0); 71 | //if (scale > 1) 72 | // Main.NewText(size); 73 | //size = Vector2.Zero; 74 | } 75 | return true; 76 | } 77 | 78 | public override float GetStringLength(DynamicSpriteFont font) 79 | { 80 | return Texture.Size().X * Scale; 81 | } 82 | } 83 | 84 | TextSnippet ITagHandler.Parse(string text, Color baseColor, string options) 85 | { 86 | // TODO: option for scale or absolute size 87 | // TODO: option for tooltip/translation key 88 | // TODO: option for frame (animated?) 89 | // tTooltip 90 | // f0;0;20;20 91 | // 92 | string tooltip = null; 93 | float scale = 1f; 94 | int vOffset = 0; 95 | string[] array = options.Split(','); 96 | foreach (var option in array) 97 | { 98 | if (option.Length != 0) 99 | { 100 | if (option[0] == 't') { 101 | tooltip = option.Substring(1).Replace(';', ':'); 102 | //if (tooltip.StartsWith("$")) { 103 | // tooltip = Language.GetTextValue(tooltip.Substring(1)); 104 | //} 105 | } 106 | if (option[0] == 's') 107 | float.TryParse(option.Substring(1), NumberStyles.Float, CultureInfo.InvariantCulture, out scale); // 0.5 is interpreted as 5 instead of 0.5 for users of some cultures, so need InvariantCulture 108 | if (option[0] == 'v') 109 | int.TryParse(option.Substring(1), out vOffset); 110 | } 111 | //return new TextSnippet("<" + text.Replace("\\[", "[").Replace("\\]", "]") + ">", baseColor, 1f); 112 | } 113 | if (ModContent.HasAsset(text)) 114 | { 115 | return new ImageSnippet(text, tooltip, 1f, scale) 116 | { 117 | vOffset = vOffset 118 | }; 119 | } 120 | return new TextSnippet(text); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /TagHandlers/ItemHoverFixHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Graphics; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Terraria; 7 | using Terraria.GameContent; 8 | using Terraria.GameContent.UI; 9 | using Terraria.ModLoader; 10 | using Terraria.ModLoader.IO; 11 | using Terraria.UI; 12 | using Terraria.UI.Chat; 13 | using Terraria.ID; 14 | using Terraria.ModLoader.UI; 15 | using System.Collections; 16 | 17 | namespace RecipeBrowser.TagHandlers 18 | { 19 | // A clone of ItemTagHandler except OnHover doesn't do MouseText. 20 | public class ItemHoverFixTagHandler : ITagHandler 21 | { 22 | private class ItemHoverFixSnippet : TextSnippet 23 | { 24 | private Item _item; 25 | private bool check; 26 | private bool itemTooltip; 27 | 28 | public ItemHoverFixSnippet(Item item, bool check = false, bool itemTooltip = false) 29 | : base("") 30 | { 31 | this._item = item; 32 | this.Color = ItemRarity.GetColor(item.rare); 33 | this.check = check; 34 | this.itemTooltip = itemTooltip; 35 | } 36 | 37 | public override void OnHover() 38 | { 39 | if(itemTooltip) { 40 | string text = _item.Name + (_item.ModItem != null && ModContent.GetInstance().ShowItemModSource ? " [" + _item.ModItem.Mod.DisplayName + "]" : ""); 41 | Main.HoverItem = _item.Clone(); 42 | Main.HoverItem.SetNameOverride(text); 43 | //Main.hoverItemName = text; 44 | Main.instance.MouseText(_item.Name, _item.rare, 0); 45 | } 46 | else { 47 | string stack = _item.stack > 1 ? $" ({_item.stack}) " : ""; 48 | string text = _item.Name + stack + (_item.ModItem != null && ModContent.GetInstance().ShowItemModSource ? " [" + _item.ModItem.Mod.DisplayName + "]" : ""); 49 | UICommon.TooltipMouseText(text); 50 | } 51 | } 52 | 53 | public override bool UniqueDraw(bool justCheckingString, out Vector2 size, SpriteBatch spriteBatch, Vector2 position = default(Vector2), Color color = default(Color), float scale = 1f) 54 | { 55 | float num = 1f; 56 | float num2 = 1f; 57 | if (Main.netMode != NetmodeID.Server && !Main.dedServ) 58 | { 59 | Utilities.LoadItem(_item.type); 60 | Texture2D texture2D = TextureAssets.Item[this._item.type].Value; 61 | Rectangle rectangle; 62 | if (Main.itemAnimations[this._item.type] != null) 63 | { 64 | rectangle = Main.itemAnimations[this._item.type].GetFrame(texture2D); 65 | } 66 | else 67 | { 68 | rectangle = texture2D.Frame(1, 1, 0, 0); 69 | } 70 | if (rectangle.Height > 32) 71 | { 72 | num2 = 32f / (float)rectangle.Height; 73 | } 74 | } 75 | /* 76 | num2 *= scale; 77 | num *= num2; 78 | if (num > 0.75f) 79 | { 80 | num = 0.75f; 81 | } 82 | */ 83 | num = scale * 0.75f; 84 | if (!justCheckingString && color != Color.Black) 85 | { 86 | float inventoryScale = Main.inventoryScale; 87 | Main.inventoryScale = num; 88 | 89 | ItemSlot.Draw(spriteBatch, ref this._item, 14, position - new Vector2(10f) * scale * num, Color.White); 90 | if (check) 91 | { 92 | ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.ItemStack.Value, "✓", position + new Vector2(14, 10), Utilities.yesColor, 0f, Vector2.Zero, new Vector2(0.7f)); 93 | } 94 | Main.inventoryScale = inventoryScale; 95 | } 96 | size = new Vector2(32f) * scale * num; 97 | return true; 98 | } 99 | 100 | public override float GetStringLength(DynamicSpriteFont font) 101 | { 102 | return 32f * this.Scale * 0.65f; 103 | } 104 | } 105 | 106 | TextSnippet ITagHandler.Parse(string text, Color baseColor, string options) 107 | { 108 | Item item = new Item(); 109 | int type; 110 | bool check = false; 111 | bool itemTooltip = false; 112 | if (int.TryParse(text, out type)) 113 | { 114 | item.netDefaults(type); 115 | } 116 | if (item.type <= 0) 117 | { 118 | return new TextSnippet(text); 119 | } 120 | item.stack = 1; 121 | // options happen here, we add MID (=ModItemData) options 122 | if (options != null) 123 | { 124 | // don't know why all these options here in vanilla, 125 | // since it only assumed one option (stack OR prefix, since prefixed items don't stack) 126 | string[] array = options.Split(new char[] 127 | { 128 | ',' 129 | }); 130 | for (int i = 0; i < array.Length; i++) 131 | { 132 | if (array[i].Length != 0) 133 | { 134 | char c = array[i][0]; 135 | int value2; 136 | // MID is present, we will override 137 | if (c == 'd') 138 | { 139 | item = ItemIO.FromBase64(array[i].Substring(1)); 140 | } 141 | else if (c == 'o') 142 | { 143 | item.SetNameOverride(array[i].Substring(1)); 144 | } 145 | else if (c == 'c') 146 | { 147 | check = true; 148 | } 149 | else if (c == 't') { 150 | itemTooltip = true; 151 | } 152 | else if (c != 'p') 153 | { 154 | int value; 155 | if ((c == 's' || c == 'x') && int.TryParse(array[i].Substring(1), out value)) 156 | { 157 | // item.stack = Utils.Clamp(value, 1, item.maxStack); 158 | item.stack = value; // Workaround for #124 since ItemTagHandler.Parse clamps to maxStack 159 | } 160 | } 161 | else if (int.TryParse(array[i].Substring(1), out value2)) 162 | { 163 | item.Prefix((int)((byte)Utils.Clamp(value2, 0, PrefixLoader.PrefixCount))); 164 | } 165 | } 166 | } 167 | } 168 | string str = ""; 169 | if (item.stack > 1) 170 | { 171 | str = " (" + item.stack + ")"; 172 | } 173 | return new ItemHoverFixTagHandler.ItemHoverFixSnippet(item, check, itemTooltip) 174 | { 175 | Text = "[" + item.AffixName() + str + "]", 176 | CheckForHover = true, 177 | DeleteWhole = true 178 | }; 179 | } 180 | 181 | // we do not alter vanilla ways of doing things 182 | // this can lead to trouble in future patches 183 | public static string GenerateTag(Item I, bool itemTooltip = false) 184 | { 185 | string text = "[itemhover"; 186 | // assuming we have modded data, simply write the item as base64 187 | // do not write other option, base64 holds all the info. 188 | //if (I.modItem != null || I.globalItems.Any()) 189 | //{ 190 | // text = text + "/d" + ItemIO.ToBase64(I); 191 | //} 192 | //else 193 | { 194 | if (I.prefix != 0) 195 | { 196 | text = text + "/p" + I.prefix; 197 | } 198 | if (I.stack != 1) 199 | { 200 | text = text + "/s" + I.stack; 201 | } 202 | if(itemTooltip) 203 | text = text + (I.prefix != 0 || I.stack != 1 ? "," : "/") + "t"; 204 | } 205 | 206 | object obj = text; 207 | return string.Concat(new object[] 208 | { 209 | obj, 210 | ":", 211 | I.netID, 212 | "]" 213 | }); 214 | } 215 | 216 | public static string GenerateTag(int type, int stack, string nameOverride = null, bool check = false) 217 | { 218 | List additionals = new List(); 219 | if (!string.IsNullOrEmpty(nameOverride)) 220 | additionals.Add($"o{nameOverride}"); 221 | if (check) 222 | additionals.Add($"c"); 223 | if (stack > 1) 224 | additionals.Add($"s{stack}"); 225 | if (additionals.Count > 0) 226 | { 227 | string options = "/" + string.Join(",", additionals); 228 | return $"[itemhover{options}:{type}]"; 229 | } 230 | return $"[itemhover:{type}]"; 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /TagHandlers/LinkHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Terraria.UI.Chat; 6 | using Newtonsoft.Json.Linq; 7 | using Terraria; 8 | using ReLogic.Graphics; 9 | using System.Net; 10 | using System.IO; 11 | using System; 12 | using System.Diagnostics; 13 | 14 | namespace RecipeBrowser.TagHandlers 15 | { 16 | public class LinkTagHandler : ITagHandler 17 | { 18 | private class LinkSnippet : TextSnippet 19 | { 20 | private string url; 21 | 22 | public LinkSnippet(string url, string text) 23 | : base(text, Color.LightBlue, 1f) 24 | { 25 | this.CheckForHover = true; 26 | this.url = url; 27 | } 28 | 29 | public override void OnHover() 30 | { 31 | // Main.hoverItemName = url; 32 | if (!string.IsNullOrWhiteSpace(url)) 33 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(url); 34 | } 35 | 36 | public override void OnClick() 37 | { 38 | //Process.Start(url); 39 | string realLink = "https://" + url; 40 | Utils.OpenToURL(realLink); 41 | } 42 | 43 | public override Color GetVisibleColor() 44 | { 45 | return Color; 46 | } 47 | } 48 | 49 | TextSnippet ITagHandler.Parse(string text, Color baseColor, string options) 50 | { 51 | return new LinkTagHandler.LinkSnippet(options, text); 52 | } 53 | 54 | public static string GenerateTag(string url, string text) 55 | { 56 | // TODO: Make some sort of escape for ":" or fix Utils.OpenToURL needing https:// 57 | return $"[l/{url}:{text}]"; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TagHandlers/NPCHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Graphics; 4 | using System.Linq; 5 | using Terraria; 6 | using Terraria.GameContent; 7 | using Terraria.ModLoader; 8 | using Terraria.ModLoader.IO; 9 | using Terraria.UI; 10 | using Terraria.UI.Chat; 11 | using Terraria.ID; 12 | 13 | namespace RecipeBrowser.TagHandlers 14 | { 15 | public class NPCTagHandler : ITagHandler 16 | { 17 | private class NPCSnippet : TextSnippet 18 | { 19 | private int npcType; 20 | private int netID; 21 | private bool head; 22 | 23 | public NPCSnippet(int netID, bool head = false) 24 | : base("") 25 | { 26 | this.netID = netID; 27 | this.head = head; 28 | npcType = ContentSamples.NpcsByNetId[netID].type; 29 | //this.Color = ItemRarity.GetColor(item.rare); 30 | } 31 | 32 | public override void OnHover() 33 | { 34 | if (npcType >= Terraria.ID.NPCID.Count) 35 | { 36 | ModNPC modNPC = NPCLoader.GetNPC(npcType); 37 | // Main.hoverItemName = Lang.GetNPCNameValue(npcType) + (modNPC != null && ModContent.GetInstance().ShowNPCModSource ? " [" + modNPC.Mod.DisplayName + "]" : ""); 38 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(Lang.GetNPCNameValue(npcType) + (modNPC != null && ModContent.GetInstance().ShowNPCModSource ? " [" + modNPC.Mod.DisplayName + "]" : "")); 39 | } 40 | else 41 | { 42 | // Main.hoverItemName = Lang.GetNPCNameValue(netID); 43 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(Lang.GetNPCNameValue(netID)); 44 | } 45 | //Main.HoverItem = this._item.Clone(); 46 | //Main.instance.MouseText(this._item.Name, this._item.rare, 0, -1, -1, -1, -1); 47 | 48 | if(head) 49 | RecipeBrowserUI.instance.npcArrow = NPC.FindFirstNPC(npcType); 50 | } 51 | 52 | public override void OnClick() { 53 | base.OnClick(); 54 | 55 | if(RecipeBrowserUI.instance.npcArrow != -1) 56 | Main.Pings.Add(Main.npc[RecipeBrowserUI.instance.npcArrow].Center / 16); 57 | } 58 | 59 | public override bool UniqueDraw(bool justCheckingString, out Vector2 size, SpriteBatch spriteBatch, Vector2 position = default(Vector2), Color color = default(Color), float scale = 1f) 60 | { 61 | //float drawScale = 1f; 62 | //float verticalScaleToFit = 1f; 63 | float maxHeight = 24 * scale; 64 | Texture2D texture2D = null; 65 | Rectangle rectangle = new Rectangle(); 66 | if (Main.netMode != NetmodeID.Server && !Main.dedServ) 67 | { 68 | Utilities.LoadNPC(npcType); 69 | texture2D = TextureAssets.Npc[npcType].Value; 70 | rectangle = new Rectangle(0, (texture2D.Height / Main.npcFrameCount[npcType]) * 0, texture2D.Width, texture2D.Height / Main.npcFrameCount[npcType]); 71 | 72 | if (head) 73 | { 74 | int headIndex = NPC.TypeToDefaultHeadIndex(npcType); 75 | if(headIndex != -1) 76 | { 77 | texture2D = TextureAssets.NpcHead[headIndex].Value; 78 | rectangle = texture2D.Bounds; // or texture2D.Frame(1, 1, 0, 0); 79 | } 80 | } 81 | 82 | //if (rectangle.Height > maxHeight) 83 | //{ 84 | // verticalScaleToFit = maxHeight / (float)rectangle.Height; 85 | //} 86 | if (rectangle.Width * scale > maxHeight || rectangle.Height * scale > maxHeight) 87 | { 88 | if (rectangle.Width > rectangle.Height) 89 | { 90 | scale = maxHeight / rectangle.Width; 91 | } 92 | else 93 | { 94 | scale = maxHeight / rectangle.Height; 95 | } 96 | } 97 | } 98 | //verticalScaleToFit *= scale; 99 | //drawScale *= verticalScaleToFit; 100 | //if (drawScale > 0.75f) 101 | //{ 102 | // drawScale = 0.75f; 103 | //} 104 | if (!justCheckingString && color != Color.Black) 105 | { 106 | //float inventoryScale = Main.inventoryScale; 107 | //Main.inventoryScale = scale * num; 108 | //if (Main.rand.NextBool()) 109 | 110 | NPC npc = ContentSamples.NpcsByNetId[netID]; 111 | Color lighting = Color.White; 112 | //Main.spriteBatch.Draw(texture2D, position + new Vector2(maxHeight / 2)/*- new Vector2(10f) * scale * num*/, rectangle, /*color*/ Color.White, 0, rectangle.Center(), scale, SpriteEffects.None, 0); 113 | Main.spriteBatch.Draw(texture2D, position + new Vector2(maxHeight / 2)/*- new Vector2(10f) * scale * num*/, rectangle, npc.GetAlpha(lighting), 0, rectangle.Center(), scale, SpriteEffects.None, 0); 114 | if (npc.color != default(Microsoft.Xna.Framework.Color)) { 115 | Main.spriteBatch.Draw(texture2D, position + new Vector2(maxHeight / 2)/*- new Vector2(10f) * scale * num*/, rectangle, npc.GetColor(lighting), 0, rectangle.Center(), scale, SpriteEffects.None, 0); 116 | } 117 | //else 118 | // Main.spriteBatch.Draw(texture2D, position /*- new Vector2(10f) * scale * num*/, rectangle, color, 0, Vector2.Zero, num, SpriteEffects.None, 0); 119 | //ItemSlot.Draw(spriteBatch, ref this._item, 14, position - new Vector2(10f) * scale * num, Color.White); 120 | //int s =24; 121 | //Main.spriteBatch.Draw(Main.magicPixel, new Rectangle((int)position.X, (int)position.Y, s, s), Color.Red * 0.3f); 122 | //Main.inventoryScale = inventoryScale; 123 | } 124 | //size = new Vector2(maxHeight) * scale /** drawScale*/; 125 | size = rectangle.Size() * scale + new Vector2(2, 0); 126 | return true; 127 | } 128 | 129 | public override float GetStringLength(DynamicSpriteFont font) 130 | { 131 | float h = font.MeasureString("TTHW").X; 132 | return 32f * this.Scale * 0.65f; 133 | } 134 | } 135 | 136 | TextSnippet ITagHandler.Parse(string text, Color baseColor, string options) 137 | { 138 | int netID; 139 | if (!int.TryParse(text, out netID) || netID >= NPCLoader.NPCCount || netID <= NPCID.NegativeIDCount) 140 | { 141 | return new TextSnippet(text); 142 | } 143 | bool head = false; 144 | if(options != null) 145 | { 146 | //string[] optionArray = options.Split(','); 147 | if (options == "head") 148 | head = true; 149 | } 150 | return new NPCTagHandler.NPCSnippet(netID, head) 151 | { 152 | Text = GenerateTag(netID), 153 | CheckForHover = true, 154 | DeleteWhole = true 155 | }; 156 | } 157 | 158 | public static string GenerateTag(int netID) 159 | { 160 | return $"[npc:{netID}]"; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Tool.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using Terraria; 4 | using Terraria.UI; 5 | 6 | namespace RecipeBrowser 7 | { 8 | // UIState needs UserInterface for Scrollbar fixes 9 | // Tool should store data? does it even matter? 10 | internal abstract class Tool 11 | { 12 | //internal bool visible; 13 | //internal string toggleTooltip; 14 | internal UserInterface userInterface; 15 | 16 | internal UIModState uistate; 17 | // Type uistateType; 18 | 19 | public Tool(Type uistateType) 20 | { 21 | userInterface = new UserInterface(); 22 | // this.uistateType = uistateType; 23 | uistate = (UIModState)Activator.CreateInstance(uistateType, new object[] { userInterface }); 24 | //uistate = (UIModState)Activator.CreateInstance(uistateType); 25 | 26 | //uistate = new LootUI(userInterface); 27 | uistate.Activate(); 28 | //uistate.userInterface = userInterface; 29 | userInterface.SetState(uistate); 30 | } 31 | 32 | /// 33 | /// Initializes this Tool. Called during Load. 34 | /// Useful for initializing data. 35 | /// 36 | internal virtual void Initialize() 37 | { 38 | } 39 | 40 | /// 41 | /// Initializes this Tool. Called during Load after Initialize only on SP and Clients. 42 | /// Useful for initializing UI. 43 | /// 44 | internal virtual void ClientInitialize() { } 45 | 46 | internal virtual void ScreenResolutionChanged() 47 | { 48 | userInterface?.Recalculate(); 49 | } 50 | 51 | internal virtual void UIUpdate(GameTime gameTime) 52 | { 53 | //if (visible) 54 | { 55 | userInterface?.Update(gameTime); 56 | } 57 | } 58 | 59 | internal virtual void UIDraw() 60 | { 61 | //if (visible) 62 | { 63 | uistate.ReverseChildren(); 64 | uistate.Draw(Main.spriteBatch); 65 | uistate.ReverseChildren(); 66 | } 67 | } 68 | 69 | internal virtual void DrawUpdateToggle() 70 | { 71 | } 72 | 73 | internal virtual void Toggled() 74 | { 75 | } 76 | 77 | internal virtual void PostSetupContent() 78 | { 79 | if (!Main.dedServ) 80 | { 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /UIElements/FixedUIScrollBar.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using Terraria.GameContent.UI.Elements; 3 | using Terraria.UI; 4 | 5 | namespace RecipeBrowser 6 | { 7 | public class FixedUIScrollbar : UIScrollbar 8 | { 9 | internal UserInterface userInterface; 10 | 11 | public FixedUIScrollbar(UserInterface userInterface) 12 | { 13 | this.userInterface = userInterface; 14 | } 15 | 16 | protected override void DrawSelf(SpriteBatch spriteBatch) 17 | { 18 | UserInterface temp = UserInterface.ActiveInstance; 19 | UserInterface.ActiveInstance = userInterface; 20 | base.DrawSelf(spriteBatch); 21 | UserInterface.ActiveInstance = temp; 22 | } 23 | 24 | public override void LeftMouseDown(UIMouseEvent evt) 25 | { 26 | UserInterface temp = UserInterface.ActiveInstance; 27 | UserInterface.ActiveInstance = userInterface; 28 | base.LeftMouseDown(evt); 29 | UserInterface.ActiveInstance = temp; 30 | } 31 | } 32 | 33 | public class InvisibleFixedUIScrollbar : FixedUIScrollbar 34 | { 35 | public InvisibleFixedUIScrollbar(UserInterface userInterface) : base(userInterface) 36 | { 37 | } 38 | 39 | protected override void DrawSelf(SpriteBatch spriteBatch) 40 | { 41 | UserInterface temp = UserInterface.ActiveInstance; 42 | UserInterface.ActiveInstance = userInterface; 43 | //base.DrawSelf(spriteBatch); 44 | UserInterface.ActiveInstance = temp; 45 | } 46 | 47 | public override void LeftMouseDown(UIMouseEvent evt) 48 | { 49 | UserInterface temp = UserInterface.ActiveInstance; 50 | UserInterface.ActiveInstance = userInterface; 51 | base.LeftMouseDown(evt); 52 | UserInterface.ActiveInstance = temp; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /UIElements/MoreDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/MoreDown.png -------------------------------------------------------------------------------- /UIElements/MoreLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/MoreLeft.png -------------------------------------------------------------------------------- /UIElements/MoreRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/MoreRight.png -------------------------------------------------------------------------------- /UIElements/MoreUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/MoreUp.png -------------------------------------------------------------------------------- /UIElements/NewUITextBox.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | using ReLogic.Graphics; 5 | using System; 6 | using ReLogic.Content; 7 | using Terraria; 8 | using Terraria.GameContent; 9 | using Terraria.GameContent.UI.Elements; 10 | using Terraria.UI; 11 | 12 | namespace RecipeBrowser 13 | { 14 | internal class NewUITextBox : UIPanel//UITextPanel 15 | { 16 | private static readonly Asset CloseButtonTexture = RecipeBrowser.instance.Assets.Request("UIElements/closeButton", AssetRequestMode.ImmediateLoad); 17 | internal bool focused = false; 18 | 19 | //private int _cursor; 20 | //private int _frameCount; 21 | private int _maxLength = 60; 22 | 23 | private string hintText; 24 | internal string currentString = ""; 25 | private int textBlinkerCount; 26 | private int textBlinkerState; 27 | 28 | public event Action OnFocus; 29 | 30 | public event Action OnUnfocus; 31 | 32 | public event Action OnTextChanged; 33 | 34 | public event Action OnTabPressed; 35 | 36 | public event Action OnEnterPressed; 37 | 38 | //public event Action OnUpPressed; 39 | internal bool unfocusOnEnter = true; 40 | 41 | internal bool unfocusOnTab = true; 42 | 43 | //public NewUITextBox(string text, float textScale = 1, bool large = false) : base("", textScale, large) 44 | public NewUITextBox(string hintText, string text = "") 45 | { 46 | this.hintText = hintText; 47 | currentString = text; 48 | SetPadding(0); 49 | BackgroundColor = Color.White; 50 | BorderColor = Color.White; 51 | // keyBoardInput.newKeyEvent += KeyboardInput_newKeyEvent; 52 | 53 | var closeButton = new UIHoverImageButton(CloseButtonTexture, ""); 54 | closeButton.OnLeftClick += (a, b) => SetText(""); 55 | closeButton.Left.Set(-20f, 1f); 56 | //closeButton.Top.Set(0f, .5f); 57 | closeButton.VAlign = 0.5f; 58 | //closeButton.HAlign = 0.5f; 59 | Append(closeButton); 60 | } 61 | 62 | public override void LeftClick(UIMouseEvent evt) 63 | { 64 | Focus(); 65 | base.LeftClick(evt); 66 | } 67 | 68 | public override void RightClick(UIMouseEvent evt) 69 | { 70 | base.RightClick(evt); 71 | SetText(""); 72 | } 73 | 74 | public void SetUnfocusKeys(bool unfocusOnEnter, bool unfocusOnTab) 75 | { 76 | this.unfocusOnEnter = unfocusOnEnter; 77 | this.unfocusOnTab = unfocusOnTab; 78 | } 79 | 80 | //void KeyboardInput_newKeyEvent(char obj) 81 | //{ 82 | // // Problem: keyBoardInput.newKeyEvent only fires on regular keyboard buttons. 83 | 84 | // if (!focused) return; 85 | // if (obj.Equals((char)Keys.Back)) // '\b' 86 | // { 87 | // Backspace(); 88 | // } 89 | // else if (obj.Equals((char)Keys.Enter)) 90 | // { 91 | // Unfocus(); 92 | // Main.chatRelease = false; 93 | // } 94 | // else if (Char.IsLetterOrDigit(obj)) 95 | // { 96 | // Write(obj.ToString()); 97 | // } 98 | //} 99 | 100 | public void Unfocus() 101 | { 102 | if (focused) 103 | { 104 | focused = false; 105 | Main.blockInput = false; 106 | 107 | OnUnfocus?.Invoke(); 108 | } 109 | } 110 | 111 | public void Focus() 112 | { 113 | if (!focused) 114 | { 115 | Main.clrInput(); 116 | focused = true; 117 | Main.blockInput = true; 118 | 119 | OnFocus?.Invoke(); 120 | } 121 | } 122 | 123 | public override void Update(GameTime gameTime) 124 | { 125 | Vector2 MousePosition = new Vector2((float)Main.mouseX, (float)Main.mouseY); 126 | if (!ContainsPoint(MousePosition) && (Main.mouseLeft || Main.mouseRight)) // This solution is fine, but we need a way to cleanly "unload" a UIElement 127 | { 128 | // TODO, figure out how to refocus without triggering unfocus while clicking enable button. 129 | Unfocus(); 130 | } 131 | base.Update(gameTime); 132 | } 133 | 134 | //public void Write(string text) 135 | //{ 136 | // base.SetText(base.Text.Insert(this._cursor, text)); 137 | // this._cursor += text.Length; 138 | // _cursor = Math.Min(Text.Length, _cursor); 139 | // Recalculate(); 140 | 141 | // OnTextChanged?.Invoke(); 142 | //} 143 | 144 | //public void WriteAll(string text) 145 | //{ 146 | // bool changed = text != Text; 147 | // if (!changed) return; 148 | // base.SetText(text); 149 | // this._cursor = text.Length; 150 | // //_cursor = Math.Min(Text.Length, _cursor); 151 | // Recalculate(); 152 | 153 | // if (changed) 154 | // { 155 | // OnTextChanged?.Invoke(); 156 | // } 157 | //} 158 | 159 | public void SetText(string text) 160 | { 161 | if (text.ToString().Length > this._maxLength) 162 | { 163 | text = text.ToString().Substring(0, this._maxLength); 164 | } 165 | if (currentString != text) 166 | { 167 | currentString = text; 168 | OnTextChanged?.Invoke(); 169 | } 170 | } 171 | 172 | public void SetTextMaxLength(int maxLength) 173 | { 174 | this._maxLength = maxLength; 175 | } 176 | 177 | //public void Backspace() 178 | //{ 179 | // if (this._cursor == 0) 180 | // { 181 | // return; 182 | // } 183 | // base.SetText(base.Text.Substring(0, base.Text.Length - 1)); 184 | // Recalculate(); 185 | //} 186 | 187 | /*public void CursorLeft() 188 | { 189 | if (this._cursor == 0) 190 | { 191 | return; 192 | } 193 | this._cursor--; 194 | } 195 | 196 | public void CursorRight() 197 | { 198 | if (this._cursor < base.Text.Length) 199 | { 200 | this._cursor++; 201 | } 202 | }*/ 203 | 204 | private static bool JustPressed(Keys key) 205 | { 206 | return Main.inputText.IsKeyDown(key) && !Main.oldInputText.IsKeyDown(key); 207 | } 208 | 209 | protected override void DrawSelf(SpriteBatch spriteBatch) 210 | { 211 | Rectangle hitbox = GetInnerDimensions().ToRectangle(); 212 | 213 | // Draw panel 214 | base.DrawSelf(spriteBatch); 215 | // Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.Yellow); 216 | 217 | if (focused) 218 | { 219 | Terraria.GameInput.PlayerInput.WritingText = true; 220 | Main.instance.HandleIME(); 221 | string newString = Main.GetInputText(currentString); 222 | if (!newString.Equals(currentString)) 223 | { 224 | currentString = newString; 225 | OnTextChanged?.Invoke(); 226 | } 227 | else 228 | { 229 | currentString = newString; 230 | } 231 | 232 | if (JustPressed(Keys.Tab)) 233 | { 234 | if (unfocusOnTab) Unfocus(); 235 | OnTabPressed?.Invoke(); 236 | } 237 | if (JustPressed(Keys.Enter)) 238 | { 239 | Main.drawingPlayerChat = false; 240 | if (unfocusOnEnter) Unfocus(); 241 | OnEnterPressed?.Invoke(); 242 | } 243 | if (++textBlinkerCount >= 20) 244 | { 245 | textBlinkerState = (textBlinkerState + 1) % 2; 246 | textBlinkerCount = 0; 247 | } 248 | Main.instance.DrawWindowsIMEPanel(new Vector2(98f, (float)(Main.screenHeight - 36)), 0f); 249 | } 250 | string displayString = currentString; 251 | if (this.textBlinkerState == 1 && focused) 252 | { 253 | displayString = displayString + "|"; 254 | } 255 | CalculatedStyle space = base.GetDimensions(); 256 | Color color = Color.Black; 257 | if (currentString.Length == 0) 258 | { 259 | } 260 | Vector2 drawPos = space.Position() + new Vector2(4, 2); 261 | if (currentString.Length == 0 && !focused) 262 | { 263 | color *= 0.5f; 264 | //Utils.DrawBorderString(spriteBatch, hintText, new Vector2(space.X, space.Y), Color.Gray, 1f); 265 | spriteBatch.DrawString(FontAssets.MouseText.Value, hintText, drawPos, color); 266 | } 267 | else 268 | { 269 | //Utils.DrawBorderString(spriteBatch, displayString, drawPos, Color.White, 1f); 270 | spriteBatch.DrawString(FontAssets.MouseText.Value, displayString, drawPos, color); 271 | } 272 | 273 | // CalculatedStyle innerDimensions2 = base.GetInnerDimensions(); 274 | // Vector2 pos2 = innerDimensions2.Position(); 275 | // if (IsLarge) 276 | // { 277 | // pos2.Y -= 10f * TextScale * TextScale; 278 | // } 279 | // else 280 | // { 281 | // pos2.Y -= 2f * TextScale; 282 | // } 283 | // //pos2.X += (innerDimensions2.Width - TextSize.X) * 0.5f; 284 | // if (IsLarge) 285 | // { 286 | // Utils.DrawBorderStringBig(spriteBatch, Text, pos2, TextColor, TextScale, 0f, 0f, -1); 287 | // return; 288 | // } 289 | // Utils.DrawBorderString(spriteBatch, Text, pos2, TextColor, TextScale, 0f, 0f, -1); 290 | // 291 | // this._frameCount++; 292 | // 293 | // CalculatedStyle innerDimensions = base.GetInnerDimensions(); 294 | // Vector2 pos = innerDimensions.Position(); 295 | // DynamicSpriteFont spriteFont = base.IsLarge ? Main.fontDeathText : FontAssets.MouseText.Value; 296 | // Vector2 vector = new Vector2(spriteFont.MeasureString(base.Text.Substring(0, this._cursor)).X, base.IsLarge ? 32f : 16f) * base.TextScale; 297 | // if (base.IsLarge) 298 | // { 299 | // pos.Y -= 8f * base.TextScale; 300 | // } 301 | // else 302 | // { 303 | // pos.Y -= 1f * base.TextScale; 304 | // } 305 | // if (Text.Length == 0) 306 | // { 307 | // Vector2 hintTextSize = new Vector2(spriteFont.MeasureString(hintText.ToString()).X, IsLarge ? 32f : 16f) * TextScale; 308 | // pos.X += 5;//(hintTextSize.X); 309 | // if (base.IsLarge) 310 | // { 311 | // Utils.DrawBorderStringBig(spriteBatch, hintText, pos, Color.Gray, base.TextScale, 0f, 0f, -1); 312 | // return; 313 | // } 314 | // Utils.DrawBorderString(spriteBatch, hintText, pos, Color.Gray, base.TextScale, 0f, 0f, -1); 315 | // pos.X -= 5; 316 | // //pos.X -= (innerDimensions.Width - hintTextSize.X) * 0.5f; 317 | // } 318 | // 319 | // if (!focused) return; 320 | // 321 | // pos.X += /*(innerDimensions.Width - base.TextSize.X) * 0.5f*/ +vector.X - (base.IsLarge ? 8f : 4f) * base.TextScale + 6f; 322 | // if ((this._frameCount %= 40) > 20) 323 | // { 324 | // return; 325 | // } 326 | // if (base.IsLarge) 327 | // { 328 | // Utils.DrawBorderStringBig(spriteBatch, "|", pos, base.TextColor, base.TextScale, 0f, 0f, -1); 329 | // return; 330 | // } 331 | // Utils.DrawBorderString(spriteBatch, "|", pos, base.TextColor, base.TextScale, 0f, 0f, -1); 332 | } 333 | } 334 | } -------------------------------------------------------------------------------- /UIElements/PrettyUITextBox.cs: -------------------------------------------------------------------------------- 1 | using Terraria.UI; 2 | 3 | namespace RecipeBrowser.UIElements 4 | { 5 | internal class PrettyUITextBox : UIElement 6 | { 7 | public PrettyUITextBox() 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /UIElements/ScrollbarHorizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/ScrollbarHorizontal.png -------------------------------------------------------------------------------- /UIElements/ScrollbarInnerHorizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/ScrollbarInnerHorizontal.png -------------------------------------------------------------------------------- /UIElements/TickOnOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/TickOnOff.png -------------------------------------------------------------------------------- /UIElements/UIBestiaryQueryItemSlot.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using Terraria.UI; 3 | 4 | namespace RecipeBrowser.UIElements 5 | { 6 | internal class UIBestiaryQueryItemSlot : UIQueryItemSlot 7 | { 8 | public UIBestiaryQueryItemSlot(Item item) : base(item) 9 | { 10 | } 11 | 12 | public override void LeftClick(UIMouseEvent evt) 13 | { 14 | base.LeftClick(evt); 15 | //BestiaryUI.instance.queryLootItem = (item.type == 0) ? null : item; 16 | ReplaceWithFake(item.type); 17 | BestiaryUI.instance.updateNeeded = true; 18 | } 19 | 20 | internal override void ReplaceWithFake(int type) 21 | { 22 | base.ReplaceWithFake(type); 23 | //BestiaryUI.instance.queryLootItem = item; 24 | BestiaryUI.instance.updateNeeded = true; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /UIElements/UIBottomlessPanel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using ReLogic.Content; 5 | using Terraria.Graphics; 6 | using Terraria.UI; 7 | using Terraria; 8 | using Terraria.GameContent.UI.Elements; 9 | using Terraria.ModLoader; 10 | 11 | namespace RecipeBrowser.UIElements 12 | { 13 | public class UIBottomlessPanel : UIPanel 14 | { 15 | private static int CORNER_SIZE = 12; 16 | private static int BAR_SIZE = 4; 17 | private static Asset _borderTexture; 18 | private static Asset _backgroundTexture; 19 | 20 | public UIBottomlessPanel() 21 | { 22 | if (UIBottomlessPanel._borderTexture == null) 23 | { 24 | UIBottomlessPanel._borderTexture = ModContent.Request("Terraria/Images/UI/PanelBorder"); 25 | } 26 | if (UIBottomlessPanel._backgroundTexture == null) 27 | { 28 | UIBottomlessPanel._backgroundTexture = ModContent.Request("Terraria/Images/UI/PanelBackground"); 29 | } 30 | base.SetPadding((float)UIBottomlessPanel.CORNER_SIZE); 31 | } 32 | 33 | private void DrawPanel(SpriteBatch spriteBatch, Texture2D texture, Color color) 34 | { 35 | CalculatedStyle dimensions = base.GetDimensions(); 36 | Point point = new Point((int)dimensions.X, (int)dimensions.Y); 37 | Point point2 = new Point(point.X + (int)dimensions.Width - UIBottomlessPanel.CORNER_SIZE, point.Y + (int)dimensions.Height); 38 | int width = point2.X - point.X - UIBottomlessPanel.CORNER_SIZE; 39 | int height = point2.Y - point.Y - UIBottomlessPanel.CORNER_SIZE; 40 | spriteBatch.Draw(texture, new Rectangle(point.X, point.Y, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE), new Rectangle?(new Rectangle(0, 0, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE)), color); 41 | spriteBatch.Draw(texture, new Rectangle(point2.X, point.Y, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE), new Rectangle?(new Rectangle(UIBottomlessPanel.CORNER_SIZE + UIBottomlessPanel.BAR_SIZE, 0, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE)), color); 42 | // spriteBatch.Draw(texture, new Rectangle(point.X, point2.Y, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE), new Rectangle?(new Rectangle(0, UIBottomlessPanel.CORNER_SIZE + UIBottomlessPanel.BAR_SIZE, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE)), color); 43 | // spriteBatch.Draw(texture, new Rectangle(point2.X, point2.Y, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE), new Rectangle?(new Rectangle(UIBottomlessPanel.CORNER_SIZE + UIBottomlessPanel.BAR_SIZE, UIBottomlessPanel.CORNER_SIZE + UIBottomlessPanel.BAR_SIZE, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE)), color); 44 | spriteBatch.Draw(texture, new Rectangle(point.X + UIBottomlessPanel.CORNER_SIZE, point.Y, width, UIBottomlessPanel.CORNER_SIZE), new Rectangle?(new Rectangle(UIBottomlessPanel.CORNER_SIZE, 0, UIBottomlessPanel.BAR_SIZE, UIBottomlessPanel.CORNER_SIZE)), color); 45 | // spriteBatch.Draw(texture, new Rectangle(point.X + UIBottomlessPanel.CORNER_SIZE, point2.Y, width, UIBottomlessPanel.CORNER_SIZE), new Rectangle?(new Rectangle(UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE + UIBottomlessPanel.BAR_SIZE, UIBottomlessPanel.BAR_SIZE, UIBottomlessPanel.CORNER_SIZE)), color); 46 | spriteBatch.Draw(texture, new Rectangle(point.X, point.Y + UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE, height), new Rectangle?(new Rectangle(0, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.BAR_SIZE)), color); 47 | spriteBatch.Draw(texture, new Rectangle(point2.X, point.Y + UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE, height), new Rectangle?(new Rectangle(UIBottomlessPanel.CORNER_SIZE + UIBottomlessPanel.BAR_SIZE, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.BAR_SIZE)), color); 48 | spriteBatch.Draw(texture, new Rectangle(point.X + UIBottomlessPanel.CORNER_SIZE, point.Y + UIBottomlessPanel.CORNER_SIZE, width, height), new Rectangle?(new Rectangle(UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.CORNER_SIZE, UIBottomlessPanel.BAR_SIZE, UIBottomlessPanel.BAR_SIZE)), color); 49 | } 50 | 51 | protected override void DrawSelf(SpriteBatch spriteBatch) 52 | { 53 | this.DrawPanel(spriteBatch, UIBottomlessPanel._backgroundTexture.Value, this.BackgroundColor); 54 | this.DrawPanel(spriteBatch, UIBottomlessPanel._borderTexture.Value, this.BorderColor); 55 | 56 | //Rectangle hitbox = GetInnerDimensions().ToRectangle(); 57 | //Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.Red * 0.6f); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /UIElements/UICheckbox.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using ReLogic.Content; 5 | using Terraria; 6 | using Terraria.GameContent.UI.Elements; 7 | using Terraria.UI; 8 | 9 | namespace RecipeBrowser.UIElements 10 | { 11 | internal class UICheckbox : UIText 12 | { 13 | public static Asset checkboxTexture; 14 | public static Asset checkmarkTexture; 15 | 16 | public event EventHandler OnSelectedChanged; 17 | 18 | private bool selected = false; 19 | private bool disabled = false; 20 | internal string hoverText; 21 | 22 | public bool Selected 23 | { 24 | get { return selected; } 25 | set 26 | { 27 | if (value != selected) 28 | { 29 | selected = value; 30 | OnSelectedChanged?.Invoke(this, EventArgs.Empty); 31 | } 32 | } 33 | } 34 | 35 | public UICheckbox(string text, string hoverText, float textScale = 1, bool large = false) : base(text, textScale, large) 36 | { 37 | this.Left.Pixels += 20; 38 | //TextColor = Color.Blue; 39 | text = " " + text; 40 | this.hoverText = hoverText; 41 | SetText(text); 42 | OnLeftClick += UICheckbox_onLeftClick; 43 | Recalculate(); 44 | } 45 | 46 | private void UICheckbox_onLeftClick(UIMouseEvent evt, UIElement listeningElement) 47 | { 48 | if (disabled) return; 49 | this.Selected = !Selected; 50 | } 51 | 52 | public void SetDisabled(bool disabled = true) 53 | { 54 | this.disabled = disabled; 55 | if (disabled) 56 | { 57 | Selected = false; 58 | } 59 | TextColor = disabled ? Color.Gray : Color.White; 60 | } 61 | public void SetHoverText(string hoverText) 62 | { 63 | this.hoverText = hoverText; 64 | } 65 | 66 | protected override void DrawSelf(SpriteBatch spriteBatch) 67 | { 68 | base.DrawSelf(spriteBatch); 69 | 70 | CalculatedStyle innerDimensions = base.GetInnerDimensions(); 71 | Vector2 pos = new Vector2(innerDimensions.X, innerDimensions.Y - 5); 72 | 73 | //Rectangle hitbox = GetInnerDimensions().ToRectangle(); 74 | //Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.Red * 0.6f); 75 | 76 | spriteBatch.Draw(checkboxTexture.Value, pos, null, disabled ? Color.Gray : Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f); 77 | if (Selected) 78 | spriteBatch.Draw(checkmarkTexture.Value, pos, null, disabled ? Color.Gray : Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f); 79 | 80 | if (IsMouseHovering) 81 | { 82 | // Main.hoverItemName = hoverText; 83 | if (!string.IsNullOrWhiteSpace(hoverText)) 84 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(hoverText); 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /UIElements/UICraftButton.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Terraria; 4 | using Terraria.Audio; 5 | using Terraria.DataStructures; 6 | using Terraria.GameContent; 7 | using Terraria.ModLoader; 8 | using Terraria.UI; 9 | using Terraria.UI.Chat; 10 | using Terraria.ID; 11 | 12 | namespace RecipeBrowser.UIElements 13 | { 14 | internal class UICraftButton : UIElement 15 | { 16 | CraftPath.RecipeNode recipeNode; 17 | Recipe recipe; 18 | int index = -1; 19 | public UICraftButton(CraftPath.RecipeNode recipeNode, Recipe recipe) 20 | { 21 | this.recipe = recipe; 22 | this.recipeNode = recipeNode; 23 | this.Width.Set(TextureAssets.Reforge[0].Width(), 0f); 24 | this.Height.Set(TextureAssets.Reforge[0].Height(), 0f); 25 | for (int i = 0; i < Recipe.numRecipes; i++) 26 | { 27 | if (recipe == Main.recipe[i]) 28 | { 29 | index = i; 30 | break; 31 | } 32 | } 33 | if (index == -1) 34 | throw new System.Exception("Index is -1??"); 35 | } 36 | 37 | protected override void DrawSelf(SpriteBatch spriteBatch) 38 | { 39 | bool ableToCraft = AbleToCraft(); 40 | //if (AbleToCraft()) 41 | { 42 | CalculatedStyle dimensions = base.GetDimensions(); 43 | spriteBatch.Draw(TextureAssets.Reforge[IsMouseHovering && ableToCraft ? 1 : 0].Value, dimensions.Position(), null, Color.White, 0, Vector2.Zero, 0.75f, SpriteEffects.None, 0); 44 | ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.ItemStack.Value, ableToCraft ? "✓" : "X", dimensions.Position() + new Vector2(14f, 10f), ableToCraft ? Utilities.yesColor : Color.LightSalmon, 0f, Vector2.Zero, new Vector2(0.7f)); 45 | if (IsMouseHovering) { 46 | // Main.hoverItemName = ableToCraft ? "Craft" : ""; // "Craft ingredients first"? 47 | if (ableToCraft) 48 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(CraftUI.RBText("Craft")); 49 | } 50 | } 51 | } 52 | 53 | public override void MouseOver(UIMouseEvent evt) 54 | { 55 | base.MouseOver(evt); 56 | if (AbleToCraft()) 57 | { 58 | SoundEngine.PlaySound(SoundID.MenuTick); 59 | } 60 | } 61 | 62 | public override void LeftClick(UIMouseEvent evt) 63 | { 64 | base.LeftClick(evt); 65 | for (int i = 0; i < recipeNode.multiplier; i++) 66 | { 67 | Recipe.FindRecipes(); 68 | if (AbleToCraft()) 69 | { 70 | //Main.CraftItem(recipe); 71 | Item result = recipe.createItem.Clone(); 72 | result.Prefix(-1); 73 | // Consumes the items. Does not actually check required items or tiles. 74 | recipe.Create(); 75 | // TODO: Alternate recipe.Create that takes from all sources. 76 | result.position = Main.LocalPlayer.Center - result.Size; // needed for ItemText 77 | 78 | RecipeLoader.OnCraft(result, recipe, null); // There is no destination, I don't put on mouse, I directly pick up. Is this and next line going to cause issues? 79 | // Duplicate call? I don't think this is needed: 80 | // ItemLoader.OnCreate(result, new RecipeItemCreationContext { recipe = recipe }); 81 | 82 | Item itemIfNoSpace = Main.LocalPlayer.GetItem(Main.myPlayer, result, GetItemSettings.PickupItemFromWorld); 83 | if (itemIfNoSpace.stack > 0) 84 | { 85 | Main.LocalPlayer.QuickSpawnItem(Main.LocalPlayer.GetSource_Misc("PlayerDropItemCheck"), itemIfNoSpace, itemIfNoSpace.stack); 86 | } 87 | } 88 | else 89 | { 90 | //Main.NewText("Oops, I couldn't actually craft that for you."); 91 | } 92 | } 93 | } 94 | 95 | // Buggy if inventory not open. 96 | bool AbleToCraft() 97 | { 98 | bool flag = Main.guideItem.type > 0 && Main.guideItem.stack > 0 && Main.guideItem.Name != ""; 99 | if (flag) 100 | return false; // Potential bug with guideItem affecting availableRecipe 101 | for (int n = 0; n < Main.numAvailableRecipes; n++) 102 | { 103 | if (index == Main.availableRecipe[n]) 104 | { 105 | return true; 106 | } 107 | } 108 | return false; 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /UIElements/UICraftQueryItemSlot.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using Terraria.UI; 3 | 4 | namespace RecipeBrowser.UIElements 5 | { 6 | class UICraftQueryItemSlot : UIQueryItemSlot 7 | { 8 | public UICraftQueryItemSlot(Item item) : base(item) 9 | { 10 | } 11 | 12 | public override void LeftClick(UIMouseEvent evt) 13 | { 14 | base.LeftClick(evt); 15 | CraftUI.instance.SetItem(item.type); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /UIElements/UICycleImage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using ReLogic.Content; 5 | using Terraria; 6 | using Terraria.UI; 7 | 8 | namespace RecipeBrowser.UIElements 9 | { 10 | internal class UICycleImage : UIElement 11 | { 12 | private Asset texture; 13 | private int _drawWidth; 14 | private int _drawHeight; 15 | private int padding; 16 | private int textureOffsetX; 17 | private int textureOffsetY; 18 | private int states; 19 | internal string[] hoverTexts; 20 | 21 | public event EventHandler OnStateChanged; 22 | 23 | private int currentState = 0; 24 | public int CurrentState 25 | { 26 | get { return currentState; } 27 | set 28 | { 29 | if (value != currentState) 30 | { 31 | currentState = value; 32 | OnStateChanged?.Invoke(this, EventArgs.Empty); 33 | } 34 | } 35 | } 36 | 37 | public UICycleImage(Asset texture, int states, string[] hoverTexts, int width, int height, int textureOffsetX = 0, int textureOffsetY = 0, int padding = 2) 38 | { 39 | this.texture = texture; 40 | this._drawWidth = width; 41 | this._drawHeight = height; 42 | this.textureOffsetX = textureOffsetX; 43 | this.textureOffsetY = textureOffsetY; 44 | this.Width.Set((float)width, 0f); 45 | this.Height.Set((float)height, 0f); 46 | this.states = states; 47 | this.padding = padding; 48 | this.hoverTexts = hoverTexts; 49 | } 50 | 51 | protected override void DrawSelf(SpriteBatch spriteBatch) 52 | { 53 | CalculatedStyle dimensions = base.GetDimensions(); 54 | Point point = new Point(textureOffsetX, textureOffsetY + ((padding + _drawHeight) * currentState)); 55 | Color color = base.IsMouseHovering ? Color.White : Color.Silver; 56 | spriteBatch.Draw(texture.Value, new Rectangle((int)dimensions.X, (int)dimensions.Y, this._drawWidth, this._drawHeight), new Rectangle?(new Rectangle(point.X, point.Y, this._drawWidth, this._drawHeight)), color); 57 | if (IsMouseHovering) 58 | { 59 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(hoverTexts[CurrentState]); 60 | } 61 | } 62 | 63 | public override void LeftClick(UIMouseEvent evt) 64 | { 65 | CurrentState = (currentState + 1) % states; 66 | base.LeftClick(evt); 67 | } 68 | 69 | public override void RightClick(UIMouseEvent evt) 70 | { 71 | CurrentState = (currentState + states - 1) % states; 72 | base.RightClick(evt); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /UIElements/UIDragableElement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using ReLogic.Content; 5 | using Terraria; 6 | using Terraria.Graphics; 7 | using Terraria.ModLoader; 8 | using Terraria.UI; 9 | 10 | namespace RecipeBrowser 11 | { 12 | internal class UIDragableElement : UIElement 13 | { 14 | private static Asset dragTexture; 15 | private Vector2 offset; 16 | private bool dragable; 17 | private bool dragging; 18 | private bool resizeableX; 19 | private bool resizeableY; 20 | private bool resizeable => resizeableX || resizeableY; 21 | private bool resizeing; 22 | 23 | //private int minX, minY, maxX, maxY; 24 | private List additionalDragTargets; 25 | 26 | // TODO, move panel back in if offscreen? prevent drag off screen? 27 | public UIDragableElement(bool dragable = true, bool resizeableX = false, bool resizeableY = false) 28 | { 29 | this.dragable = dragable; 30 | this.resizeableX = resizeableX; 31 | this.resizeableY = resizeableY; 32 | if (dragTexture == null) 33 | { 34 | dragTexture = ModContent.Request("Terraria/Images/UI/PanelBorder"); 35 | } 36 | additionalDragTargets = new List(); 37 | } 38 | 39 | public void AddDragTarget(UIElement element) 40 | { 41 | additionalDragTargets.Add(element); 42 | } 43 | 44 | //public void SetMinMaxWidth(int min, int max) 45 | //{ 46 | // this.minX = min; 47 | // this.maxX = max; 48 | //} 49 | 50 | //public void SetMinMaxHeight(int min, int max) 51 | //{ 52 | // this.minY = min; 53 | // this.maxY = max; 54 | //} 55 | 56 | public override void LeftMouseDown(UIMouseEvent evt) 57 | { 58 | DragStart(evt); 59 | base.LeftMouseDown(evt); 60 | } 61 | 62 | public override void LeftMouseUp(UIMouseEvent evt) 63 | { 64 | DragEnd(evt); 65 | base.LeftMouseUp(evt); 66 | } 67 | 68 | private void DragStart(UIMouseEvent evt) 69 | { 70 | CalculatedStyle innerDimensions = GetInnerDimensions(); 71 | if (evt.Target == this || additionalDragTargets.Contains(evt.Target)) 72 | { 73 | if (resizeable && new Rectangle((int)(innerDimensions.X + innerDimensions.Width - 18), (int)(innerDimensions.Y + innerDimensions.Height - 18), 18, 18).Contains(evt.MousePosition.ToPoint())) 74 | //if (resizeable && new Rectangle((int)(innerDimensions.X + innerDimensions.Width - 12), (int)(innerDimensions.Y + innerDimensions.Height - 12), 12 + 6, 12 + 6).Contains(evt.MousePosition.ToPoint())) 75 | { 76 | offset = new Vector2(evt.MousePosition.X - innerDimensions.X - innerDimensions.Width, evt.MousePosition.Y - innerDimensions.Y - innerDimensions.Height); 77 | //offset = new Vector2(evt.MousePosition.X - innerDimensions.X - innerDimensions.Width - 6, evt.MousePosition.Y - innerDimensions.Y - innerDimensions.Height - 6); 78 | resizeing = true; 79 | } 80 | else if (dragable) 81 | { 82 | offset = new Vector2(evt.MousePosition.X - Left.Pixels, evt.MousePosition.Y - Top.Pixels); 83 | dragging = true; 84 | } 85 | } 86 | } 87 | 88 | private void DragEnd(UIMouseEvent evt) 89 | { 90 | if (evt.Target == this || additionalDragTargets.Contains(evt.Target)) 91 | { 92 | dragging = false; 93 | resizeing = false; 94 | } 95 | if(this == RecipeBrowserUI.instance.mainPanel) { 96 | RecipeBrowserClientConfig config = ModContent.GetInstance(); 97 | CalculatedStyle dimensions = GetOuterDimensions(); // Drag can go negative, need clamped by Min and Max values 98 | config.RecipeBrowserSize = new Vector2(dimensions.Width, dimensions.Height); 99 | config.RecipeBrowserPosition = new Vector2(Left.Pixels, Top.Pixels); 100 | RecipeBrowserClientConfig.SaveConfig(); 101 | } 102 | } 103 | 104 | protected override void DrawSelf(SpriteBatch spriteBatch) 105 | { 106 | //Rectangle hitbox = GetInnerDimensions().ToRectangle(); 107 | //Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.Red * 0.6f); 108 | 109 | CalculatedStyle dimensions = base.GetOuterDimensions(); 110 | if (ContainsPoint(Main.MouseScreen)) 111 | { 112 | Main.LocalPlayer.mouseInterface = true; 113 | Main.LocalPlayer.cursorItemIconEnabled = false; 114 | Main.ItemIconCacheUpdate(0); 115 | } 116 | if (dragging) 117 | { 118 | Left.Set(Main.MouseScreen.X - offset.X, 0f); 119 | Top.Set(Main.MouseScreen.Y - offset.Y, 0f); 120 | Recalculate(); 121 | } 122 | if (resizeing) 123 | { 124 | if (resizeableX) 125 | { 126 | //Width.Pixels = Utils.Clamp(Main.MouseScreen.X - dimensions.X - offset.X, minX, maxX); 127 | Width.Pixels = Main.MouseScreen.X - dimensions.X - offset.X; 128 | } 129 | if (resizeableY) 130 | { 131 | //Height.Pixels = Utils.Clamp(Main.MouseScreen.Y - dimensions.Y - offset.Y, minY, maxY); 132 | Height.Pixels = Main.MouseScreen.Y - dimensions.Y - offset.Y; 133 | } 134 | Recalculate(); 135 | } 136 | base.DrawSelf(spriteBatch); 137 | } 138 | 139 | protected override void DrawChildren(SpriteBatch spriteBatch) 140 | { 141 | base.DrawChildren(spriteBatch); 142 | if (resizeable) 143 | { 144 | DrawDragAnchor(spriteBatch, dragTexture.Value, Color.Black/*this.BorderColor*/); 145 | } 146 | } 147 | 148 | private void DrawDragAnchor(SpriteBatch spriteBatch, Texture2D texture, Color color) 149 | { 150 | CalculatedStyle dimensions = GetOuterDimensions(); 151 | 152 | //Rectangle hitbox = GetInnerDimensions().ToRectangle(); 153 | //hitbox = new Rectangle((int)(innerDimensions.X + innerDimensions.Width - 18), (int)(innerDimensions.Y + innerDimensions.Height - 18), 18, 18); 154 | //Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.LightBlue * 0.6f); 155 | 156 | Point point = new Point((int)(dimensions.X + dimensions.Width - 12), (int)(dimensions.Y + dimensions.Height - 12)); 157 | spriteBatch.Draw(texture, new Rectangle(point.X - 2, point.Y - 2, 12 - 2, 12 - 2), new Rectangle(12 + 4, 12 + 4, 12, 12), color); 158 | spriteBatch.Draw(texture, new Rectangle(point.X - 4, point.Y - 4, 12 - 4, 12 - 4), new Rectangle(12 + 4, 12 + 4, 12, 12), color); 159 | spriteBatch.Draw(texture, new Rectangle(point.X - 6, point.Y - 6, 12 - 6, 12 - 6), new Rectangle(12 + 4, 12 + 4, 12, 12), color); 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /UIElements/UIDragablePanel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using ReLogic.Content; 5 | using Terraria; 6 | using Terraria.GameContent.UI.Elements; 7 | using Terraria.Graphics; 8 | using Terraria.ModLoader; 9 | using Terraria.UI; 10 | 11 | namespace RecipeBrowser 12 | { 13 | internal class UIDragablePanel : UIPanel 14 | { 15 | private static Asset dragTexture; 16 | private Vector2 offset; 17 | private bool dragable; 18 | private bool dragging; 19 | private bool resizeableX; 20 | private bool resizeableY; 21 | private bool resizeable => resizeableX || resizeableY; 22 | private bool resizeing; 23 | 24 | //private int minX, minY, maxX, maxY; 25 | private List additionalDragTargets; 26 | 27 | // TODO, move panel back in if offscreen? prevent drag off screen? 28 | public UIDragablePanel(bool dragable = true, bool resizeableX = false, bool resizeableY = false) 29 | { 30 | this.dragable = dragable; 31 | this.resizeableX = resizeableX; 32 | this.resizeableY = resizeableY; 33 | if (dragTexture == null) 34 | { 35 | dragTexture = ModContent.Request("Terraria/Images/UI/PanelBorder"); 36 | } 37 | additionalDragTargets = new List(); 38 | } 39 | 40 | public void AddDragTarget(UIElement element) 41 | { 42 | additionalDragTargets.Add(element); 43 | } 44 | 45 | //public void SetMinMaxWidth(int min, int max) 46 | //{ 47 | // this.minX = min; 48 | // this.maxX = max; 49 | //} 50 | 51 | //public void SetMinMaxHeight(int min, int max) 52 | //{ 53 | // this.minY = min; 54 | // this.maxY = max; 55 | //} 56 | 57 | public override void LeftMouseDown(UIMouseEvent evt) 58 | { 59 | DragStart(evt); 60 | base.LeftMouseDown(evt); 61 | } 62 | 63 | public override void LeftMouseUp(UIMouseEvent evt) 64 | { 65 | DragEnd(evt); 66 | base.LeftMouseUp(evt); 67 | } 68 | 69 | private void DragStart(UIMouseEvent evt) 70 | { 71 | CalculatedStyle innerDimensions = GetInnerDimensions(); 72 | if (evt.Target == this || additionalDragTargets.Contains(evt.Target)) 73 | { 74 | if (resizeable && new Rectangle((int)(innerDimensions.X + innerDimensions.Width - 12), (int)(innerDimensions.Y + innerDimensions.Height - 12), 12 + 6, 12 + 6).Contains(evt.MousePosition.ToPoint())) 75 | { 76 | offset = new Vector2(evt.MousePosition.X - innerDimensions.X - innerDimensions.Width - 6, evt.MousePosition.Y - innerDimensions.Y - innerDimensions.Height - 6); 77 | resizeing = true; 78 | } 79 | else if (dragable) 80 | { 81 | offset = new Vector2(evt.MousePosition.X - Left.Pixels, evt.MousePosition.Y - Top.Pixels); 82 | dragging = true; 83 | } 84 | } 85 | } 86 | 87 | private void DragEnd(UIMouseEvent evt) 88 | { 89 | if (evt.Target == this || additionalDragTargets.Contains(evt.Target)) 90 | { 91 | dragging = false; 92 | resizeing = false; 93 | } 94 | if (this == RecipeBrowserUI.instance.favoritePanel) { 95 | RecipeBrowserClientConfig config = ModContent.GetInstance(); 96 | config.FavoritedRecipePanelPosition = new Vector2(Left.Pixels, Top.Pixels); 97 | RecipeBrowserClientConfig.SaveConfig(); 98 | } 99 | } 100 | 101 | protected override void DrawSelf(SpriteBatch spriteBatch) 102 | { 103 | CalculatedStyle dimensions = base.GetOuterDimensions(); 104 | if (ContainsPoint(Main.MouseScreen)) 105 | { 106 | Main.LocalPlayer.mouseInterface = true; 107 | Main.LocalPlayer.cursorItemIconEnabled = false; 108 | Main.ItemIconCacheUpdate(0); 109 | } 110 | if (dragging) 111 | { 112 | Left.Set(Main.MouseScreen.X - offset.X, 0f); 113 | Top.Set(Main.MouseScreen.Y - offset.Y, 0f); 114 | Recalculate(); 115 | } 116 | else 117 | { 118 | if (Parent != null && !dimensions.ToRectangle().Intersects(Parent.GetDimensions().ToRectangle())) 119 | { 120 | var parentSpace = Parent.GetDimensions().ToRectangle(); 121 | Left.Pixels = Utils.Clamp(Left.Pixels, Width.Pixels - parentSpace.Right, 0); // TODO: Adjust automatically for Left.Percent (measure from left or right edge) 122 | Top.Pixels = Utils.Clamp(Top.Pixels, 0, parentSpace.Bottom - Height.Pixels); 123 | Recalculate(); 124 | } 125 | } 126 | if (resizeing) 127 | { 128 | if (resizeableX) 129 | { 130 | //Width.Pixels = Utils.Clamp(Main.MouseScreen.X - dimensions.X - offset.X, minX, maxX); 131 | Width.Pixels = Main.MouseScreen.X - dimensions.X - offset.X; 132 | } 133 | if (resizeableY) 134 | { 135 | //Height.Pixels = Utils.Clamp(Main.MouseScreen.Y - dimensions.Y - offset.Y, minY, maxY); 136 | Height.Pixels = Main.MouseScreen.Y - dimensions.Y - offset.Y; 137 | } 138 | Recalculate(); 139 | } 140 | base.DrawSelf(spriteBatch); 141 | if (resizeable) 142 | { 143 | DrawDragAnchor(spriteBatch, dragTexture.Value, this.BorderColor); 144 | } 145 | } 146 | 147 | private void DrawDragAnchor(SpriteBatch spriteBatch, Texture2D texture, Color color) 148 | { 149 | CalculatedStyle dimensions = GetDimensions(); 150 | 151 | // Rectangle hitbox = GetInnerDimensions().ToRectangle(); 152 | // Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.LightBlue * 0.6f); 153 | 154 | Point point = new Point((int)(dimensions.X + dimensions.Width - 12), (int)(dimensions.Y + dimensions.Height - 12)); 155 | spriteBatch.Draw(texture, new Rectangle(point.X - 2, point.Y - 2, 12 - 2, 12 - 2), new Rectangle(12 + 4, 12 + 4, 12, 12), color); 156 | spriteBatch.Draw(texture, new Rectangle(point.X - 4, point.Y - 4, 12 - 4, 12 - 4), new Rectangle(12 + 4, 12 + 4, 12, 12), color); 157 | spriteBatch.Draw(texture, new Rectangle(point.X - 6, point.Y - 6, 12 - 6, 12 - 6), new Rectangle(12 + 4, 12 + 4, 12, 12), color); 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /UIElements/UIGrid.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Content; 4 | using System; 5 | using System.Collections.Generic; 6 | using Terraria; 7 | using Terraria.GameContent.UI.Elements; 8 | using Terraria.UI; 9 | 10 | namespace RecipeBrowser 11 | { 12 | public class UIGrid : UIElement 13 | { 14 | public delegate bool ElementSearchMethod(UIElement element); 15 | 16 | private class UIInnerList : UIElement 17 | { 18 | public override bool ContainsPoint(Vector2 point) 19 | { 20 | return true; 21 | } 22 | 23 | protected override void DrawChildren(SpriteBatch spriteBatch) 24 | { 25 | Vector2 position = this.Parent.GetDimensions().Position(); 26 | Vector2 dimensions = new Vector2(this.Parent.GetDimensions().Width, this.Parent.GetDimensions().Height); 27 | foreach (UIElement current in this.Elements) 28 | { 29 | Vector2 position2 = current.GetDimensions().Position(); 30 | Vector2 dimensions2 = new Vector2(current.GetDimensions().Width, current.GetDimensions().Height); 31 | if (Collision.CheckAABBvAABBCollision(position, dimensions, position2, dimensions2)) 32 | { 33 | current.Draw(spriteBatch); 34 | } 35 | } 36 | } 37 | } 38 | 39 | public List _items = new List(); 40 | protected UIScrollbar _scrollbar; 41 | internal UIElement _innerList = new UIGrid.UIInnerList(); 42 | private float _innerListHeight; 43 | public float ListPadding = 5f; 44 | 45 | public static Asset moreUpTexture; 46 | public static Asset moreDownTexture; 47 | 48 | public int Count 49 | { 50 | get 51 | { 52 | return this._items.Count; 53 | } 54 | } 55 | 56 | // todo, vertical/horizontal orientation, left to right, etc? 57 | public UIGrid() 58 | { 59 | this._innerList.OverflowHidden = false; 60 | this._innerList.Width.Set(0f, 1f); 61 | this._innerList.Height.Set(0f, 1f); 62 | this.OverflowHidden = true; 63 | base.Append(this._innerList); 64 | } 65 | 66 | public float GetTotalHeight() 67 | { 68 | return this._innerListHeight; 69 | } 70 | 71 | public void Goto(UIGrid.ElementSearchMethod searchMethod, bool center = false, bool fuzzy = false) 72 | { 73 | var innerDimensionHeight = GetInnerDimensions().Height; 74 | for (int i = 0; i < this._items.Count; i++) 75 | { 76 | var item = this._items[i]; 77 | if (searchMethod(item)) 78 | { 79 | if (fuzzy) 80 | { 81 | if (item.Top.Pixels > _scrollbar.ViewPosition && item.Top.Pixels + item.GetOuterDimensions().Height < _scrollbar.ViewPosition + innerDimensionHeight) 82 | return; 83 | } 84 | this._scrollbar.ViewPosition = item.Top.Pixels; 85 | if (center) 86 | { 87 | this._scrollbar.ViewPosition = item.Top.Pixels - innerDimensionHeight / 2 + item.GetOuterDimensions().Height / 2; 88 | } 89 | return; 90 | } 91 | } 92 | } 93 | 94 | public virtual void Add(UIElement item) 95 | { 96 | this._items.Add(item); 97 | this._innerList.Append(item); 98 | this.UpdateOrder(); 99 | this._innerList.Recalculate(); 100 | } 101 | 102 | public virtual void AddRange(IEnumerable items) 103 | { 104 | this._items.AddRange(items); 105 | foreach (var item in items) 106 | this._innerList.Append(item); 107 | this.UpdateOrder(); 108 | this._innerList.Recalculate(); 109 | } 110 | 111 | public virtual bool Remove(UIElement item) 112 | { 113 | this._innerList.RemoveChild(item); 114 | this.UpdateOrder(); 115 | return this._items.Remove(item); 116 | } 117 | 118 | public virtual void Clear() 119 | { 120 | this._innerList.RemoveAllChildren(); 121 | this._items.Clear(); 122 | } 123 | 124 | public override void Recalculate() 125 | { 126 | base.Recalculate(); 127 | this.UpdateScrollbar(); 128 | } 129 | 130 | public override void ScrollWheel(UIScrollWheelEvent evt) 131 | { 132 | base.ScrollWheel(evt); 133 | if (this._scrollbar != null) 134 | { 135 | this._scrollbar.ViewPosition -= (float)evt.ScrollWheelValue; 136 | } 137 | } 138 | 139 | public override void RecalculateChildren() 140 | { 141 | float availableWidth = GetInnerDimensions().Width; 142 | base.RecalculateChildren(); 143 | float top = 0f; 144 | float left = 0f; 145 | float maxRowHeight = 0f; 146 | for (int i = 0; i < this._items.Count; i++) 147 | { 148 | var item = this._items[i]; 149 | var outerDimensions = item.GetOuterDimensions(); 150 | if (left + outerDimensions.Width > availableWidth && left > 0) 151 | { 152 | top += maxRowHeight + this.ListPadding; 153 | left = 0; 154 | maxRowHeight = 0; 155 | } 156 | maxRowHeight = Math.Max(maxRowHeight, outerDimensions.Height); 157 | item.Left.Set(left, 0f); 158 | left += outerDimensions.Width + this.ListPadding; 159 | item.Top.Set(top, 0f); 160 | item.Recalculate(); 161 | } 162 | this._innerListHeight = top + maxRowHeight; 163 | } 164 | 165 | private void UpdateScrollbar() 166 | { 167 | if (this._scrollbar == null) 168 | { 169 | return; 170 | } 171 | this._scrollbar.SetView(base.GetInnerDimensions().Height, this._innerListHeight); 172 | } 173 | 174 | public void SetScrollbar(UIScrollbar scrollbar) 175 | { 176 | this._scrollbar = scrollbar; 177 | this.UpdateScrollbar(); 178 | } 179 | 180 | //internal delegate int ElementSort(UIElement item1, UIElement item2); 181 | internal Comparison alternateSort; 182 | public void UpdateOrder() 183 | { 184 | if (alternateSort != null) 185 | this._items.Sort(alternateSort); 186 | else 187 | this._items.Sort(new Comparison(this.SortMethod)); 188 | this.UpdateScrollbar(); 189 | } 190 | 191 | public int SortMethod(UIElement item1, UIElement item2) 192 | { 193 | return item1.CompareTo(item2); 194 | } 195 | 196 | public override List GetSnapPoints() 197 | { 198 | List list = new List(); 199 | SnapPoint item; 200 | if (base.GetSnapPoint(out item)) 201 | { 202 | list.Add(item); 203 | } 204 | foreach (UIElement current in this._items) 205 | { 206 | list.AddRange(current.GetSnapPoints()); 207 | } 208 | return list; 209 | } 210 | 211 | protected override void DrawSelf(SpriteBatch spriteBatch) 212 | { 213 | if (this._scrollbar != null) 214 | { 215 | this._innerList.Top.Set(-this._scrollbar.GetValue(), 0f); 216 | } 217 | if (IsMouseHovering) 218 | Terraria.GameInput.PlayerInput.LockVanillaMouseScroll("RecipeBrowser/UIHorizontalGrid"); 219 | this.Recalculate(); 220 | } 221 | 222 | public bool drawArrows; 223 | protected override void DrawChildren(SpriteBatch spriteBatch) { 224 | base.DrawChildren(spriteBatch); 225 | if (drawArrows) { 226 | var inner = GetInnerDimensions().ToRectangle(); 227 | if (this._scrollbar.ViewPosition != 0) { 228 | int centeredX = inner.X + inner.Width / 2 - moreUpTexture.Width() / 2; 229 | spriteBatch.Draw(moreUpTexture.Value, new Vector2(centeredX, inner.Y), Color.White * .5f); 230 | } 231 | if (this._scrollbar.ViewPosition < _innerListHeight - inner.Height) { 232 | int centeredX = inner.X + inner.Width / 2 - moreUpTexture.Width() / 2; 233 | spriteBatch.Draw(moreDownTexture.Value, new Vector2(centeredX, inner.Bottom - moreDownTexture.Height()), Color.White * .5f); 234 | } 235 | } 236 | } 237 | } 238 | } -------------------------------------------------------------------------------- /UIElements/UIHorizontalGrid.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using RecipeBrowser.UIElements; 4 | using System; 5 | using System.Collections.Generic; 6 | using ReLogic.Content; 7 | using Terraria; 8 | using Terraria.UI; 9 | using Terraria.GameInput; 10 | 11 | namespace RecipeBrowser 12 | { 13 | public class UIHorizontalGrid : UIElement 14 | { 15 | public delegate bool ElementSearchMethod(UIElement element); 16 | 17 | private class UIInnerList : UIElement 18 | { 19 | public override bool ContainsPoint(Vector2 point) 20 | { 21 | return true; 22 | } 23 | 24 | protected override void DrawChildren(SpriteBatch spriteBatch) 25 | { 26 | Vector2 position = this.Parent.GetDimensions().Position(); 27 | Vector2 dimensions = new Vector2(this.Parent.GetDimensions().Width, this.Parent.GetDimensions().Height); 28 | foreach (UIElement current in this.Elements) 29 | { 30 | Vector2 position2 = current.GetDimensions().Position(); 31 | Vector2 dimensions2 = new Vector2(current.GetDimensions().Width, current.GetDimensions().Height); 32 | if (Collision.CheckAABBvAABBCollision(position, dimensions, position2, dimensions2)) 33 | { 34 | current.Draw(spriteBatch); 35 | } 36 | } 37 | } 38 | } 39 | 40 | public List _items = new List(); 41 | protected UIHorizontalScrollbar _scrollbar; 42 | internal UIElement _innerList = new UIHorizontalGrid.UIInnerList(); 43 | private float _innerListWidth; 44 | public float ListPadding = 5f; 45 | 46 | public static Asset moreLeftTexture; 47 | public static Asset moreRightTexture; 48 | 49 | public int Count 50 | { 51 | get 52 | { 53 | return this._items.Count; 54 | } 55 | } 56 | 57 | // todo, vertical/horizontal orientation, left to right, etc? 58 | public UIHorizontalGrid() 59 | { 60 | this._innerList.OverflowHidden = false; 61 | this._innerList.Width.Set(0f, 1f); 62 | this._innerList.Height.Set(0f, 1f); 63 | this.OverflowHidden = true; 64 | base.Append(this._innerList); 65 | } 66 | 67 | public float GetTotalWidth() 68 | { 69 | return this._innerListWidth; 70 | } 71 | 72 | public void Goto(UIHorizontalGrid.ElementSearchMethod searchMethod, bool center = false) 73 | { 74 | for (int i = 0; i < this._items.Count; i++) 75 | { 76 | if (searchMethod(this._items[i])) 77 | { 78 | this._scrollbar.ViewPosition = this._items[i].Left.Pixels; 79 | if (center) 80 | { 81 | this._scrollbar.ViewPosition = this._items[i].Left.Pixels - GetInnerDimensions().Width / 2 + _items[i].GetOuterDimensions().Width / 2; 82 | } 83 | return; 84 | } 85 | } 86 | } 87 | 88 | public virtual void Add(UIElement item) 89 | { 90 | this._items.Add(item); 91 | this._innerList.Append(item); 92 | this.UpdateOrder(); 93 | this._innerList.Recalculate(); 94 | } 95 | 96 | public virtual void AddRange(IEnumerable items) 97 | { 98 | this._items.AddRange(items); 99 | foreach (var item in items) 100 | this._innerList.Append(item); 101 | this.UpdateOrder(); 102 | this._innerList.Recalculate(); 103 | } 104 | 105 | public virtual bool Remove(UIElement item) 106 | { 107 | this._innerList.RemoveChild(item); 108 | this.UpdateOrder(); 109 | return this._items.Remove(item); 110 | } 111 | 112 | public virtual void Clear() 113 | { 114 | this._innerList.RemoveAllChildren(); 115 | this._items.Clear(); 116 | } 117 | 118 | public override void Recalculate() 119 | { 120 | base.Recalculate(); 121 | this.UpdateScrollbar(); 122 | } 123 | 124 | public override void ScrollWheel(UIScrollWheelEvent evt) 125 | { 126 | base.ScrollWheel(evt); 127 | if (this._scrollbar != null) 128 | { 129 | this._scrollbar.ViewPosition -= (float)evt.ScrollWheelValue; 130 | } 131 | } 132 | 133 | public override void RecalculateChildren() 134 | { 135 | float availableHeight = GetInnerDimensions().Height; 136 | base.RecalculateChildren(); 137 | float left = 0f; 138 | float top = 0f; 139 | float maxRowWidth = 0f; 140 | for (int i = 0; i < this._items.Count; i++) 141 | { 142 | var item = this._items[i]; 143 | var outerDimensions = item.GetOuterDimensions(); 144 | if (top + outerDimensions.Height > availableHeight && top > 0) 145 | { 146 | left += maxRowWidth + this.ListPadding; 147 | top = 0; 148 | maxRowWidth = 0; 149 | } 150 | maxRowWidth = Math.Max(maxRowWidth, outerDimensions.Width); 151 | item.Top.Set(top, 0f); 152 | top += outerDimensions.Height + this.ListPadding; 153 | item.Left.Set(left, 0f); 154 | item.Recalculate(); 155 | } 156 | this._innerListWidth = left + maxRowWidth; 157 | } 158 | 159 | private void UpdateScrollbar() 160 | { 161 | if (this._scrollbar == null) 162 | { 163 | return; 164 | } 165 | this._scrollbar.SetView(base.GetInnerDimensions().Width, this._innerListWidth); 166 | } 167 | 168 | public void SetScrollbar(UIHorizontalScrollbar scrollbar) 169 | { 170 | this._scrollbar = scrollbar; 171 | this.UpdateScrollbar(); 172 | } 173 | 174 | public void UpdateOrder() 175 | { 176 | this._items.Sort(new Comparison(this.SortMethod)); 177 | this.UpdateScrollbar(); 178 | } 179 | 180 | public int SortMethod(UIElement item1, UIElement item2) 181 | { 182 | return item1.CompareTo(item2); 183 | } 184 | 185 | public override List GetSnapPoints() 186 | { 187 | List list = new List(); 188 | SnapPoint item; 189 | if (base.GetSnapPoint(out item)) 190 | { 191 | list.Add(item); 192 | } 193 | foreach (UIElement current in this._items) 194 | { 195 | list.AddRange(current.GetSnapPoints()); 196 | } 197 | return list; 198 | } 199 | 200 | protected override void DrawSelf(SpriteBatch spriteBatch) 201 | { 202 | //var r = GetDimensions().ToRectangle(); 203 | //r.Inflate(-10,-10); 204 | //spriteBatch.Draw(Main.magicPixel, r, Color.Yellow); 205 | if (this._scrollbar != null) 206 | { 207 | this._innerList.Left.Set(-this._scrollbar.GetValue(), 0f); 208 | } 209 | if(IsMouseHovering) 210 | PlayerInput.LockVanillaMouseScroll("RecipeBrowser/UIHorizontalGrid"); 211 | this.Recalculate(); 212 | } 213 | 214 | public bool drawArrows; 215 | protected override void DrawChildren(SpriteBatch spriteBatch) 216 | { 217 | base.DrawChildren(spriteBatch); 218 | if (drawArrows) 219 | { 220 | var inner = GetInnerDimensions().ToRectangle(); 221 | if (this._scrollbar.ViewPosition != 0) 222 | { 223 | int centeredY = inner.Y + inner.Height / 2 - moreLeftTexture.Height() / 2; 224 | spriteBatch.Draw(moreLeftTexture.Value, new Vector2(inner.X, centeredY), Color.White * .5f); 225 | } 226 | if (this._scrollbar.ViewPosition < _innerListWidth - inner.Width - 1) // -1 due to odd width leading to 0.5 view position offset. 227 | { 228 | int centeredY = inner.Y + inner.Height / 2 - moreRightTexture.Height() / 2; 229 | spriteBatch.Draw(moreRightTexture.Value, new Vector2(inner.Right - moreRightTexture.Width(), centeredY), Color.White * .5f); 230 | } 231 | } 232 | } 233 | } 234 | } -------------------------------------------------------------------------------- /UIElements/UIHorizontalScrollbar.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Content; 4 | using Terraria; 5 | using Terraria.Audio; 6 | using Terraria.UI; 7 | using Terraria.ID; 8 | 9 | namespace RecipeBrowser.UIElements 10 | { 11 | public class UIHorizontalScrollbar : UIElement 12 | { 13 | private float _viewPosition; 14 | private float _viewSize = 1f; 15 | private float _maxViewSize = 20f; 16 | private bool _isDragging; 17 | private bool _isHoveringOverHandle; 18 | private float _dragXOffset; 19 | private Asset _texture; 20 | private Asset _innerTexture; 21 | 22 | public float ViewPosition 23 | { 24 | get 25 | { 26 | return this._viewPosition; 27 | } 28 | set 29 | { 30 | this._viewPosition = MathHelper.Clamp(value, 0f, this._maxViewSize - this._viewSize); 31 | } 32 | } 33 | 34 | public UIHorizontalScrollbar() 35 | { 36 | this.Height.Set(20f, 0f); 37 | this.MaxHeight.Set(20f, 0f); 38 | this._texture = RecipeBrowser.instance.Assets.Request("UIElements/ScrollbarHorizontal"); //TextureManager.Load("Terraria/Images/UI/Scrollbar"); 39 | this._innerTexture = RecipeBrowser.instance.Assets.Request("UIElements/ScrollbarInnerHorizontal"); //TextureManager.Load("Terraria/Images/UI/ScrollbarInner"); 40 | this.PaddingLeft = 5f; 41 | this.PaddingRight = 5f; 42 | } 43 | 44 | public void SetView(float viewSize, float maxViewSize) 45 | { 46 | viewSize = MathHelper.Clamp(viewSize, 0f, maxViewSize); 47 | this._viewPosition = MathHelper.Clamp(this._viewPosition, 0f, maxViewSize - viewSize); 48 | this._viewSize = viewSize; 49 | this._maxViewSize = maxViewSize; 50 | } 51 | 52 | public float GetValue() 53 | { 54 | return this._viewPosition; 55 | } 56 | 57 | private Rectangle GetHandleRectangle() 58 | { 59 | CalculatedStyle innerDimensions = base.GetInnerDimensions(); 60 | if (this._maxViewSize == 0f && this._viewSize == 0f) 61 | { 62 | this._viewSize = 1f; 63 | this._maxViewSize = 1f; 64 | } 65 | //return new Rectangle((int)innerDimensions.X, (int)(innerDimensions.Y + innerDimensions.Height * (this._viewPosition / this._maxViewSize)) - 3, 20, (int)(innerDimensions.Height * (this._viewSize / this._maxViewSize)) + 7); 66 | return new Rectangle((int)(innerDimensions.X + innerDimensions.Width * (this._viewPosition / this._maxViewSize)) - 3, (int)innerDimensions.Y, (int)(innerDimensions.Width * (this._viewSize / this._maxViewSize)) + 7, 20); 67 | } 68 | 69 | private void DrawBar(SpriteBatch spriteBatch, Texture2D texture, Rectangle dimensions, Color color) 70 | { 71 | //spriteBatch.Draw(texture, new Rectangle(dimensions.X, dimensions.Y - 6, dimensions.Width, 6), new Rectangle?(new Rectangle(0, 0, texture.Width, 6)), color); 72 | //spriteBatch.Draw(texture, new Rectangle(dimensions.X, dimensions.Y, dimensions.Width, dimensions.Height), new Rectangle?(new Rectangle(0, 6, texture.Width, 4)), color); 73 | //spriteBatch.Draw(texture, new Rectangle(dimensions.X, dimensions.Y + dimensions.Height, dimensions.Width, 6), new Rectangle?(new Rectangle(0, texture.Height - 6, texture.Width, 6)), color); 74 | spriteBatch.Draw(texture, new Rectangle(dimensions.X - 6, dimensions.Y, 6, dimensions.Height), new Rectangle(0, 0, 6, texture.Height), color); 75 | spriteBatch.Draw(texture, new Rectangle(dimensions.X, dimensions.Y, dimensions.Width, dimensions.Height), new Rectangle(6, 0, 4, texture.Height), color); 76 | spriteBatch.Draw(texture, new Rectangle(dimensions.X + dimensions.Width, dimensions.Y, 6, dimensions.Height), new Rectangle(texture.Width - 6, 0, 6, texture.Height), color); 77 | } 78 | 79 | protected override void DrawSelf(SpriteBatch spriteBatch) 80 | { 81 | CalculatedStyle dimensions = base.GetDimensions(); 82 | CalculatedStyle innerDimensions = base.GetInnerDimensions(); 83 | if (this._isDragging) 84 | { 85 | float num = UserInterface.ActiveInstance.MousePosition.X - innerDimensions.X - this._dragXOffset; 86 | this._viewPosition = MathHelper.Clamp(num / innerDimensions.Width * this._maxViewSize, 0f, this._maxViewSize - this._viewSize); 87 | } 88 | Rectangle handleRectangle = this.GetHandleRectangle(); 89 | Vector2 mousePosition = UserInterface.ActiveInstance.MousePosition; 90 | bool isHoveringOverHandle = this._isHoveringOverHandle; 91 | this._isHoveringOverHandle = handleRectangle.Contains(new Point((int)mousePosition.X, (int)mousePosition.Y)); 92 | if (!isHoveringOverHandle && this._isHoveringOverHandle && Main.hasFocus) 93 | { 94 | SoundEngine.PlaySound(SoundID.MenuTick); 95 | } 96 | this.DrawBar(spriteBatch, this._texture.Value, dimensions.ToRectangle(), Color.White); 97 | this.DrawBar(spriteBatch, this._innerTexture.Value, handleRectangle, Color.White * ((this._isDragging || this._isHoveringOverHandle) ? 1f : 0.85f)); 98 | } 99 | 100 | public override void LeftMouseDown(UIMouseEvent evt) 101 | { 102 | base.LeftMouseDown(evt); 103 | if (evt.Target == this) 104 | { 105 | Rectangle handleRectangle = this.GetHandleRectangle(); 106 | if (handleRectangle.Contains(new Point((int)evt.MousePosition.X, (int)evt.MousePosition.Y))) 107 | { 108 | this._isDragging = true; 109 | this._dragXOffset = evt.MousePosition.X - (float)handleRectangle.X; 110 | return; 111 | } 112 | CalculatedStyle innerDimensions = base.GetInnerDimensions(); 113 | float num = UserInterface.ActiveInstance.MousePosition.X - innerDimensions.X - (float)(handleRectangle.Width >> 1); 114 | this._viewPosition = MathHelper.Clamp(num / innerDimensions.Width * this._maxViewSize, 0f, this._maxViewSize - this._viewSize); 115 | } 116 | } 117 | 118 | public override void LeftMouseUp(UIMouseEvent evt) 119 | { 120 | base.LeftMouseUp(evt); 121 | this._isDragging = false; 122 | } 123 | } 124 | 125 | public class FixedUIHorizontalScrollbar : UIHorizontalScrollbar 126 | { 127 | internal UserInterface userInterface; 128 | 129 | public FixedUIHorizontalScrollbar(UserInterface userInterface) 130 | { 131 | this.userInterface = userInterface; 132 | } 133 | 134 | protected override void DrawSelf(SpriteBatch spriteBatch) 135 | { 136 | UserInterface temp = UserInterface.ActiveInstance; 137 | UserInterface.ActiveInstance = userInterface; 138 | base.DrawSelf(spriteBatch); 139 | UserInterface.ActiveInstance = temp; 140 | } 141 | 142 | public override void LeftMouseDown(UIMouseEvent evt) 143 | { 144 | UserInterface temp = UserInterface.ActiveInstance; 145 | UserInterface.ActiveInstance = userInterface; 146 | base.LeftMouseDown(evt); 147 | UserInterface.ActiveInstance = temp; 148 | } 149 | } 150 | 151 | public class InvisibleFixedUIHorizontalScrollbar : FixedUIHorizontalScrollbar 152 | { 153 | public InvisibleFixedUIHorizontalScrollbar(UserInterface userInterface) : base(userInterface) 154 | { 155 | } 156 | 157 | protected override void DrawSelf(SpriteBatch spriteBatch) 158 | { 159 | UserInterface temp = UserInterface.ActiveInstance; 160 | UserInterface.ActiveInstance = userInterface; 161 | //base.DrawSelf(spriteBatch); 162 | UserInterface.ActiveInstance = temp; 163 | } 164 | 165 | public override void LeftMouseDown(UIMouseEvent evt) 166 | { 167 | UserInterface temp = UserInterface.ActiveInstance; 168 | UserInterface.ActiveInstance = userInterface; 169 | base.LeftMouseDown(evt); 170 | UserInterface.ActiveInstance = temp; 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /UIElements/UIHoverImageButton.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using ReLogic.Content; 3 | using Terraria; 4 | using Terraria.GameContent.UI.Elements; 5 | 6 | namespace RecipeBrowser 7 | { 8 | internal class UIHoverImageButton : UIImageButton 9 | { 10 | internal string hoverText; 11 | 12 | public UIHoverImageButton(Asset texture, string hoverText) : base(texture) 13 | { 14 | this.hoverText = hoverText; 15 | } 16 | 17 | protected override void DrawSelf(SpriteBatch spriteBatch) 18 | { 19 | base.DrawSelf(spriteBatch); 20 | if (IsMouseHovering) 21 | { 22 | // Main.hoverItemName = hoverText; 23 | if (!string.IsNullOrWhiteSpace(hoverText)) 24 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(hoverText); 25 | // Main.toolTip = new Item(); 26 | // Main.toolTip.name = hoverText; 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /UIElements/UIHoverImageButtonMod.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Content; 4 | using Terraria; 5 | using Terraria.UI; 6 | 7 | namespace RecipeBrowser 8 | { 9 | internal class UIHoverImageButtonMod : UIHoverImageButton 10 | { 11 | internal Texture2D texture; 12 | private Asset textureColorable; 13 | private Vector2 offset = new Vector2(0, 12); 14 | 15 | public UIHoverImageButtonMod(Asset texture, Asset textureColorable, string hoverText) : base(texture, hoverText) 16 | { 17 | this.textureColorable = textureColorable; 18 | } 19 | 20 | protected override void DrawSelf(SpriteBatch spriteBatch) 21 | { 22 | if (RecipeBrowserUI.modIndex != 0) { 23 | CalculatedStyle dimensions = GetDimensions(); 24 | spriteBatch.Draw(textureColorable.Value, dimensions.Position(), Main.DiscoColor); 25 | 26 | // Duplicate code here since we don't want to re-draw base texture. 27 | if (IsMouseHovering) { 28 | if (!string.IsNullOrWhiteSpace(hoverText)) 29 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(hoverText); 30 | } 31 | } 32 | else { 33 | base.DrawSelf(spriteBatch); 34 | } 35 | if (IsMouseHovering && texture != null) 36 | { 37 | Rectangle hitbox = GetInnerDimensions().ToRectangle(); 38 | spriteBatch.Draw(texture, new Vector2(hitbox.X + hitbox.Width / 2 - 40, hitbox.Y - 80), Color.White); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /UIElements/UIIngredientSlot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Content; 4 | using Terraria; 5 | using Terraria.GameContent; 6 | using Terraria.UI; 7 | 8 | namespace RecipeBrowser.UIElements 9 | { 10 | internal class UIIngredientSlot : UIItemSlot 11 | { 12 | public static Asset selectedBackgroundTexture = TextureAssets.InventoryBack15; 13 | private int clickIndicatorTime = 0; 14 | private const int ClickTime = 30; 15 | private int order; // Recipe Ingredient Order 16 | 17 | public UIIngredientSlot(Item item, int order, float scale = 0.75f) : base(item, scale) 18 | { 19 | this.order = order; 20 | } 21 | 22 | public override void LeftClick(UIMouseEvent evt) 23 | { 24 | base.LeftClick(evt); 25 | //RecipeBrowserUI.instance.SetRecipe(index); 26 | RecipeCatalogueUI.instance.queryLootItem = this.item; 27 | RecipeCatalogueUI.instance.updateNeeded = true; 28 | clickIndicatorTime = ClickTime; 29 | } 30 | 31 | public override void LeftDoubleClick(UIMouseEvent evt) 32 | { 33 | RecipeBrowserUI.instance.tabController.SetPanel(RecipeBrowserUI.RecipeCatalogue); 34 | if (!RecipeBrowserUI.instance.ShowRecipeBrowser) 35 | RecipeBrowserUI.instance.ShowRecipeBrowser = true; 36 | RecipeCatalogueUI.instance.itemDescriptionFilter.SetText(""); 37 | RecipeCatalogueUI.instance.itemNameFilter.SetText(""); 38 | RecipeCatalogueUI.instance.queryItem.ReplaceWithFake(item.type); 39 | } 40 | 41 | internal override void DrawAdditionalOverlays(SpriteBatch spriteBatch, Vector2 vector2, float scale) 42 | { 43 | if (clickIndicatorTime > 0) 44 | { 45 | clickIndicatorTime--; 46 | spriteBatch.Draw(selectedBackgroundTexture.Value, vector2, null, Color.White * ((float)clickIndicatorTime / ClickTime), 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); 47 | } 48 | } 49 | 50 | public override int CompareTo(object obj) 51 | { 52 | UIIngredientSlot other = obj as UIIngredientSlot; 53 | return order.CompareTo(other.order); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /UIElements/UIJourneyDuplicateButton.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Terraria; 4 | using Terraria.Audio; 5 | using Terraria.UI; 6 | using Terraria.ID; 7 | using ReLogic.Content; 8 | using Terraria.DataStructures; 9 | using System; 10 | 11 | namespace RecipeBrowser.UIElements 12 | { 13 | internal class UIJourneyDuplicateButton : UIElement 14 | { 15 | public static Asset duplicateOn; 16 | public static Asset duplicateOff; 17 | CraftPath.JourneyDuplicateItemNode duplicationNode; 18 | 19 | public UIJourneyDuplicateButton(CraftPath.JourneyDuplicateItemNode duplicationNode) { 20 | this.duplicationNode = duplicationNode; 21 | this.Width.Set(duplicateOn.Width(), 0f); 22 | this.Height.Set(duplicateOn.Height(), 0f); 23 | } 24 | 25 | protected override void DrawSelf(SpriteBatch spriteBatch) { 26 | bool ableToDuplicate = AbleToDuplicate(); 27 | CalculatedStyle dimensions = base.GetDimensions(); 28 | spriteBatch.Draw((IsMouseHovering && ableToDuplicate ? duplicateOn : duplicateOff).Value, dimensions.Position(), null, Color.White, 0, Vector2.Zero, 0.8f, SpriteEffects.None, 0); 29 | //ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.ItemStack.Value, ableToDuplicate ? "✓" : "X", dimensions.Position() + new Vector2(14f, 10f), ableToDuplicate ? Utilities.yesColor : Color.LightSalmon, 0f, Vector2.Zero, new Vector2(0.7f)); 30 | if (IsMouseHovering) { 31 | //Main.hoverItemName = ableToDuplicate ? "Duplicate" : ""; 32 | if (ableToDuplicate) 33 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(CraftUI.RBText("Duplicate")); 34 | } 35 | } 36 | 37 | public override void MouseOver(UIMouseEvent evt) { 38 | base.MouseOver(evt); 39 | if (AbleToDuplicate()) { 40 | SoundEngine.PlaySound(SoundID.MenuTick); 41 | } 42 | } 43 | 44 | public override void LeftClick(UIMouseEvent evt) { 45 | base.LeftClick(evt); 46 | if (AbleToDuplicate()) { 47 | int stack = duplicationNode.stack; 48 | while(stack > 0) { 49 | Item duplicateItem = ContentSamples.ItemsByType[duplicationNode.itemid].Clone(); 50 | int itemStack = Math.Min(stack, duplicateItem.maxStack); 51 | duplicateItem.stack = itemStack; 52 | duplicateItem.OnCreated(new JourneyDuplicationItemCreationContext()); 53 | duplicateItem = Main.player[Main.myPlayer].GetItem(Main.myPlayer, duplicateItem, GetItemSettings.InventoryEntityToPlayerInventorySettings); 54 | if (duplicateItem.stack > 0) { 55 | Main.LocalPlayer.QuickSpawnItem(Main.LocalPlayer.GetSource_Misc("PlayerDropItemCheck"), duplicateItem, duplicateItem.stack); 56 | } 57 | 58 | SoundEngine.PlaySound(SoundID.MenuTick); 59 | stack -= itemStack; 60 | } 61 | } 62 | } 63 | 64 | // Probably not necessary to check again... 65 | bool AbleToDuplicate() { 66 | if (Main.GameModeInfo.IsJourneyMode) { 67 | if (RecipePath.ItemFullyResearched(duplicationNode.itemid)) { 68 | return true; 69 | } 70 | } 71 | return false; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /UIElements/UIMockRecipeSlot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using System.Linq; 3 | using Terraria; 4 | using Terraria.UI; 5 | using Microsoft.Xna.Framework; 6 | using ReLogic.Content; 7 | using Terraria.GameContent; 8 | 9 | namespace RecipeBrowser.UIElements 10 | { 11 | // used as a duplicate 12 | // Todo, temporary? 13 | internal class UIMockRecipeSlot : UIItemSlot 14 | { 15 | public static Asset ableToCraftBackgroundTexture; 16 | public static Asset unableToCraftBackgroundTexture = TextureAssets.InventoryBack11; 17 | private UIRecipeSlot slot; 18 | 19 | public UIMockRecipeSlot(UIRecipeSlot slot, float scale = 0.75f) : base(slot.item, scale) 20 | { 21 | this.slot = slot; 22 | } 23 | 24 | public override void LeftClick(UIMouseEvent evt) 25 | { 26 | slot.LeftClick(evt); 27 | if (!Main.keyState.IsKeyDown(Main.FavoriteKey)) 28 | { 29 | if ((slot.craftPathCalculated || slot.craftPathsCalculated) && slot.craftPaths.Count > 0) 30 | { 31 | RecipeBrowserUI.instance.tabController.SetPanel(RecipeBrowserUI.Craft); 32 | CraftUI.instance.SetRecipe(slot.index); 33 | if (!RecipeBrowserUI.instance.ShowRecipeBrowser) 34 | RecipeBrowserUI.instance.ShowRecipeBrowser = true; 35 | } 36 | else 37 | { 38 | // inherited. RecipeCatalogueUI.instance.SetRecipe(slot.index); 39 | RecipeBrowserUI.instance.tabController.SetPanel(RecipeBrowserUI.RecipeCatalogue); 40 | RecipeCatalogueUI.instance.recipeGrid.Goto(delegate (UIElement element) 41 | { 42 | UIRecipeSlot itemSlot = element as UIRecipeSlot; 43 | return itemSlot == slot; 44 | }, true); 45 | if (!RecipeBrowserUI.instance.ShowRecipeBrowser) 46 | RecipeBrowserUI.instance.ShowRecipeBrowser = true; 47 | } 48 | } 49 | } 50 | 51 | public override void RightClick(UIMouseEvent evt) 52 | { 53 | RecipeBrowserUI.instance.ShowRecipeBrowser = false; 54 | } 55 | 56 | internal override void DrawAdditionalOverlays(SpriteBatch spriteBatch, Vector2 vector2, float scale) 57 | { 58 | bool favorited = slot.favorited; 59 | slot.favorited = false; 60 | slot.DrawAdditionalOverlays(spriteBatch, vector2, scale); 61 | slot.favorited = favorited; 62 | } 63 | 64 | protected override void DrawSelf(SpriteBatch spriteBatch) 65 | { 66 | if (IsMouseHovering) 67 | if (Main.keyState.IsKeyDown(Main.FavoriteKey)) 68 | if (Main.drawingPlayerChat) 69 | Main.cursorOverride = 2; 70 | else 71 | Main.cursorOverride = 3; 72 | 73 | // TODO: Trigger slot.CraftPathsNeeded if RecipePath.extendedCraft 74 | 75 | backgroundTexture = unableToCraftBackgroundTexture; 76 | if(RecipePath.extendedCraft) 77 | slot.CraftPathNeeded(); 78 | if ((slot.craftPathCalculated || slot.craftPathsCalculated) && slot.craftPaths.Count > 0) 79 | backgroundTexture = UIRecipeSlot.ableToCraftExtendedBackgroundTexture; 80 | 81 | for (int n = 0; n < Main.numAvailableRecipes; n++) 82 | { 83 | if (slot.index == Main.availableRecipe[n]) 84 | { 85 | backgroundTexture = ableToCraftBackgroundTexture; 86 | break; 87 | } 88 | } 89 | 90 | base.DrawSelf(spriteBatch); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /UIElements/UIModState.cs: -------------------------------------------------------------------------------- 1 | using Terraria.UI; 2 | 3 | namespace RecipeBrowser 4 | { 5 | internal class UIModState : UIState 6 | { 7 | internal UserInterface userInterface; 8 | 9 | public UIModState(UserInterface userInterface) 10 | { 11 | this.userInterface = userInterface; 12 | } 13 | 14 | public void ReverseChildren() 15 | { 16 | Elements.Reverse(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /UIElements/UINPCSlot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Terraria; 4 | using Terraria.ModLoader; 5 | using Terraria.GameContent.UI.Elements; 6 | using Terraria.UI; 7 | using Terraria.Localization; 8 | using System; 9 | using Terraria.ID; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Collections.Generic; 13 | using Terraria.GameContent; 14 | 15 | namespace RecipeBrowser.UIElements 16 | { 17 | internal class UINPCSlot : UIElement 18 | { 19 | public static Texture2D selectedBackgroundTexture = TextureAssets.InventoryBack15.Value; 20 | public static Texture2D backgroundTexture = TextureAssets.InventoryBack9.Value; 21 | private float scale = .75f; 22 | public int npcType; 23 | public NPC npc; 24 | public bool selected; 25 | 26 | private int clickIndicatorTime = 0; 27 | private const int ClickTime = 30; 28 | 29 | //public UINPCSlot(int npcType) 30 | //{ 31 | // this.npcType = npcType; 32 | // this.Width.Set(backgroundTexture.Width * scale, 0f); 33 | // this.Height.Set(backgroundTexture.Height * scale, 0f); 34 | //} 35 | 36 | public UINPCSlot(NPC npc) 37 | { 38 | this.npc = npc; 39 | this.npcType = npc.type; 40 | this.Width.Set(backgroundTexture.Width * scale, 0f); 41 | this.Height.Set(backgroundTexture.Height * scale, 0f); 42 | } 43 | 44 | internal int frameCounter = 0; 45 | internal int frameTimer = 0; 46 | private const int frameDelay = 7; 47 | 48 | protected override void DrawSelf(SpriteBatch spriteBatch) 49 | { 50 | Utilities.LoadNPC(npcType); 51 | Texture2D npcTexture = TextureAssets.Npc[npcType].Value; 52 | 53 | if (++frameTimer > frameDelay) 54 | { 55 | frameCounter = frameCounter + 1; 56 | frameTimer = 0; 57 | if (frameCounter > Main.npcFrameCount[npcType] - 1) 58 | { 59 | frameCounter = 0; 60 | } 61 | } 62 | 63 | Rectangle npcDrawRectangle = new Rectangle(0, (npcTexture.Height / Main.npcFrameCount[npcType]) * frameCounter, npcTexture.Width, npcTexture.Height / Main.npcFrameCount[npcType]); 64 | 65 | CalculatedStyle dimensions = base.GetInnerDimensions(); 66 | spriteBatch.Draw(backgroundTexture, dimensions.Position(), null, Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); 67 | DrawAdditionalOverlays(spriteBatch, dimensions.Position(), scale); 68 | Rectangle rectangle = dimensions.ToRectangle(); 69 | 70 | int height = npcTexture.Height / Main.npcFrameCount[npcType]; 71 | int width = npcTexture.Width; 72 | 73 | float drawScale = 2f; 74 | float availableWidth = (float)backgroundTexture.Width * scale - 6; 75 | if (width * drawScale > availableWidth || height * drawScale > availableWidth) 76 | { 77 | if (width > height) 78 | { 79 | drawScale = availableWidth / width; 80 | } 81 | else 82 | { 83 | drawScale = availableWidth / height; 84 | } 85 | } 86 | Vector2 drawPosition = dimensions.Position(); 87 | drawPosition.X += backgroundTexture.Width * scale / 2f - (float)width * drawScale / 2f; 88 | drawPosition.Y += backgroundTexture.Height * scale / 2f - (float)height * drawScale / 2f; 89 | 90 | Color color = (npc.color != new Color(byte.MinValue, byte.MinValue, byte.MinValue, byte.MinValue)) ? new Color(npc.color.R, npc.color.G, npc.color.B, 255f) : new Color(1f, 1f, 1f); 91 | 92 | Main.spriteBatch.Draw(npcTexture, drawPosition, npcDrawRectangle, color, 0, Vector2.Zero, drawScale, SpriteEffects.None, 0); 93 | 94 | if (IsMouseHovering) 95 | { 96 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(Lang.GetNPCNameValue(npc.type) + (npc.ModNPC != null && ModContent.GetInstance().ShowNPCModSource ? " [" + npc.ModNPC.Mod.DisplayName + "]" : "")); 97 | } 98 | } 99 | 100 | public override int CompareTo(object obj) 101 | { 102 | UINPCSlot other = obj as UINPCSlot; 103 | return /*-1 * */npcType.CompareTo(other.npcType); 104 | } 105 | 106 | public SortedSet GetDrops() 107 | { 108 | SortedSet drops = new SortedSet(); 109 | foreach (var kvp in LootCache.instance.lootInfos) 110 | { 111 | foreach (var npc in kvp.Value) 112 | { 113 | if (npc == this.npc.type) 114 | { 115 | drops.Add(kvp.Key); 116 | } 117 | } 118 | } 119 | //drops.Remove(0); 120 | return drops; 121 | } 122 | 123 | public override void LeftClick(UIMouseEvent evt) 124 | { 125 | clickIndicatorTime = ClickTime; 126 | // Calculate 127 | var drops = GetDrops(); 128 | 129 | if (RecipeBrowserUI.instance.CurrentPanel == RecipeBrowserUI.RecipeCatalogue) 130 | { 131 | StringBuilder sb = new StringBuilder(); 132 | 133 | sb.Append(Language.GetTextValue("Mods.RecipeBrowser.BestiaryUI.NPCDrops", Lang.GetNPCNameValue(npc.type))); 134 | foreach (var item in drops) 135 | { 136 | sb.Append($"[i:{item}]"); 137 | } 138 | 139 | Main.NewText(sb.ToString()); 140 | } 141 | else if (RecipeBrowserUI.instance.CurrentPanel == RecipeBrowserUI.Bestiary) 142 | { 143 | // Ug. double click calls click again after double click, leading to this being called on the wrong set. 144 | if (BestiaryUI.instance.npcSlots.Contains(this)) 145 | { 146 | BestiaryUI.instance.queryLootNPC = this; 147 | BestiaryUI.instance.updateNeeded = true; 148 | BestiaryUI.instance.SetNPC(this); 149 | } 150 | } 151 | } 152 | 153 | public override void LeftDoubleClick(UIMouseEvent evt) 154 | { 155 | // Open up bestiary tab 156 | // Large grid for npc 157 | // 158 | // Small drops grid 159 | 160 | // Catalogue double click? recipe ingredient? 161 | // quickly visiting catalogue not really needed...just share type so scroll to it/select it when switch tabs 162 | 163 | // Make 164 | if (RecipeBrowserUI.instance.CurrentPanel == RecipeBrowserUI.RecipeCatalogue) 165 | { 166 | RecipeBrowserUI.instance.tabController.SetPanel(RecipeBrowserUI.Bestiary); 167 | BestiaryUI.instance.npcNameFilter.SetText(""); 168 | BestiaryUI.instance.queryItem.ReplaceWithFake(0); 169 | // Need update before Goto 170 | BestiaryUI.instance.updateNeeded = true; 171 | BestiaryUI.instance.Update(); 172 | BestiaryUI.instance.npcGrid.Recalculate(); 173 | BestiaryUI.instance.npcGrid.Goto((element) => 174 | { 175 | UINPCSlot slot = element as UINPCSlot; 176 | if (slot != null) 177 | { 178 | if (slot.npcType == this.npcType) 179 | { 180 | BestiaryUI.instance.queryLootNPC = slot; 181 | BestiaryUI.instance.updateNeeded = true; 182 | BestiaryUI.instance.SetNPC(slot); 183 | return true; 184 | } 185 | return false; 186 | } 187 | return false; 188 | }, true); 189 | } 190 | } 191 | 192 | internal void DrawAdditionalOverlays(SpriteBatch spriteBatch, Vector2 vector2, float scale) 193 | { 194 | if (selected) 195 | spriteBatch.Draw(selectedBackgroundTexture, vector2, null, Color.White * Main.essScale, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); 196 | 197 | if (clickIndicatorTime > 0) 198 | { 199 | clickIndicatorTime--; 200 | spriteBatch.Draw(selectedBackgroundTexture, vector2, null, Color.White * ((float)clickIndicatorTime / ClickTime), 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); 201 | } 202 | } 203 | } 204 | } -------------------------------------------------------------------------------- /UIElements/UIQueryItemSlot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using ReLogic.Content; 8 | using Terraria; 9 | using Terraria.GameContent; 10 | using Terraria.ModLoader; 11 | using Terraria.UI; 12 | using Terraria.ID; 13 | using Terraria.Localization; 14 | 15 | namespace RecipeBrowser.UIElements 16 | { 17 | internal class UIQueryItemSlot : UIItemSlot 18 | { 19 | public static Asset backgroundTextureFake = TextureAssets.InventoryBack8; 20 | internal bool real = true; 21 | internal string emptyHintText; 22 | 23 | public event Action OnItemChanged; 24 | 25 | public UIQueryItemSlot(Item item) : base(item) 26 | { 27 | } 28 | 29 | protected override void DrawSelf(SpriteBatch spriteBatch) 30 | { 31 | base.DrawSelf(spriteBatch); 32 | if (item.IsAir && IsMouseHovering) 33 | { 34 | // Main.hoverItemName = emptyHintText; 35 | if (!string.IsNullOrWhiteSpace(emptyHintText)) 36 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(emptyHintText); 37 | } 38 | } 39 | 40 | public override void LeftClick(UIMouseEvent evt) 41 | { 42 | Player player = Main.LocalPlayer; 43 | if (player.itemAnimation == 0 && player.itemTime == 0) 44 | { 45 | if (real) 46 | { 47 | Item item = Main.mouseItem.Clone(); 48 | Main.mouseItem = this.item.Clone(); 49 | if (Main.mouseItem.type > 0) 50 | { 51 | Main.playerInventory = true; 52 | } 53 | this.item = item.Clone(); 54 | } 55 | else 56 | { 57 | item = Main.mouseItem.Clone(); 58 | Main.mouseItem.SetDefaults(0); 59 | real = true; 60 | } 61 | if (item.type == 0) real = true; 62 | OnItemChanged?.Invoke(); 63 | } 64 | backgroundTexture = real ? defaultBackgroundTexture : backgroundTextureFake; 65 | } 66 | 67 | internal virtual void ReplaceWithFake(int type) 68 | { 69 | if (real && item.stack > 0) 70 | { 71 | // Main.player[Main.myPlayer].QuickSpawnItem(RecipeBrowserWindow.lookupItemSlot.item.type, RecipeBrowserWindow.lookupItemSlot.item.stack); 72 | 73 | Player player = Main.player[Main.myPlayer]; 74 | item.position = player.Center; 75 | Item item2 = player.GetItem(player.whoAmI, item, GetItemSettings.GetItemInDropItemCheck); 76 | if (item2.stack > 0) 77 | { 78 | int num = Item.NewItem(Main.LocalPlayer.GetSource_Misc("PlayerDropItemCheck"), (int)player.position.X, (int)player.position.Y, player.width, player.height, item2.type, item2.stack, false, (int)item.prefix, true, false); 79 | Main.item[num].newAndShiny = false; 80 | if (Main.netMode == NetmodeID.MultiplayerClient) 81 | { 82 | NetMessage.SendData(MessageID.SyncItem, -1, -1, null, num, 1f, 0f, 0f, 0, 0, 0); 83 | } 84 | else 85 | { 86 | // TODO: Detect PreSaveAndQuit only. 87 | RecipeBrowser.instance.Logger.Warn(Language.GetTextValue("Mods.RecipeBrowser.ItemLostInQuerySlotWarning") + item2.Name); 88 | } 89 | } 90 | item = new Item(); 91 | } 92 | 93 | item.SetDefaults(type); 94 | real = type == 0; 95 | backgroundTexture = real ? defaultBackgroundTexture : backgroundTextureFake; 96 | OnItemChanged?.Invoke(); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /UIElements/UIRadioButton.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Terraria.ModLoader; 4 | using Terraria.GameContent.UI.Elements; 5 | using Terraria.UI; 6 | using System; 7 | using ReLogic.Content; 8 | using Terraria.GameContent.UI.Chat; 9 | using Terraria; 10 | using Terraria.Graphics; 11 | 12 | namespace RecipeBrowser 13 | { 14 | internal class UIRadioButton : UIText 15 | { 16 | private Asset _toggleTexture; 17 | 18 | public event EventHandler OnSelectedChanged; 19 | 20 | private bool selected = false; 21 | private bool disabled = false; 22 | internal bool partOfGroup; 23 | internal int groupID; 24 | internal string hoverText; 25 | 26 | public bool Selected 27 | { 28 | get { return selected; } 29 | set 30 | { 31 | if (value != selected) 32 | { 33 | selected = value; 34 | OnSelectedChanged?.Invoke(this, EventArgs.Empty); 35 | } 36 | } 37 | } 38 | 39 | public override void LeftClick(UIMouseEvent evt) 40 | { 41 | if (disabled) return; 42 | if (!partOfGroup) 43 | { 44 | Selected = !Selected; 45 | Recalculate(); 46 | } 47 | else 48 | { 49 | (Parent as UIRadioButtonGroup).ButtonClicked(groupID); 50 | } 51 | } 52 | 53 | public UIRadioButton(string text, string hoverText, float textScale = 1, bool large = false) : base(text, textScale, large) 54 | { 55 | this._toggleTexture = ModContent.Request("Terraria/Images/UI/Settings_Toggle"); 56 | text = " " + text; 57 | this.hoverText = hoverText; 58 | SetText(text); 59 | Recalculate(); 60 | } 61 | 62 | public void SetDisabled(bool disabled = true) 63 | { 64 | this.disabled = disabled; 65 | if (disabled) 66 | { 67 | Selected = false; 68 | } 69 | TextColor = disabled ? Color.Gray : Color.White; 70 | } 71 | 72 | public void SetHoverText(string hoverText) 73 | { 74 | this.hoverText = hoverText; 75 | } 76 | 77 | protected override void DrawSelf(SpriteBatch spriteBatch) 78 | { 79 | base.DrawSelf(spriteBatch); 80 | CalculatedStyle innerDimensions = base.GetInnerDimensions(); 81 | Vector2 pos = new Vector2(innerDimensions.X, innerDimensions.Y); 82 | 83 | //spriteBatch.Draw(checkboxTexture, pos, null, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f); 84 | 85 | Rectangle value = new Rectangle(Selected ? ((_toggleTexture.Width() - 2) / 2 + 2) : 0, 0, (_toggleTexture.Width() - 2) / 2, this._toggleTexture.Height()); 86 | //Vector2 vector2 = new Vector2((float)value.Width, 0f); 87 | //position = new Vector2(dimensions.X + dimensions.Width - vector2.X - 10f, dimensions.Y + 2f + num); 88 | spriteBatch.Draw(this._toggleTexture.Value, pos, new Rectangle?(value), disabled ? Color.Gray : Color.White, 0f, Vector2.Zero, Vector2.One, SpriteEffects.None, 0f); 89 | 90 | if (IsMouseHovering) 91 | { 92 | // Main.hoverItemName = hoverText; 93 | if(!string.IsNullOrWhiteSpace(hoverText)) 94 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(hoverText); 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /UIElements/UIRadioButtonGroup.cs: -------------------------------------------------------------------------------- 1 | using Terraria.GameContent.UI.Elements; 2 | using Terraria.UI; 3 | using System; 4 | using Terraria.GameContent.UI.Chat; 5 | using Terraria; 6 | using Terraria.Graphics; 7 | using Microsoft.Xna.Framework.Graphics; 8 | using Microsoft.Xna.Framework; 9 | 10 | namespace RecipeBrowser 11 | { 12 | internal class UIRadioButtonGroup : UIElement 13 | { 14 | private int idCount = 0; 15 | 16 | public UIRadioButtonGroup() 17 | { 18 | this.Height.Set(20f, 0f); 19 | this.Width.Set(0f, 1f); 20 | } 21 | 22 | public virtual void Add(UIRadioButton radioButton) 23 | { 24 | radioButton.partOfGroup = true; 25 | radioButton.groupID = idCount; 26 | radioButton.Top.Set(20f * idCount, 0f); 27 | idCount++; 28 | Append(radioButton); 29 | Height.Set(20f * idCount, 0f); 30 | Recalculate(); 31 | } 32 | 33 | internal void ButtonClicked(int id) 34 | { 35 | for (int i = 0; i < idCount; i++) 36 | { 37 | (Elements[i] as UIRadioButton).Selected = false; 38 | } 39 | (Elements[id] as UIRadioButton).Selected = true; 40 | } 41 | 42 | protected override void DrawSelf(SpriteBatch spriteBatch) 43 | { 44 | //Rectangle hitbox = GetInnerDimensions().ToRectangle(); 45 | //Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.Blue); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /UIElements/UIRecipeCatalogueQueryItemSlot.cs: -------------------------------------------------------------------------------- 1 | using Terraria; 2 | using Terraria.UI; 3 | 4 | namespace RecipeBrowser.UIElements 5 | { 6 | internal class UIRecipeCatalogueQueryItemSlot : UIQueryItemSlot 7 | { 8 | public UIRecipeCatalogueQueryItemSlot(Item item) : base(item) 9 | { 10 | } 11 | 12 | public override void LeftClick(UIMouseEvent evt) 13 | { 14 | base.LeftClick(evt); 15 | ReplaceWithFake(item.type); 16 | RecipeCatalogueUI.instance.queryLootItem = (item.type == 0) ? null : item; 17 | RecipeCatalogueUI.instance.updateNeeded = true; 18 | SharedUI.instance.SelectedCategory = SharedUI.instance.categories[0]; 19 | } 20 | 21 | internal override void ReplaceWithFake(int type) 22 | { 23 | base.ReplaceWithFake(type); 24 | RecipeCatalogueUI.instance.queryLootItem = item; 25 | RecipeCatalogueUI.instance.updateNeeded = true; 26 | RecipeCatalogueUI.instance.Tile = -1; 27 | RecipeCatalogueUI.instance.TileLookupRadioButton.Selected = false; 28 | SharedUI.instance.SelectedCategory = SharedUI.instance.categories[0]; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /UIElements/UIRecipeInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using RecipeBrowser.UIElements; 4 | using System.Text; 5 | using Terraria; 6 | using Terraria.GameContent; 7 | using Terraria.GameContent.UI.Elements; 8 | using Terraria.Localization; 9 | using Terraria.UI; 10 | 11 | namespace RecipeBrowser 12 | { 13 | internal class UIRecipeInfo : UIElement 14 | { 15 | // TODO: Fix order of ingredients to match recipe. 16 | // TODO: Use Tile images here as well? Optional? // internal List tileList; 17 | internal UIHorizontalGrid craftingIngredientsGrid; 18 | 19 | public UIRecipeInfo() 20 | { 21 | UIPanel craftingPanel = new UIPanel(); 22 | craftingPanel.SetPadding(6); 23 | craftingPanel.Top.Set(-50, 1f); 24 | craftingPanel.Left.Set(180, 0f); 25 | craftingPanel.Width.Set(-180 - 2, 1f); //- 50 26 | craftingPanel.Height.Set(50, 0f); 27 | craftingPanel.BackgroundColor = Color.CornflowerBlue; 28 | Append(craftingPanel); 29 | 30 | craftingIngredientsGrid = new UIHorizontalGrid(); 31 | craftingIngredientsGrid.Width.Set(0, 1f); 32 | craftingIngredientsGrid.Height.Set(0, 1f); 33 | craftingIngredientsGrid.ListPadding = 2f; 34 | craftingIngredientsGrid.drawArrows = true; 35 | craftingPanel.Append(craftingIngredientsGrid); 36 | 37 | var craftingTilesGridScrollbar = new InvisibleFixedUIHorizontalScrollbar(RecipeBrowserUI.instance.userInterface); 38 | craftingTilesGridScrollbar.SetView(100f, 1000f); 39 | craftingTilesGridScrollbar.Width.Set(0, 1f); 40 | craftingTilesGridScrollbar.Top.Set(-20, 1f); 41 | //craftingPanel.Append(craftingTilesGridScrollbar); 42 | craftingIngredientsGrid.SetScrollbar(craftingTilesGridScrollbar); 43 | 44 | //tileList = new List(); 45 | } 46 | 47 | private const int cols = 5; 48 | 49 | protected override void DrawSelf(SpriteBatch spriteBatch) 50 | { 51 | //Rectangle hitbox = GetInnerDimensions().ToRectangle(); 52 | //Main.spriteBatch.Draw(Main.magicPixel, hitbox, Color.LightBlue * 0.6f); 53 | 54 | if (RecipeCatalogueUI.instance.selectedIndex < 0) return; 55 | 56 | Recipe selectedRecipe = Main.recipe[RecipeCatalogueUI.instance.selectedIndex]; 57 | 58 | CalculatedStyle innerDimensions = GetInnerDimensions(); 59 | Vector2 pos = innerDimensions.Position(); 60 | 61 | float positionX = pos.X; 62 | float positionY = pos.Y; 63 | if (selectedRecipe != null) 64 | { 65 | StringBuilder sb = new StringBuilder(); 66 | StringBuilder sbTiles = new StringBuilder(); 67 | 68 | sb.Append($"[c/{Utilities.textColor.Hex3()}:{Language.GetTextValue("LegacyInterface.22")}] "); 69 | Terraria.UI.Chat.ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.MouseText.Value, Language.GetTextValue("LegacyInterface.22"), new Vector2((float)positionX, (float)(positionY)), Utilities.textColor, 0f, Vector2.Zero, Vector2.One, -1f, 2f); 70 | int row = 0; 71 | int tileIndex = 0; 72 | bool comma = false; 73 | if(selectedRecipe.requiredTile.Count == 0) { 74 | sb.Append($"{(comma ? ", " : "")}[c/{Utilities.textColor.Hex3()}:{Language.GetTextValue("LegacyInterface.23")}]"); 75 | sbTiles.Append($"{(comma ? ", " : "")}[c/{Utilities.textColor.Hex3()}:{Language.GetTextValue("LegacyInterface.23")}]"); 76 | comma = true; 77 | } 78 | 79 | while (tileIndex < selectedRecipe.requiredTile.Count) 80 | { 81 | int num63 = (tileIndex + 1) * 26; 82 | if (selectedRecipe.requiredTile[tileIndex] == -1) 83 | { 84 | break; 85 | } 86 | else 87 | { 88 | row++; 89 | int tileID = selectedRecipe.requiredTile[tileIndex]; 90 | string tileName = Utilities.GetTileName(tileID); 91 | //Terraria.UI.Chat.ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.MouseText.Value, tileName, new Vector2(positionX, positionY + num63), Main.LocalPlayer.adjTile[tileID] ? yesColor : noColor, 0f, Vector2.Zero, Vector2.One, -1f, 2f); 92 | DoChatTag(sb, comma, Main.LocalPlayer.adjTile[tileID], tileName); 93 | DoChatTag(sbTiles, comma, Main.LocalPlayer.adjTile[tileID], tileName); 94 | 95 | tileIndex++; 96 | comma = true; 97 | } 98 | } 99 | // white if window not open? 100 | int yAdjust = (row + 1) * 26; 101 | foreach (var condition in selectedRecipe.Conditions) { 102 | bool state = condition.IsMet(); //.RecipeAvailable(selectedRecipe); 103 | string description = condition.Description.Value; 104 | DoChatTag(sb, comma, state, description); 105 | DoChatTag(sbTiles, comma, state, description); 106 | yAdjust += 26; 107 | comma = true; 108 | } 109 | float width = Terraria.UI.Chat.ChatManager.GetStringSize(FontAssets.MouseText.Value, sbTiles.ToString(), Vector2.One).X; 110 | if (width > 170) 111 | { 112 | Vector2 scale = new Vector2(170 / width); 113 | Terraria.UI.Chat.ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.MouseText.Value, sbTiles.ToString(), new Vector2(positionX, positionY + 26), Color.White, 0f, Vector2.Zero, scale, -1f, 2f); 114 | } 115 | else 116 | { 117 | Terraria.UI.Chat.ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.MouseText.Value, sbTiles.ToString(), new Vector2(positionX, positionY + 26), Color.White, 0f, Vector2.Zero, Vector2.One, -1f, 2f); 118 | } 119 | Rectangle rectangle = GetDimensions().ToRectangle(); 120 | rectangle.Width = 180; 121 | //if (IsMouseHovering) 122 | if (rectangle.Contains(Main.MouseScreen.ToPoint()) && Terraria.UI.Chat.ChatManager.GetStringSize(FontAssets.MouseText.Value, sbTiles.ToString(), Vector2.One).X > 180) 123 | { 124 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(sb.ToString()); 125 | // TODO: Issue #79, make multiline?: Terraria.ModLoader.UI.UICommon.TooltipMouseText(string.Join('\n', sb.ToString().Split(','))); 126 | /* Different approach to informing recipe mod source 127 | ModRecipe modRecipe = selectedRecipe as ModRecipe; 128 | if (Terraria.UI.Chat.ChatManager.GetStringSize(FontAssets.MouseText.Value, sbTiles.ToString(), Vector2.One).X > 180) 129 | Main.hoverItemName = sb.ToString() + (modRecipe != null ? $"\n[{modRecipe.mod.DisplayName}]" : ""); 130 | else if (modRecipe != null) 131 | Main.hoverItemName = $"[{modRecipe.mod.DisplayName}]"; 132 | */ 133 | } 134 | } 135 | } 136 | 137 | private void DoChatTag(StringBuilder sb, bool comma, bool state, string text) 138 | { 139 | sb.Append($"{(comma ? ", " : "")}[c/{(state ? Utilities.yesColor : Utilities.noColor).Hex3()}:{text}]"); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /UIElements/UIRecipeInfoRightAligned.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Terraria; 6 | using Terraria.GameContent; 7 | using Terraria.Localization; 8 | using Terraria.UI; 9 | using Terraria.UI.Chat; 10 | 11 | namespace RecipeBrowser 12 | { 13 | internal class UIRecipeInfoRightAligned : UIElement 14 | { 15 | Recipe recipe; 16 | List tiles; 17 | bool needWater; 18 | bool needHoney; 19 | bool needLava; 20 | public UIRecipeInfoRightAligned(Recipe recipe, List tiles, bool needWater, bool needHoney, bool needLava) 21 | { 22 | this.recipe = recipe; 23 | this.tiles = tiles; 24 | this.needWater = needWater; 25 | this.needHoney = needHoney; 26 | this.needLava = needLava; 27 | } 28 | 29 | public override void Update(GameTime gameTime) 30 | { 31 | base.Update(gameTime); 32 | 33 | foreach (var tile in tiles) 34 | { 35 | if (!Utilities.tileTextures.ContainsKey(tile)) 36 | { 37 | Utilities.GenerateTileTexture(tile); 38 | } 39 | } 40 | } 41 | 42 | protected override void DrawSelf(SpriteBatch spriteBatch) 43 | { 44 | if (CraftUI.instance.recipeResultItemSlot.item.IsAir) return; 45 | 46 | CalculatedStyle innerDimensions = GetInnerDimensions(); 47 | Vector2 pos = innerDimensions.Position(); 48 | 49 | float positionX = pos.X; 50 | float positionY = pos.Y; 51 | StringBuilder sbTiles = new StringBuilder(); 52 | 53 | bool comma = false; 54 | 55 | foreach (var condition in recipe.Conditions) { 56 | bool state = condition.IsMet(); 57 | string description = condition.Description.Value; 58 | DoChatTag(sbTiles, comma, state, description); 59 | comma = true; 60 | // Idea: Instead of chat tag, make icons for each condition instead? 61 | } 62 | /* 63 | if (needWater) 64 | { 65 | DoChatTag(sbTiles, comma, Main.LocalPlayer.adjWater, Language.GetTextValue("LegacyInterface.53")); 66 | comma = true; 67 | } 68 | if (needHoney) 69 | { 70 | DoChatTag(sbTiles, comma, Main.LocalPlayer.adjHoney, Language.GetTextValue("LegacyInterface.58")); 71 | comma = true; 72 | } 73 | if (needLava) 74 | { 75 | DoChatTag(sbTiles, comma, Main.LocalPlayer.adjLava, Language.GetTextValue("LegacyInterface.56")); 76 | comma = true; 77 | } 78 | */ 79 | string message = sbTiles.ToString(); 80 | float stringWidth = ChatManager.GetStringSize(FontAssets.MouseText.Value, message, Vector2.One).X; 81 | ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.MouseText.Value, message, new Vector2(positionX - stringWidth, positionY), Color.White, 0f, Vector2.Zero, Vector2.One, -1f, 2f); 82 | stringWidth += 2; 83 | int drawNumber = 0; 84 | for (int i = 0; i < tiles.Count; i++) 85 | { 86 | int tile = tiles[i]; 87 | //if (!Main.LocalPlayer.adjTile[tile]) // Show all, use ✓, X, and ? 88 | { 89 | Texture2D texture; 90 | Utilities.tileTextures.TryGetValue(tile, out texture); 91 | if (texture != null) 92 | { 93 | drawNumber++; 94 | int height = texture.Height; 95 | int width = texture.Width; 96 | float drawScale = 1f; 97 | float availableWidth = 22; 98 | if (width > availableWidth || height > availableWidth) 99 | { 100 | if (width > height) 101 | { 102 | drawScale = availableWidth / width; 103 | } 104 | else 105 | { 106 | drawScale = availableWidth / height; 107 | } 108 | } 109 | 110 | //spriteBatch.Draw(Main.magicPixel, new Rectangle((int)(positionX - stringWidth - tiles.Count * 24 + i * 24), (int)positionY, 22, 22), Color.Red * 0.6f); 111 | spriteBatch.Draw(texture, new Vector2(positionX - stringWidth - drawNumber * 24 + 11, positionY + 11), null, Color.White, 0f, texture.Size() * 0.5f, drawScale, SpriteEffects.None, 0f); 112 | ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.ItemStack.Value, Main.LocalPlayer.adjTile[tile] ? "✓" : RecipeBrowserPlayer.seenTiles[tile] ? "X" : "?", new Vector2(positionX - stringWidth - drawNumber * 24, positionY) + new Vector2(14f, 10f), Main.LocalPlayer.adjTile[tile] ? Utilities.yesColor : RecipeBrowserPlayer.seenTiles[tile] ? Utilities.maybeColor : Utilities.noColor, 0f, Vector2.Zero, new Vector2(0.7f)); 113 | 114 | Rectangle rectangle = new Rectangle((int)(positionX - stringWidth - drawNumber * 24), (int)(positionY), 22, 22); 115 | if (rectangle.Contains(Main.MouseScreen.ToPoint())) 116 | { 117 | string tileName = Utilities.GetTileName(tile); 118 | Terraria.ModLoader.UI.UICommon.TooltipMouseText($"[c/{(Main.LocalPlayer.adjTile[tile] ? Utilities.yesColor : RecipeBrowserPlayer.seenTiles[tile] ? Utilities.maybeColor : Utilities.noColor).Hex3()}:{(Main.LocalPlayer.adjTile[tile] ? "" : RecipeBrowserPlayer.seenTiles[tile] ? CraftUI.RBText("Missing") : CraftUI.RBText("Unseen"))}{tileName}]"); 119 | } 120 | } 121 | } 122 | } 123 | // TODO: RecipeAvailable Icon or something. 124 | } 125 | 126 | private void DoChatTag(StringBuilder sb, bool comma, bool state, string text) 127 | { 128 | sb.Append($"{(comma ? ", " : "")}[c/{(state ? Utilities.yesColor : Utilities.noColor).Hex3()}:{text}]"); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /UIElements/UIRecipePath.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using RecipeBrowser.TagHandlers; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using Terraria; 7 | using Terraria.GameContent; 8 | using Terraria.GameContent.UI.Elements; 9 | using Terraria.ID; 10 | using Terraria.Localization; 11 | 12 | namespace RecipeBrowser.UIElements 13 | { 14 | class UIRecipePath : UIPanel 15 | { 16 | private CraftPath path; 17 | const int verticalSpace = 24; 18 | const int HorizontalTab = 20; 19 | 20 | // Using Chat Tags is cool, but I can't assign individual code to them. 21 | // They are first and foremost just Text, made to look like things. 22 | // Word Wrap is possible with UIMessageBox code, but scrollbar is needed? With UIGrid, I could probably do this better 23 | public UIRecipePath(CraftPath path) 24 | { 25 | this.path = path; 26 | 27 | SetPadding(6); 28 | Width.Set(0, 1f); 29 | int top = 0; 30 | // Craftable 31 | // Total Money Cost 32 | var totalItemCost = new Dictionary(); 33 | // Does all the other lines. 34 | int count = Traverse(path.root, 0, ref top, totalItemCost); 35 | 36 | var neededTiles = new HashSet(path.root.GetAllChildrenPreOrder().OfType().SelectMany(x => x.recipe.requiredTile)); 37 | neededTiles.Remove(-1); 38 | var needWater = path.root.GetAllChildrenPreOrder().OfType().Any(x => x.recipe.HasCondition(Condition.NearWater)); 39 | var needHoney = path.root.GetAllChildrenPreOrder().OfType().Any(x => x.recipe.HasCondition(Condition.NearHoney)); 40 | var needLava = path.root.GetAllChildrenPreOrder().OfType().Any(x => x.recipe.HasCondition(Condition.NearLava)); 41 | 42 | var missingTiles = neededTiles.Where(x => !Main.LocalPlayer.adjTile[x]); 43 | 44 | StringBuilder sb = new StringBuilder(); 45 | sb.Append(CraftUI.RBText("Cost")); 46 | foreach (var data in totalItemCost) 47 | { 48 | if (data.Key == ItemID.CopperCoin) // Assuming Coins not used as ingredients, fix if problem. 49 | sb.Append(CraftPath.BuyItemNode.GetTotalCostAsTags(data.Value)); 50 | else 51 | sb.Append(ItemHoverFixTagHandler.GenerateTag(data.Key, data.Value)); 52 | } 53 | 54 | // Maybe have a summary tiles needed if Full Craft implemented 55 | 56 | var drawTextSnippets = UIMessageBox.WordwrapStringSmart(sb.ToString(), Color.White, FontAssets.MouseText.Value, 300, -1); 57 | foreach (var textSnippet in drawTextSnippets) 58 | { 59 | string s = string.Concat(textSnippet.Select(x => x.TextOriginal)); 60 | UITextSnippet snippet = new UITextSnippet(s); 61 | snippet.Top.Set(top, 0); 62 | snippet.Left.Set(0, 0); 63 | Append(snippet); 64 | top += verticalSpace; 65 | count++; 66 | } 67 | 68 | Height.Set(count * verticalSpace + PaddingBottom + PaddingTop, 0f); 69 | 70 | // TODO: Full Craft Button 71 | //var craftButton = new UITextPanel("Craft"); 72 | //craftButton.Top.Set(-38, 1f); 73 | //craftButton.Left.Set(-63, 1f); 74 | //craftButton.OnClick += CraftButton_OnClick; 75 | //Append(craftButton); 76 | } 77 | 78 | private int Traverse(CraftPath.CraftPathNode node, int left, ref int top, Dictionary totalItemCost) 79 | { 80 | int count = 1; 81 | 82 | StringBuilder sb = new StringBuilder(); 83 | var recipeNode = node as CraftPath.RecipeNode; 84 | if (recipeNode != null) 85 | { 86 | sb.Append(ItemHoverFixTagHandler.GenerateTag(recipeNode.recipe.createItem.type, recipeNode.recipe.createItem.stack * recipeNode.multiplier)); 87 | sb.Append('<'); 88 | for (int i = 0; i < recipeNode.recipe.requiredItem.Count; i++) 89 | { 90 | Item item = recipeNode.recipe.requiredItem[i]; 91 | bool check = recipeNode.children[i] is CraftPath.HaveItemNode; 92 | 93 | string nameOverride = RecipeCatalogueUI.OverrideForGroups(recipeNode.recipe, item.type); 94 | sb.Append(ItemHoverFixTagHandler.GenerateTag(item.type, item.stack * recipeNode.multiplier, nameOverride, check)); 95 | } 96 | } 97 | else 98 | { 99 | if (node is CraftPath.HaveItemNode) 100 | { 101 | count--; 102 | } 103 | else 104 | { 105 | sb.Append(node.ToUITextString()); 106 | } 107 | } 108 | 109 | var haveItemNode = node as CraftPath.HaveItemNode; 110 | if (haveItemNode != null) 111 | { 112 | totalItemCost.Adjust(haveItemNode.itemid, haveItemNode.stack); 113 | } 114 | var haveItemsNode = node as CraftPath.HaveItemsNode; 115 | if (haveItemsNode != null) 116 | { 117 | foreach (var item in haveItemsNode.listOfItems) 118 | { 119 | totalItemCost.Adjust(item.Item1, item.Item2); 120 | } 121 | } 122 | if(node is CraftPath.BuyItemNode buyItemNode) { 123 | totalItemCost.Adjust(ItemID.CopperCoin, buyItemNode.TotalPrice); 124 | } 125 | 126 | if (sb.Length > 0) 127 | { 128 | var snippet = new UITextSnippet(sb.ToString()); 129 | snippet.Top.Set(top, 0); 130 | snippet.Left.Set(left, 0); 131 | Append(snippet); 132 | 133 | if (recipeNode != null) 134 | { 135 | var neededTiles = new HashSet(recipeNode.recipe.requiredTile); 136 | neededTiles.Remove(-1); 137 | var needWater = recipeNode.recipe.HasCondition(Condition.NearWater); 138 | var needHoney = recipeNode.recipe.HasCondition(Condition.NearHoney); 139 | var needLava = recipeNode.recipe.HasCondition(Condition.NearLava); 140 | 141 | UIRecipeInfoRightAligned simpleRecipeInfo = new UIRecipeInfoRightAligned(recipeNode.recipe, neededTiles.ToList(), needWater, needHoney, needLava); 142 | simpleRecipeInfo.Top.Set(top, 0); 143 | simpleRecipeInfo.Left.Set(-30, 1f); 144 | Append(simpleRecipeInfo); 145 | 146 | UICraftButton craftButton = new UICraftButton(recipeNode, recipeNode.recipe); 147 | craftButton.Top.Set(top, 0); 148 | craftButton.Left.Set(-26, 1f); 149 | Append(craftButton); 150 | } 151 | if (node is CraftPath.JourneyDuplicateItemNode duplicationNode) { 152 | UIJourneyDuplicateButton duplicationButton = new UIJourneyDuplicateButton(duplicationNode); 153 | duplicationButton.Top.Set(top, 0); 154 | duplicationButton.Left.Set(-26, 1f); 155 | Append(duplicationButton); 156 | } 157 | top += verticalSpace; 158 | } 159 | 160 | if (node.children != null) 161 | foreach (var child in node.children) 162 | { 163 | if (child != null) 164 | count += Traverse(child, left + HorizontalTab, ref top, totalItemCost); 165 | } 166 | return count; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /UIElements/UIRecipeProgress.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Reflection; 4 | using ReLogic.Content; 5 | using Terraria; 6 | using Terraria.Graphics; 7 | using Terraria.ModLoader; 8 | using Terraria.UI; 9 | 10 | namespace RecipeBrowser.UIElements 11 | { 12 | internal class UIRecipeProgress : UIElement 13 | { 14 | private int order; 15 | private int owner; // which player are we tracking the progress for. 16 | Asset playerBackGroundTexture; 17 | 18 | public UIRecipeProgress(int index, Recipe recipe, int order, int owner) 19 | { 20 | playerBackGroundTexture = ModContent.Request("Terraria/Images/UI/PlayerBackground"); 21 | this.order = order; 22 | this.owner = owner; 23 | // TODO: Implement Craft Path for teammates. 24 | UIMockRecipeSlot create = new UIMockRecipeSlot(RecipeCatalogueUI.instance.recipeSlots[index], owner != Main.myPlayer ? .5f : 0.75f); 25 | create.Recalculate(); 26 | create.Left.Set(-create.Width.Pixels - (owner != Main.myPlayer ? 23 : 0), 1f); 27 | var b = create.GetOuterDimensions(); 28 | Append(create); 29 | int x = (owner != Main.myPlayer ? 23 : 0); 30 | x += (int)b.Width + 2; 31 | int y = 0; 32 | int maxX = x; 33 | int maxRecipesPerRow = owner != Main.myPlayer ? 8: 6; 34 | for (int j = 0; j < recipe.requiredItem.Count; j++) 35 | { 36 | Item item = new Item(); 37 | item.SetDefaults(recipe.requiredItem[j].type); 38 | UITrackIngredientSlot ingredient = 39 | new UITrackIngredientSlot(item, recipe.requiredItem[j].stack, recipe, j, owner, owner != Main.myPlayer ? .5f : 0.75f); 40 | if (j % maxRecipesPerRow == 0 && j > 0) { 41 | maxX = System.Math.Max(maxX, x); 42 | x = (owner != Main.myPlayer ? 23 : 0); 43 | x += (int)b.Width + 2; 44 | y += (int)b.Height + 2; 45 | } 46 | x += (int)b.Width + 2; 47 | ingredient.Left.Set(-x, 1f); 48 | ingredient.Top.Set(y, 0f); 49 | 50 | RecipeCatalogueUI.OverrideForGroups(recipe, ingredient.item); 51 | 52 | Append(ingredient); 53 | } 54 | Height.Pixels = y + b.Height; 55 | Width.Pixels = System.Math.Max(maxX, x) + 12; 56 | 57 | // Center recipe result vertically. Feedback said top aligned is better. 58 | //create.Top.Set(y / 2, 0f); 59 | } 60 | 61 | private bool updateNeeded; 62 | 63 | public override void Update(GameTime gameTime) 64 | { 65 | if (!updateNeeded) return; 66 | updateNeeded = false; 67 | } 68 | 69 | public override int CompareTo(object obj) 70 | { 71 | UIRecipeProgress other = obj as UIRecipeProgress; 72 | return order.CompareTo(other.order); 73 | } 74 | 75 | protected override void DrawSelf(SpriteBatch spriteBatch) 76 | { 77 | base.DrawSelf(spriteBatch); 78 | if (IsMouseHovering && owner != Main.myPlayer) 79 | { 80 | Main.hoverItemName = Main.player[owner].name; //+ "'s Recipe"; 81 | var a = GetInnerDimensions().ToRectangle(); 82 | Main.MapPlayerRenderer.DrawPlayerHead(Main.Camera, Main.player[owner], new Vector2(a.Right - 16, a.Y + 8), 1f, 1f, Color.White); 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /UIElements/UIRecipeSlot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using ReLogic.Content; 8 | using Terraria; 9 | using Terraria.Audio; 10 | using Terraria.GameContent; 11 | using Terraria.GameContent.UI.Chat; 12 | using Terraria.UI; 13 | using Terraria.UI.Chat; 14 | using Terraria.ID; 15 | using RecipeBrowser.TagHandlers; 16 | 17 | namespace RecipeBrowser.UIElements 18 | { 19 | internal class UIRecipeSlot : UIItemSlot 20 | { 21 | public static Asset selectedBackgroundTexture; 22 | public static Asset recentlyDiscoveredBackgroundTexture = TextureAssets.InventoryBack8; 23 | public static Asset favoritedBackgroundTexture; 24 | public static Asset ableToCraftBackgroundTexture; 25 | public static Asset ableToCraftExtendedBackgroundTexture; 26 | public int index; 27 | public bool recentlyDiscovered; 28 | public bool favorited; 29 | public bool selected; 30 | 31 | // Single vs All needed. 32 | public bool craftPathNeeded; 33 | public bool craftPathCalculated; 34 | public bool craftPathCalculationBegun; 35 | internal CancellationTokenSource craftPathCancellationTokenSource; 36 | 37 | // seen limits craftPaths calculation 38 | public bool craftPathsCalculated; 39 | public List craftPaths; 40 | 41 | public UIRecipeSlot(int index, float scale = 0.75f) : base(Main.recipe[index].createItem, scale) 42 | { 43 | this.index = index; 44 | } 45 | 46 | public override void LeftClick(UIMouseEvent evt) 47 | { 48 | if (Main.keyState.IsKeyDown(Main.FavoriteKey)) 49 | { 50 | if (Main.drawingPlayerChat) 51 | { 52 | StringBuilder sb = new StringBuilder(); 53 | foreach (var reqItem in Main.recipe[index].requiredItem) 54 | { 55 | sb.Append(ItemHoverFixTagHandler.GenerateTag(reqItem, itemTooltip: true)); 56 | } 57 | sb.Append("-->"); 58 | sb.Append(ItemHoverFixTagHandler.GenerateTag(Main.recipe[index].createItem, itemTooltip: true)); 59 | if (ChatManager.AddChatText(FontAssets.MouseText.Value, sb.ToString(), Vector2.One)) 60 | { 61 | SoundEngine.PlaySound(SoundID.MenuTick); 62 | } 63 | } 64 | else 65 | RecipeBrowserUI.instance.FavoriteChange(this.index, !favorited); 66 | } 67 | else 68 | { 69 | RecipeCatalogueUI.instance.SetRecipe(index); 70 | RecipeCatalogueUI.instance.queryLootItem = Main.recipe[index].createItem; 71 | RecipeCatalogueUI.instance.updateNeeded = true; 72 | } 73 | 74 | for (int n = 0; n < Main.numAvailableRecipes; n++) 75 | { 76 | if (index == Main.availableRecipe[n]) 77 | { 78 | Main.playerInventory = true; 79 | Main.focusRecipe = n; 80 | Main.recFastScroll = true; 81 | break; 82 | } 83 | } 84 | 85 | // Idea: Glint the CraftUI tab if extended craft available? 86 | } 87 | 88 | public override void LeftDoubleClick(UIMouseEvent evt) 89 | { 90 | if (Main.keyState.IsKeyDown(Main.FavoriteKey)) 91 | return; 92 | RecipeCatalogueUI.instance.itemDescriptionFilter.SetText(""); 93 | RecipeCatalogueUI.instance.itemNameFilter.SetText(""); 94 | RecipeCatalogueUI.instance.queryItem.ReplaceWithFake(item.type); 95 | } 96 | 97 | public override void RightClick(UIMouseEvent evt) 98 | { 99 | // TODO: Idea. Draw hinted craft path and suggest right click to view it? 100 | base.RightClick(evt); 101 | 102 | RecipeCatalogueUI.instance.SetRecipe(index); 103 | RecipeCatalogueUI.instance.queryLootItem = Main.recipe[index].createItem; 104 | RecipeCatalogueUI.instance.updateNeeded = true; 105 | 106 | RecipeBrowserUI.instance.tabController.SetPanel(RecipeBrowserUI.Craft); 107 | CraftUI.instance.SetRecipe(index); 108 | //Main.NewText($"{craftPaths.Count} path(s) found:"); 109 | } 110 | 111 | public override int CompareTo(object obj) 112 | { 113 | UIRecipeSlot other = obj as UIRecipeSlot; 114 | if (favorited && !other.favorited) 115 | { 116 | return -1; 117 | } 118 | if (!favorited && other.favorited) 119 | { 120 | return 1; 121 | } 122 | if (recentlyDiscovered && !other.recentlyDiscovered) 123 | { 124 | return -1; 125 | } 126 | if (!recentlyDiscovered && other.recentlyDiscovered) 127 | { 128 | return 1; 129 | } 130 | if (favorited && other.favorited) 131 | { 132 | return RecipeBrowserUI.instance.localPlayerFavoritedRecipes.IndexOf(this.index).CompareTo(RecipeBrowserUI.instance.localPlayerFavoritedRecipes.IndexOf(other.index)); 133 | } 134 | return index.CompareTo(other.index); 135 | } 136 | 137 | public int CompareToIgnoreIndex(UIRecipeSlot other) 138 | { 139 | if (favorited && !other.favorited) 140 | { 141 | return -1; 142 | } 143 | if (!favorited && other.favorited) 144 | { 145 | return 1; 146 | } 147 | if (recentlyDiscovered && !other.recentlyDiscovered) 148 | { 149 | return -1; 150 | } 151 | if (!recentlyDiscovered && other.recentlyDiscovered) 152 | { 153 | return 1; 154 | } 155 | if (favorited && other.favorited) 156 | { 157 | return RecipeBrowserUI.instance.localPlayerFavoritedRecipes.IndexOf(this.index).CompareTo(RecipeBrowserUI.instance.localPlayerFavoritedRecipes.IndexOf(other.index)); 158 | } 159 | return 0; 160 | } 161 | 162 | protected override void DrawSelf(SpriteBatch spriteBatch) 163 | { 164 | if (RecipePath.extendedCraft) 165 | craftPathNeeded = true; 166 | if (IsMouseHovering) 167 | { 168 | if (Main.keyState.IsKeyDown(Main.FavoriteKey)) 169 | if (Main.drawingPlayerChat) 170 | Main.cursorOverride = 2; 171 | else 172 | Main.cursorOverride = 3; 173 | RecipeCatalogueUI.instance.hoveredIndex = index; 174 | } 175 | 176 | backgroundTexture = defaultBackgroundTexture; 177 | 178 | if ((craftPathCalculated || craftPathsCalculated) && craftPaths.Count > 0) 179 | backgroundTexture = ableToCraftExtendedBackgroundTexture; 180 | 181 | for (int n = 0; n < Main.numAvailableRecipes; n++) 182 | { 183 | if (index == Main.availableRecipe[n]) 184 | { 185 | backgroundTexture = ableToCraftBackgroundTexture; 186 | break; 187 | } 188 | } 189 | 190 | if (recentlyDiscovered) 191 | backgroundTexture = recentlyDiscoveredBackgroundTexture; 192 | 193 | base.DrawSelf(spriteBatch); 194 | } 195 | 196 | internal override void DrawAdditionalOverlays(SpriteBatch spriteBatch, Vector2 vector2, float scale) 197 | { 198 | if (favorited) 199 | spriteBatch.Draw(favoritedBackgroundTexture.Value, vector2, null, Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); 200 | if (selected) 201 | spriteBatch.Draw(selectedBackgroundTexture.Value, vector2, null, Color.White * Main.essScale, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); 202 | } 203 | 204 | internal override void DrawAdditionalBadges(SpriteBatch spriteBatch, Vector2 vector2, float scale) { 205 | base.DrawAdditionalBadges(spriteBatch, vector2, scale); 206 | if (Main.recipe[index].Disabled) 207 | ChatManager.DrawColorCodedStringWithShadow(Main.spriteBatch, FontAssets.ItemStack.Value, "X", vector2 + new Vector2(32f, 32f) * scale, Color.LightSalmon, 0f, Vector2.Zero, new Vector2(0.7f)); 208 | } 209 | 210 | public override void Update(GameTime gameTime) 211 | { 212 | base.Update(gameTime); 213 | // Is this called even when not drawn? Yes. Need Draw flag 214 | if (craftPathNeeded && !(craftPathCalculated || craftPathsCalculated) && !craftPathCalculationBegun) 215 | { 216 | DoGetCraftPath(); 217 | } 218 | } 219 | 220 | internal void CraftPathsImmediatelyNeeded() 221 | { 222 | if (!craftPathsCalculated) 223 | { 224 | RecipePath.PrepareGetCraftPaths(); 225 | craftPaths = RecipePath.GetCraftPaths(Main.recipe[index], CancellationToken.None, false); 226 | craftPathsCalculated = true; 227 | } 228 | } 229 | 230 | internal void CraftPathNeeded() 231 | { 232 | craftPathNeeded = true; 233 | if (!(craftPathCalculated || craftPathsCalculated) && !craftPathCalculationBegun) 234 | { 235 | DoGetCraftPath(); 236 | } 237 | } 238 | 239 | private void DoGetCraftPath() 240 | { 241 | //var firstTask = Task.Run(() => craftPaths = RecipePath.GetCraftPaths(Main.recipe[index], cancellationTokenSource.Token), cancellationTokenSource.Token); 242 | RecipePath.PrepareGetCraftPaths(); 243 | var task = new Task(TaskAction); 244 | RecipeBrowser.instance.concurrentTasks.Enqueue(task); 245 | //firstTask.Start(); 246 | craftPathCalculationBegun = true; 247 | } 248 | 249 | private void TaskAction() { 250 | craftPathCancellationTokenSource = new CancellationTokenSource(1); // TODO: Configurable 251 | // could a full override a simple? 252 | craftPaths = RecipePath.GetCraftPaths(Main.recipe[index], craftPathCancellationTokenSource.Token, true); 253 | if (!craftPathCancellationTokenSource.Token.IsCancellationRequested) { 254 | craftPathCalculated = true; 255 | //RecipeCatalogueUI.instance.updateNeeded = true; 256 | if (RecipeCatalogueUI.instance.slowUpdateNeeded == 0) 257 | RecipeCatalogueUI.instance.slowUpdateNeeded = 5; 258 | if (ItemCatalogueUI.instance.slowUpdateNeeded == 0) 259 | ItemCatalogueUI.instance.slowUpdateNeeded = 30; 260 | } 261 | } 262 | } 263 | } -------------------------------------------------------------------------------- /UIElements/UISilentImageButton.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Content; 4 | using ReLogic.Graphics; 5 | using Terraria; 6 | using Terraria.GameContent; 7 | using Terraria.UI; 8 | using Terraria.UI.Chat; 9 | 10 | namespace RecipeBrowser.UIElements 11 | { 12 | // A bit confusing, don't use. 13 | class UIBadgedSilentImageButton : UISilentImageButton 14 | { 15 | internal bool drawX = false; 16 | public UIBadgedSilentImageButton(Asset texture, string hoverText) : base(texture, hoverText) { 17 | } 18 | 19 | protected override void DrawSelf(SpriteBatch spriteBatch) { 20 | base.DrawSelf(spriteBatch); 21 | if (drawX) { 22 | CalculatedStyle dimensions = base.GetDimensions(); 23 | //ChatManager.DrawColorCodedStringWithShadow(spriteBatch, Main.fontItemStack, "X", dimensions.Position() + new Vector2(14f, 10f), Color.LightSalmon, 0f, Vector2.Zero, new Vector2(0.7f)); 24 | var r = dimensions.ToRectangle(); 25 | r.Inflate(-2, -2); 26 | spriteBatch.Draw(TextureAssets.Cd.Value, r, null, Color.White, 0, Vector2.Zero, SpriteEffects.None, 0); 27 | } 28 | } 29 | } 30 | 31 | class UISilentImageButton : UIElement 32 | { 33 | private Asset _texture; 34 | private float _visibilityActive = 1f; 35 | private float _visibilityHovered = .9f; 36 | private float _visibilityInactive = 0.8f; // or color? same thing? 37 | 38 | public bool selected; 39 | internal string hoverText; 40 | 41 | public UISilentImageButton(Asset texture, string hoverText) { 42 | this._texture = texture; 43 | this.Width.Set((float)this._texture.Value.Width, 0f); 44 | this.Height.Set((float)this._texture.Value.Height, 0f); 45 | this.hoverText = hoverText; 46 | 47 | base.Recalculate(); 48 | } 49 | 50 | public void SetImage(Asset texture) { 51 | this._texture = texture; 52 | this.Width.Set((float)this._texture.Value.Width, 0f); 53 | this.Height.Set((float)this._texture.Value.Height, 0f); 54 | 55 | base.Recalculate(); 56 | } 57 | 58 | protected override void DrawSelf(SpriteBatch spriteBatch) { 59 | if (selected) { 60 | var r = GetDimensions().ToRectangle(); 61 | r.Inflate(0, 0); 62 | //spriteBatch.Draw(UIElements.UIRecipeSlot.selectedBackgroundTexture, r, Color.White); 63 | spriteBatch.Draw(TextureAssets.InventoryBack14.Value, r, Color.White); 64 | } 65 | 66 | CalculatedStyle dimensions = base.GetDimensions(); 67 | spriteBatch.Draw(this._texture.Value, dimensions.Position(), Color.White * (selected ? _visibilityActive : (IsMouseHovering ? _visibilityHovered : this._visibilityInactive))); 68 | if (IsMouseHovering) { 69 | // Main.hoverItemName = hoverText; 70 | if (!string.IsNullOrWhiteSpace(hoverText)) 71 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(hoverText); 72 | } 73 | 74 | if (this == SharedUI.instance.ObtainableFilter.button && IsMouseHovering) { 75 | if(RecipeBrowser.instance.concurrentTasks.Count > 0) 76 | Terraria.ModLoader.UI.UICommon.TooltipMouseText($"{hoverText}\n{RecipeBrowser.instance.concurrentTasks.Count} {SharedUI.RBText("RecipesRemainToBeCalculated")}"); 77 | //spriteBatch.DrawString(FontAssets.MouseText.Value, UISystem.Instance.concurrentTasks.Count + "", dimensions.Position(), Color.White); 78 | } 79 | } 80 | 81 | public override void MouseOver(UIMouseEvent evt) { 82 | base.MouseOver(evt); 83 | //Main.PlaySound(12, -1, -1, 1, 1f, 0f); 84 | } 85 | 86 | //public void SetVisibility(float whenActive, float whenInactive) 87 | //{ 88 | // this._visibilityActive = MathHelper.Clamp(whenActive, 0f, 1f); 89 | // this._visibilityInactive = MathHelper.Clamp(whenInactive, 0f, 1f); 90 | //} 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /UIElements/UITabControl.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Terraria.UI; 3 | using Terraria.GameContent.UI.Elements; 4 | using System.Collections.Generic; 5 | 6 | namespace RecipeBrowser 7 | { 8 | internal class UITabControl : UIElement 9 | { 10 | internal UIPanel mainPanel; 11 | private List panels; 12 | private List texts; 13 | 14 | public UITabControl() 15 | { 16 | panels = new List(); 17 | texts = new List(); 18 | mainPanel = new UIPanel(); 19 | Width = StyleDimension.Fill; 20 | Height = StyleDimension.Fill; 21 | mainPanel.SetPadding(6); 22 | mainPanel.Width = StyleDimension.Fill; 23 | mainPanel.Height = StyleDimension.Fill; 24 | mainPanel.BackgroundColor = Color.LightCoral; 25 | } 26 | 27 | public override void OnInitialize() 28 | { 29 | Append(mainPanel); 30 | 31 | SetPanel(0); 32 | } 33 | 34 | public void AddTab(string label, UIPanel panel) 35 | { 36 | UIText text = new UIText(label, 0.85f); 37 | text.Top.Set(0, 0f); 38 | //text.HAlign = 1f; 39 | text.OnLeftClick += Text_OnClick; 40 | mainPanel.Append(text); 41 | texts.Add(text); 42 | 43 | panel.Top.Pixels = 20; 44 | panel.Width = StyleDimension.Fill; 45 | panel.Height = StyleDimension.Fill; 46 | panel.Height.Pixels = -20; 47 | panels.Add(panel); 48 | 49 | // 1 -> 0 50 | // 2 -> 0, 1 51 | // 3 -> 0, 0.5, 1 52 | // 4 -> 0, 0.33, 0.66, 1 53 | if (texts.Count > 1) 54 | { 55 | for (int i = 0; i < texts.Count; i++) 56 | { 57 | texts[i].HAlign = i / (texts.Count - 1f); 58 | } 59 | } 60 | } 61 | 62 | private void Text_OnClick(UIMouseEvent evt, UIElement listeningElement) 63 | { 64 | UIText text = (evt.Target as UIText); 65 | int index = texts.IndexOf(text); 66 | SetPanel(index); 67 | } 68 | 69 | public void SetPanel(int panelIndex) 70 | { 71 | if (panelIndex >= 0 && panelIndex < panels.Count) 72 | { 73 | panels.ForEach(panel => { if (mainPanel.HasChild(panel)) mainPanel.RemoveChild(panel); }); 74 | texts.ForEach(atext => { atext.TextColor = Color.Gray; }); 75 | 76 | mainPanel.Append(panels[panelIndex]); 77 | texts[panelIndex].TextColor = Color.White; 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /UIElements/UITextSnippet.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using ReLogic.Graphics; 4 | using System; 5 | using ReLogic.Content; 6 | using Terraria; 7 | using Terraria.GameContent; 8 | using Terraria.GameContent.UI.Elements; 9 | using Terraria.Localization; 10 | using Terraria.UI; 11 | using Terraria.UI.Chat; 12 | 13 | namespace RecipeBrowser.UIElements 14 | { 15 | // Same as Terraria.GameContent.UI.Elements.UIText except Hover and Click of TextSnippets are supported 16 | class UITextSnippet : UIElement 17 | { 18 | private object _text = ""; 19 | private float _textScale = 1f; 20 | private Vector2 _textSize = Vector2.Zero; 21 | private bool _isLarge; 22 | private Color _color = Color.White; 23 | 24 | public string Text 25 | { 26 | get 27 | { 28 | return this._text.ToString(); 29 | } 30 | } 31 | 32 | public Color TextColor 33 | { 34 | get 35 | { 36 | return this._color; 37 | } 38 | set 39 | { 40 | this._color = value; 41 | } 42 | } 43 | 44 | public string HoverText { get; set; } 45 | 46 | public UITextSnippet(string text, float textScale = 1f, bool large = false) 47 | { 48 | this.InternalSetText(text, textScale, large); 49 | } 50 | 51 | public UITextSnippet(LocalizedText text, float textScale = 1f, bool large = false) 52 | { 53 | this.InternalSetText(text, textScale, large); 54 | } 55 | 56 | public override void Recalculate() 57 | { 58 | this.InternalSetText(this._text, this._textScale, this._isLarge); 59 | base.Recalculate(); 60 | } 61 | 62 | public void SetText(string text) 63 | { 64 | this.InternalSetText(text, this._textScale, this._isLarge); 65 | } 66 | 67 | public void SetText(LocalizedText text) 68 | { 69 | this.InternalSetText(text, this._textScale, this._isLarge); 70 | } 71 | 72 | public void SetText(string text, float textScale, bool large) 73 | { 74 | this.InternalSetText(text, textScale, large); 75 | } 76 | 77 | public void SetText(LocalizedText text, float textScale, bool large) 78 | { 79 | this.InternalSetText(text, textScale, large); 80 | } 81 | 82 | private void InternalSetText(object text, float textScale, bool large) 83 | { 84 | Asset dynamicSpriteFont = large ? FontAssets.DeathText : FontAssets.MouseText; 85 | //Vector2 textSize = new Vector2(dynamicSpriteFont.MeasureString(text.ToString()).X, large ? 32f : 16f) * textScale; 86 | _textSize = ChatManager.GetStringSize(dynamicSpriteFont.Value, Text, new Vector2(textScale)); 87 | this._text = text; 88 | this._textScale = textScale; 89 | //this._textSize = textSize; 90 | this._isLarge = large; 91 | this.MinWidth.Set(_textSize.X + this.PaddingLeft + this.PaddingRight, 0f); 92 | this.MinHeight.Set(_textSize.Y + this.PaddingTop + this.PaddingBottom, 0f); 93 | } 94 | 95 | protected override void DrawSelf(SpriteBatch spriteBatch) 96 | { 97 | base.DrawSelf(spriteBatch); 98 | //spriteBatch.Draw(Main.magicPixel, GetDimensions().ToRectangle(), Color.Red * 0.5f); 99 | 100 | CalculatedStyle innerDimensions = base.GetInnerDimensions(); 101 | Vector2 pos = innerDimensions.Position(); 102 | if (this._isLarge) 103 | { 104 | pos.Y -= 10f * this._textScale; 105 | } 106 | else 107 | { 108 | pos.Y -= 2f * this._textScale; 109 | } 110 | pos.X += (innerDimensions.Width - this._textSize.X) * 0.5f; 111 | //if (this._isLarge) 112 | //{ 113 | // Utils.DrawBorderStringBig(spriteBatch, this.Text, pos, this._color, this._textScale, 0f, 0f, -1); 114 | // return; 115 | //} 116 | //Utils.DrawBorderString(spriteBatch, this.Text, pos, this._color, this._textScale, 0f, 0f, -1); 117 | 118 | if (IsMouseHovering) 119 | Main.hoverItemName = HoverText; 120 | 121 | var font = _isLarge ? FontAssets.DeathText : FontAssets.MouseText; 122 | int hoveredSnippet = -1; 123 | TextSnippet[] textSnippets = ChatManager.ParseMessage(Text, Color.White).ToArray(); 124 | ChatManager.ConvertNormalSnippets(textSnippets); 125 | 126 | ChatManager.DrawColorCodedStringWithShadow(spriteBatch, font.Value, textSnippets, pos, 0f, Vector2.Zero, new Vector2(_textScale), out hoveredSnippet); 127 | if (hoveredSnippet > -1) 128 | { 129 | // annoying click. Main.NewText(hoveredSnippet); 130 | textSnippets[hoveredSnippet].OnHover(); 131 | if (Main.mouseLeft && Main.mouseLeftRelease) 132 | { 133 | textSnippets[hoveredSnippet].OnClick(); 134 | } 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /UIElements/UITileSlot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using System.Linq; 3 | using Terraria; 4 | using Terraria.UI; 5 | using Microsoft.Xna.Framework; 6 | using System; 7 | using ReLogic.Content; 8 | using Terraria.GameContent; 9 | using Terraria.ObjectData; 10 | using Terraria.Map; 11 | using Terraria.ID; 12 | 13 | namespace RecipeBrowser.UIElements 14 | { 15 | class UITileSlot : UIElement 16 | { 17 | public Texture2D backgroundTexture => TextureAssets.InventoryBack9.Value; 18 | public Asset selectedTexture => UIRecipeSlot.selectedBackgroundTexture; 19 | internal float scale = .75f; 20 | public int order; // usage count 21 | public int tile; 22 | public bool selected; 23 | Texture2D texture; 24 | 25 | public UITileSlot(int tile, int order, float scale = 0.75f) 26 | { 27 | this.scale = scale; 28 | this.order = order; 29 | this.tile = tile; 30 | this.Width.Set(backgroundTexture.Width * scale, 0f); 31 | this.Height.Set(backgroundTexture.Height * scale, 0f); 32 | } 33 | 34 | public override void LeftClick(UIMouseEvent evt) 35 | { 36 | //RecipeCatalogueUI.instance.ToggleTileChooser(false); 37 | //RecipeCatalogueUI.instance.queryItem.ReplaceWithFake(item.type); 38 | //RecipeCatalogueUI.instance.TileLookupRadioButton.SetDisabled(false); 39 | //RecipeCatalogueUI.instance.TileLookupRadioButton.Selected = true; 40 | if (selected) 41 | RecipeCatalogueUI.instance.Tile = -1; 42 | else 43 | RecipeCatalogueUI.instance.Tile = tile; 44 | } 45 | 46 | public override void LeftDoubleClick(UIMouseEvent evt) 47 | { 48 | //RecipeCatalogueUI.instance.queryItem.ReplaceWithFake(0); 49 | //RecipeCatalogueUI.instance.Tile = tile; 50 | //RecipeCatalogueUI.instance.ToggleTileChooser(false); 51 | } 52 | 53 | public override int CompareTo(object obj) 54 | { 55 | UITileSlot other = obj as UITileSlot; 56 | return -order.CompareTo(other.order); 57 | } 58 | 59 | public override void Update(GameTime gameTime) 60 | { 61 | if (texture == null) 62 | { 63 | if (!Utilities.tileTextures.ContainsKey(tile)) 64 | { 65 | Utilities.GenerateTileTexture(tile); 66 | } 67 | texture = Utilities.tileTextures[tile]; 68 | } 69 | } 70 | 71 | protected override void DrawSelf(SpriteBatch spriteBatch) 72 | { 73 | if (texture == null) 74 | return; 75 | 76 | CalculatedStyle dimensions = base.GetInnerDimensions(); 77 | Rectangle rectangle = dimensions.ToRectangle(); 78 | spriteBatch.Draw(backgroundTexture, dimensions.Position(), null, Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); 79 | 80 | if (selected) 81 | spriteBatch.Draw(selectedTexture.Value, dimensions.Position(), null, Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); 82 | 83 | int height = texture.Height; 84 | int width = texture.Width; 85 | float drawScale = 1f; // larger, uncomment below 86 | float availableWidth = (float)backgroundTexture.Width * scale; 87 | if (width /** drawScale*/ > availableWidth || height /** drawScale*/ > availableWidth) 88 | { 89 | if (width > height) 90 | { 91 | drawScale = availableWidth / width; 92 | } 93 | else 94 | { 95 | drawScale = availableWidth / height; 96 | } 97 | } 98 | drawScale *= scale; 99 | Vector2 vector = backgroundTexture.Size() * scale; 100 | Vector2 position2 = dimensions.Position() + vector / 2f - texture.Size() * drawScale / 2f; 101 | //Vector2 origin = texture.Size() * (1f / 2f - 0.5f); 102 | spriteBatch.Draw(texture, position2, null, Color.White, 0f, Vector2.Zero, drawScale, SpriteEffects.None, 0f); 103 | 104 | if (IsMouseHovering) 105 | { 106 | Terraria.ModLoader.UI.UICommon.TooltipMouseText(Utilities.GetTileName(tile)); 107 | } 108 | } 109 | } 110 | 111 | class UITileNoSlot : UIElement 112 | { 113 | internal float scale = .75f; 114 | public int order; 115 | public int tile; 116 | Texture2D texture; 117 | 118 | public UITileNoSlot(int tile, int order, float scale = 0.75f) 119 | { 120 | this.scale = scale; 121 | this.order = order; 122 | this.tile = tile; 123 | this.Width.Set(TextureAssets.InventoryBack9.Value.Width * scale, 0f); 124 | this.Height.Set(TextureAssets.InventoryBack9.Value.Height * scale, 0f); 125 | } 126 | 127 | public override int CompareTo(object obj) 128 | { 129 | UITileSlot other = obj as UITileSlot; 130 | return -order.CompareTo(other.order); 131 | } 132 | 133 | public override void Update(GameTime gameTime) 134 | { 135 | if (texture == null) 136 | { 137 | if (!Utilities.tileTextures.ContainsKey(tile)) 138 | { 139 | Utilities.GenerateTileTexture(tile); 140 | } 141 | texture = Utilities.tileTextures[tile]; 142 | } 143 | } 144 | 145 | protected override void DrawSelf(SpriteBatch spriteBatch) 146 | { 147 | if (texture == null) 148 | return; 149 | 150 | CalculatedStyle dimensions = base.GetInnerDimensions(); 151 | Rectangle rectangle = dimensions.ToRectangle(); 152 | 153 | int height = texture.Height; 154 | int width = texture.Width; 155 | float drawScale = 1f; // larger, uncomment below 156 | float availableWidth = (float)TextureAssets.InventoryBack9.Value.Width * scale; 157 | if (width /** drawScale*/ > availableWidth || height /** drawScale*/ > availableWidth) 158 | { 159 | if (width > height) 160 | { 161 | drawScale = availableWidth / width; 162 | } 163 | else 164 | { 165 | drawScale = availableWidth / height; 166 | } 167 | } 168 | drawScale *= scale; 169 | Vector2 vector = TextureAssets.InventoryBack9.Size() * scale; 170 | Vector2 position2 = dimensions.Position() + vector / 2f - texture.Size() * drawScale / 2f; 171 | //Vector2 origin = texture.Size() * (1f / 2f - 0.5f); 172 | spriteBatch.Draw(texture, position2, null, Color.White, 0f, Vector2.Zero, drawScale, SpriteEffects.None, 0f); 173 | 174 | if (IsMouseHovering) 175 | { 176 | Main.hoverItemName = Utilities.GetTileName(tile); 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /UIElements/UITrackIngredientSlot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using Terraria; 6 | using Terraria.Audio; 7 | using Terraria.GameContent; 8 | using Terraria.GameContent.UI.Chat; 9 | using Terraria.ID; 10 | using Terraria.UI; 11 | using Terraria.UI.Chat; 12 | 13 | namespace RecipeBrowser.UIElements 14 | { 15 | internal class UITrackIngredientSlot : UIIngredientSlot 16 | { 17 | private int targetStack; 18 | private int owner; 19 | private Recipe recipe; 20 | 21 | public UITrackIngredientSlot(Item item, int targetStack, Recipe recipe, int order, int owner, float scale = 0.75f) : base(item, order, scale) 22 | { 23 | this.targetStack = targetStack; 24 | this.recipe = recipe; 25 | this.owner = owner; 26 | } 27 | 28 | protected override void DrawSelf(SpriteBatch spriteBatch) 29 | { 30 | base.DrawSelf(spriteBatch); 31 | if (item != null) 32 | { 33 | CalculatedStyle dimensions = base.GetInnerDimensions(); 34 | Rectangle rectangle = dimensions.ToRectangle(); 35 | if (!item.IsAir) 36 | { 37 | if (IsMouseHovering) 38 | if (Main.keyState.IsKeyDown(Main.FavoriteKey)) 39 | if (Main.drawingPlayerChat) 40 | Main.cursorOverride = 2; 41 | else 42 | Main.cursorOverride = 3; 43 | 44 | int count = CountItemGroups(Main.player[owner], recipe, item.type, targetStack > 999 ? targetStack : 999 ); // stopping at item.maxStack means you can't see if you can make multiple. 45 | string progress = count + "/" + targetStack; 46 | Color progressColor = count >= targetStack ? Color.LightGreen : Color.LightSalmon; 47 | ChatManager.DrawColorCodedStringWithShadow(spriteBatch, FontAssets.ItemStack.Value, progress, dimensions.Position() + new Vector2(10f, 26f) * scale + new Vector2(-4f, 0f), progressColor, 0f, Vector2.Zero, new Vector2(scale), -1f, /*scale*/1); 48 | } 49 | } 50 | } 51 | 52 | // Like Player.CountItem, except caps at stopCountingAt exactly and counts items that satisfy recipe as well. 53 | public int CountItemGroups(Player player, Recipe recipe, int type, int stopCountingAt = 1) 54 | { 55 | int count = 0; 56 | if (type == 0) 57 | { 58 | return 0; 59 | } 60 | for (int i = 0; i <= 58; i++) 61 | { 62 | if (!player.inventory[i].IsAir) 63 | { 64 | int current = player.inventory[i].type; 65 | if (recipe.AcceptedByItemGroups(current, item.type)) 66 | { 67 | count += player.inventory[i].stack; 68 | } 69 | else if (current == type) 70 | { 71 | count += player.inventory[i].stack; 72 | } 73 | //if (count >= stopCountingAt) 74 | //{ 75 | // return stopCountingAt; 76 | //} 77 | } 78 | } 79 | return count >= stopCountingAt ? stopCountingAt : count; 80 | } 81 | 82 | public override void LeftClick(UIMouseEvent evt) { 83 | base.LeftClick(evt); 84 | 85 | if (Main.keyState.IsKeyDown(Main.FavoriteKey)) { 86 | if (Main.drawingPlayerChat) { 87 | if (ChatManager.AddChatText(FontAssets.MouseText.Value, ItemTagHandler.GenerateTag(item), Vector2.One)) { 88 | SoundEngine.PlaySound(SoundID.MenuTick); 89 | } 90 | } 91 | else { 92 | bool found = false; 93 | for (int i = 0; i < Recipe.numRecipes; i++) { 94 | Recipe r = Main.recipe[i]; 95 | if (r.createItem.type == itemType) { 96 | RecipeBrowserUI.instance.FavoriteChange(i, true); 97 | found = true; 98 | } 99 | } 100 | if (!found) { 101 | Main.NewText(SharedUI.RBText("FavoritedUI.NoRecipeFoundFor") + ItemTagHandler.GenerateTag(item)); 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /UIElements/checkBox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/checkBox.png -------------------------------------------------------------------------------- /UIElements/checkMark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/checkMark.png -------------------------------------------------------------------------------- /UIElements/closeButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/UIElements/closeButton.png -------------------------------------------------------------------------------- /UISystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | using Microsoft.Xna.Framework.Graphics; 8 | using RecipeBrowser.UIElements; 9 | using Terraria; 10 | using Terraria.ID; 11 | using Terraria.ModLoader; 12 | using Terraria.UI; 13 | 14 | namespace RecipeBrowser 15 | { 16 | public class UISystem : ModSystem 17 | { 18 | public override void UpdateUI(GameTime gameTime) => RecipeBrowser.instance.UpdateUI(gameTime); 19 | 20 | public override void ModifyInterfaceLayers(List layers) => RecipeBrowser.instance.ModifyInterfaceLayers(layers); 21 | 22 | public override void PreSaveAndQuit() => RecipeBrowser.instance.PreSaveAndQuit(); 23 | 24 | public override void PostAddRecipes() 25 | { 26 | if (!Main.dedServ) { 27 | LootCacheManager.Setup(RecipeBrowser.instance); 28 | RecipeBrowserUI.instance.PostSetupContent(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Utilities.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using ReLogic.Content; 6 | using Terraria; 7 | using Terraria.GameContent; 8 | using Terraria.ID; 9 | using Terraria.Map; 10 | using Terraria.ObjectData; 11 | using Terraria.ModLoader; 12 | 13 | namespace RecipeBrowser 14 | { 15 | static class Utilities 16 | { 17 | public static Asset ToAsset(this Texture2D texture) 18 | { 19 | // TODO: Is this inefficient? Is there a way to make an Asset from an actual Texture2D instead of this? 20 | using MemoryStream stream = new(); 21 | 22 | if (!Program.IsMainThread) { 23 | Main.RunOnMainThread(() => { 24 | texture.SaveAsPng(stream, texture.Width, texture.Height); 25 | stream.Position = 0; 26 | }).GetAwaiter().GetResult(); 27 | } 28 | else { 29 | texture.SaveAsPng(stream, texture.Width, texture.Height); 30 | stream.Position = 0; 31 | } 32 | 33 | return RecipeBrowser.instance.Assets.CreateUntracked(stream, texture.Name ?? "NoName.png"); 34 | } 35 | 36 | //internal static Texture2D StackResizeImage(Asset[] texture2D, int desiredWidth, int desiredHeight) 37 | //{ 38 | // foreach (Asset asset in texture2D) 39 | // asset.Wait?.Invoke(); 40 | 41 | // return StackResizeImage(texture2D.Select(asset => asset.Value).ToArray(), desiredWidth, desiredHeight); 42 | //} 43 | 44 | internal static Asset StackResizeImage(Asset[] texture2D, int desiredWidth, int desiredHeight) 45 | { 46 | foreach (Asset asset in texture2D) 47 | asset.Wait?.Invoke(); 48 | 49 | float overlap = .5f; 50 | float totalScale = 1 / (1f + ((1 - overlap) * (texture2D.Length - 1))); 51 | int newWidth = (int)(desiredWidth * totalScale); 52 | int newHeight = (int)(desiredHeight * totalScale); 53 | //var texture2Ds = texture2D.Select(x => ResizeImage(x, newWidth, newHeight)); 54 | 55 | RenderTarget2D renderTarget = new RenderTarget2D(Main.graphics.GraphicsDevice, desiredWidth, desiredHeight); 56 | Main.instance.GraphicsDevice.SetRenderTarget(renderTarget); 57 | Main.instance.GraphicsDevice.Clear(Color.Transparent); 58 | Main.spriteBatch.Begin(); 59 | 60 | int index = 0; 61 | foreach (var texture in texture2D) 62 | { 63 | float scale = 1; 64 | if (texture.Value.Width > newWidth || texture.Value.Height > newHeight) 65 | { 66 | if (texture.Value.Height > texture.Value.Width) 67 | scale = (float)newHeight / texture.Value.Height; 68 | else 69 | scale = (float)newWidth / texture.Value.Width; 70 | } 71 | 72 | Vector2 position = new Vector2(newWidth / 2, newHeight / 2); 73 | position += new Vector2(index * (1 - overlap) * newWidth, index * (1 - overlap) * newHeight); 74 | Main.spriteBatch.Draw(texture.Value, position, null, Color.White, 0f, new Vector2(texture.Value.Width / 2, texture.Value.Height / 2), scale, SpriteEffects.None, 0f); 75 | index++; 76 | } 77 | Main.spriteBatch.End(); 78 | Main.instance.GraphicsDevice.SetRenderTarget(null); 79 | 80 | Texture2D mergedTexture = new Texture2D(Main.instance.GraphicsDevice, desiredWidth, desiredHeight); 81 | Color[] content = new Color[desiredWidth * desiredHeight]; 82 | renderTarget.GetData(content); 83 | mergedTexture.SetData(content); 84 | 85 | // TODO: Is there a way to make an Asset from the Color[] directly? 86 | return mergedTexture.ToAsset(); 87 | } 88 | 89 | //internal static Asset ResizeImage(Asset asset, int desiredWidth, int desiredHeight) 90 | //{ 91 | // asset.Wait?.Invoke(); 92 | // return ResizeImage(asset.Value, desiredWidth, desiredHeight); 93 | //} 94 | 95 | internal static Asset ResizeImage(Asset texture2D, int desiredWidth, int desiredHeight) 96 | { 97 | texture2D.Wait(); 98 | RenderTarget2D renderTarget = new RenderTarget2D(Main.graphics.GraphicsDevice, desiredWidth, desiredHeight); 99 | Main.instance.GraphicsDevice.SetRenderTarget(renderTarget); 100 | Main.instance.GraphicsDevice.Clear(Color.Transparent); 101 | Main.spriteBatch.Begin(); 102 | 103 | float scale = 1; 104 | if (texture2D.Value.Width > desiredWidth || texture2D.Value.Height > desiredHeight) 105 | { 106 | if (texture2D.Value.Height > texture2D.Value.Width) 107 | scale = (float)desiredWidth / texture2D.Value.Height; 108 | else 109 | scale = (float)desiredWidth / texture2D.Value.Width; 110 | } 111 | 112 | //new Vector2(texture2D.Width / 2 * scale, texture2D.Height / 2 * scale) desiredWidth/2, desiredHeight/2 113 | Main.spriteBatch.Draw(texture2D.Value, new Vector2(desiredWidth / 2, desiredHeight / 2), null, Color.White, 0f, new Vector2(texture2D.Value.Width / 2, texture2D.Value.Height / 2), scale, SpriteEffects.None, 0f); 114 | 115 | Main.spriteBatch.End(); 116 | Main.instance.GraphicsDevice.SetRenderTarget(null); 117 | 118 | Texture2D mergedTexture = new Texture2D(Main.instance.GraphicsDevice, desiredWidth, desiredHeight); 119 | Color[] content = new Color[desiredWidth * desiredHeight]; 120 | renderTarget.GetData(content); 121 | mergedTexture.SetData(content); 122 | 123 | return mergedTexture.ToAsset(); 124 | } 125 | 126 | internal static Dictionary tileTextures; 127 | 128 | internal static void GenerateTileTexture(int tile) 129 | { 130 | Texture2D texture; 131 | Main.instance.LoadTiles(tile); 132 | 133 | var tileObjectData = TileObjectData.GetTileData(tile, 0, 0); 134 | if (tileObjectData == null) 135 | { 136 | tileTextures[tile] = TextureAssets.MagicPixel.Value; 137 | return; 138 | } 139 | 140 | int width = tileObjectData.Width; 141 | int height = tileObjectData.Height; 142 | int padding = tileObjectData.CoordinatePadding; 143 | 144 | //Main.spriteBatch.End(); 145 | RenderTarget2D renderTarget = new RenderTarget2D(Main.graphics.GraphicsDevice, width * 16, height * 16); 146 | Main.instance.GraphicsDevice.SetRenderTarget(renderTarget); 147 | Main.instance.GraphicsDevice.Clear(Color.Transparent); 148 | Main.spriteBatch.Begin(); 149 | 150 | for (int i = 0; i < width; i++) 151 | { 152 | for (int j = 0; j < height; j++) 153 | { 154 | Main.spriteBatch.Draw(TextureAssets.Tile[tile].Value, new Vector2(i * 16, j * 16), new Rectangle(i * 16 + i * padding, j * 16 + j * padding, 16, 16), Color.White, 0f, Vector2.Zero, 1, SpriteEffects.None, 0f); 155 | } 156 | } 157 | 158 | Main.spriteBatch.End(); 159 | Main.instance.GraphicsDevice.SetRenderTarget(null); 160 | 161 | texture = new Texture2D(Main.instance.GraphicsDevice, width * 16, height * 16); 162 | Color[] content = new Color[width * 16 * height * 16]; 163 | renderTarget.GetData(content); 164 | texture.SetData(content); 165 | tileTextures[tile] = texture; 166 | } 167 | 168 | internal static string GetTileName(int tile) 169 | { 170 | int requiredTileStyle = Recipe.GetRequiredTileStyle(tile); 171 | string tileName = Lang.GetMapObjectName(MapHelper.TileToLookup(tile, requiredTileStyle)); 172 | if (tileName == "") 173 | { 174 | if (tile < TileID.Count) 175 | tileName = TileID.Search.GetName(tile);// $"Tile {tile}"; 176 | else 177 | tileName = Terraria.ModLoader.TileLoader.GetTile(tile).Name + RecipeBrowserUI.RBText("NoTileEntry"); 178 | } 179 | return tileName; 180 | } 181 | 182 | internal static List PopulateAdjTilesForTile(int Tile) { 183 | List adjTiles = new List(); 184 | adjTiles.Add(Tile); 185 | 186 | ModTile modTile = TileLoader.GetTile(Tile); 187 | if (modTile != null) { 188 | adjTiles.AddRange(modTile.AdjTiles); 189 | } 190 | if (Tile == 302) 191 | adjTiles.Add(17); 192 | if (Tile == 77) 193 | adjTiles.Add(17); 194 | if (Tile == 133) { 195 | adjTiles.Add(17); 196 | adjTiles.Add(77); 197 | } 198 | if (Tile == 134) 199 | adjTiles.Add(16); 200 | if (Tile == 354) 201 | adjTiles.Add(14); 202 | if (Tile == 469) 203 | adjTiles.Add(14); 204 | if (Tile == 487) 205 | adjTiles.Add(14); 206 | if (Tile == 355) { 207 | adjTiles.Add(13); 208 | adjTiles.Add(14); 209 | } 210 | // TODO: GlobalTile.AdjTiles support (no player object, reflection needed since private) 211 | return adjTiles; 212 | } 213 | 214 | internal static Color textColor = Color.White; // new Color(Main.mouseTextColor, Main.mouseTextColor, Main.mouseTextColor); 215 | internal static Color noColor = Color.LightSalmon; // OrangeRed Red 216 | internal static Color yesColor = Color.LightGreen; // Green 217 | internal static Color maybeColor = Color.Yellow; // LightYellow LightGoldenrodYellow Yellow Goldenrod 218 | 219 | internal static void LoadItem(int type) { 220 | // Use this instead of Main.instance.LoadItem because we don't need ImmediateLoad 221 | if (TextureAssets.Item[type].State == AssetState.NotLoaded) 222 | Main.Assets.Request(TextureAssets.Item[type].Name, AssetRequestMode.AsyncLoad); 223 | } 224 | 225 | internal static void LoadNPC(int type) { 226 | // Use this instead of Main.instance.LoadNPC because we don't need ImmediateLoad 227 | if (TextureAssets.Npc[type].State == AssetState.NotLoaded) 228 | Main.Assets.Request(TextureAssets.Npc[type].Name, AssetRequestMode.AsyncLoad); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /build.txt: -------------------------------------------------------------------------------- 1 | author = jopojelly 2 | version = 0.10.9.2 3 | displayName = Recipe Browser 4 | homepage = https://forums.terraria.org/index.php?threads/recipe-browser.62462/ 5 | buildIgnore = .vs\*, Properties\*, *.csproj, *.user, *.config, unused\*, .git\*, Press\*, *.psd, Publicity\* 6 | -------------------------------------------------------------------------------- /description.txt: -------------------------------------------------------------------------------- 1 | Use the "Toggle Recipe Browser" hotkey to show/hide the recipe browser. (Assign it in the keybindings menu) 2 | 3 | Use the "Query Hovered Item" hotkey to quickly bring up the recipe browser with the hovered item. (Assign it in the keybindings menu, I suggest middle click) 4 | 5 | Click on any Recipe to see details. Use categories and search filter to find the recipe you want. 6 | 7 | Alt click on a recipe to favorite a recipe. You can toggle the visibility of the favorited recipes with the "Toggle Favorited Recipes Window" hotkey. 8 | 9 | Place an item in the slot to find related recipes. 10 | 11 | You can view extended crafting options in the Craft menu. Recipes that can be satisfied with multiple crafting steps will be here. 12 | 13 | Also try out the Item Catalogue and Bestiary. 14 | 15 | There is an integrated Help in the Recipe Browser as well. -------------------------------------------------------------------------------- /description_workshop.txt: -------------------------------------------------------------------------------- 1 | [img]https://i.imgur.com/W2c5CX6.gif[/img] 2 | 3 | [h1]Recipe Browser![/h1] 4 | Recipe Browser is an indispensable mod for any player. It provides a service to the user that the Guide could only dream of providing. [img]https://i.imgur.com/420S4WA.gif[/img] With Recipe Browser, you have access to powerful tools to help you plan out what items you want to craft. You'll be able to quickly find items that you can craft with the items in your possession, even if it takes several intermediate crafting steps. 5 | 6 | [h1]How to Use[/h1] 7 | To use, first you need to assign some hotkeys. Make sure to go into the Keybindings menu and assign hotkeys. The "Toggle Recipe Browser" hotkey is necessary, the other 2 are useful but somewhat optional. The "Query Hovered Item" hotkey streamlines checking recipes and the "Toggle Favorited Recipes Window" is useful when using the favorited recipes feature. 8 | [img]https://i.imgur.com/Bv1TOPR.png[/img] 9 | 10 | [h1]Basic Features[/h1] 11 | [img]https://i.imgur.com/wt2VPR0.png[/img] 12 | The main window consists of several tabs. You can click on the tabs to visit each tool. You can resize the window as well. The Help tab explains each tool in greater detail, be sure to check it out. 13 | 14 | [h1]Recipes[/h1] 15 | This tool allows you to quickly explore all recipes in the game. You can place an item in the query slot to find recipes using or resulting in that item. You can also search and filter with the various options in the window to discover recipes you'd like to work towards. Crafting stations and recipe conditions for a selected recipe are listed at the bottom. The color will be red or green showing if the player is near one or not. The right side will show any NPC that drops the selected item. Double Click on an NPC to visit the Bestiary. Double Clicking on a Recipe or Ingredient will populate the query slot with the item, great for traversing crafting trees for items with multiple levels of crafting. Alt-click on a recipe to favorite a recipe, see Favorite section below. 16 | [video mp4=https://github.com/user-attachments/assets/a1e74e40-73e7-4a83-9175-59a35cd4ec9b][/video] 17 | 18 | [h1]Craft[/h1] 19 | This tool can be used to craft recipes, even recipes that require multiple steps to achieve! In the Recipes tool, right click on any recipe to bring up this tool. Recipes with the yellow background can be crafted via multiple steps. For example, if you have gold and iron ore and click on the Gold Watch recipe, this tool will show you the multiple steps needed to craft gold and iron ore, followed by crafting chains, and finally the gold watch itself. You can also enable the Loot option to allow farming as part of the multi-step process. Simply find and kill the specified enemies to farm the needed ingredient. By default, all crafting stations that you have seen will be available for the extended crafting paths. If you enable the Missing Stations option, unseen crafting stations will also be shown. See the Help tab for more information on this tool. 20 | [video mp4=https://github.com/user-attachments/assets/83eb8e02-b9dc-49de-a68e-00e56cb7a83b][/video] 21 | 22 | [h1]Items[/h1] 23 | Here you can explore all items in the game. Use the filters and sorts to find what you want. Double click an item to query it in the Recipe panel. Double right click an item to query the item in the Bestiary. 24 | 25 | [h1]Bestiary[/h1] 26 | Here you can explore all NPC in the game. Use the filters to find what you want. Clicking on an NPC shows the loot from that NPC. Placing an item in the query slot filters the grid to show only NPC that drop that item. Double click an item to query it in the Recipe panel. Right click an item to populate the query slot, great for seeing who else drops the same item. 27 | 28 | [h1]Favorite[/h1] 29 | In the Recipe panel, you can favorite recipes by alt-clicking on the recipe. If you have favorited recipes, the favorites panel will appear on screen. This panel will help you track progress towards collecting all ingredients. In multiplayer, recipes favorited by other players will also appear in the favorites panel. You can track the progress of other players and cooperate efficiently. 30 | [img]https://i.imgur.com/gvG1FpY.png[/img] 31 | [video mp4=https://github.com/user-attachments/assets/116666b8-dcb6-42c1-b410-2dc9e3ba3bf9][/video] 32 | 33 | [h1]Query Hovered Item[/h1] 34 | By assigning a hotkey in the keybindings menu, you can use this feature to quickly bring the item into one of the tools. You can use it anytime you are hovering over any item anywhere in the game. 35 | [video mp4=https://github.com/user-attachments/assets/08e0eb61-be39-4500-a1e4-4cda344c9c90][/video] 36 | 37 | [h1]Categories, Sub Categories, Sorts, and Filters[/h1] 38 | There are powerful sorting and filtering options. You can search for specific weapon types, find the most powerful pickaxe you can craft, and more. See the Help tab for more information. 39 | [img]https://i.imgur.com/cGlveJX.png[/img] 40 | 41 | [h2]Armor Set[/h2] 42 | The Armor Set category lets you view armor sets. Plan out which armor set you want to work towards that fits your playstyle! 43 | [video mp4=https://github.com/user-attachments/assets/f8a52ee3-133c-4220-bbfe-9b722c5a14f3][/video] 44 | 45 | [h1]Help[/h1] 46 | There are many more features in Recipe Browser, see the Help tab to read and learn more about the features of this mod. 47 | 48 | [h1]Links[/h1] 49 | Source code hosted on [url=github.com/JavidPack/RecipeBrowser]GitHub[/url], collaboration welcome 50 | For questions or suggestions, come to my Discord: [url=discord.gg/w8Hcwby][img]https://discordapp.com/api/guilds/276235094622994433/widget.png?style=shield[/img][/url] 51 | Bug reports in the comments here will not be seen, come to Discord or Github to report issues. 52 | If you'd like to support my mods, I have a Patreon [url=www.patreon.com/jopojelly][img]https://i.imgur.com/xII2DwJ.png[/img][/url] 53 | [url=https://steamcommunity.com/profiles/76561198025715866/myworkshopfiles/?appid=1281930]Check out all my mods[/url] -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/icon.png -------------------------------------------------------------------------------- /icon_workshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/icon_workshop.png -------------------------------------------------------------------------------- /unused/ButtonMinus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/ButtonMinus.png -------------------------------------------------------------------------------- /unused/ButtonPlus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/ButtonPlus.png -------------------------------------------------------------------------------- /unused/Inventory_Back2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/Inventory_Back2.png -------------------------------------------------------------------------------- /unused/Reforge_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/Reforge_0.png -------------------------------------------------------------------------------- /unused/Reforge_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/Reforge_1.png -------------------------------------------------------------------------------- /unused/UISlideWindow.cs: -------------------------------------------------------------------------------- 1 | //using RecipeBrowser.Menus; 2 | //using RecipeBrowser.UI; 3 | //using Microsoft.Xna.Framework; 4 | //using System; 5 | //using System.Collections.Generic; 6 | //using System.Linq; 7 | //using System.Text; 8 | //using System.Threading.Tasks; 9 | //using Terraria; 10 | 11 | //namespace RecipeBrowser.CustomUI 12 | //{ 13 | // class UISlideWindow : UIWindow 14 | // { 15 | // internal static float moveSpeed = 10f; 16 | // internal float lerpAmount; 17 | 18 | // internal Vector2 shownPosition = new Vector2(10, 100); 19 | // internal Vector2 hiddenPosition = new Vector2(300, 300); 20 | // internal Vector2 defaultPosition = new Vector2(200, 200); 21 | 22 | // internal bool hidden; 23 | // internal bool arrived; 24 | 25 | // private bool _selected; 26 | // internal bool selected 27 | // { 28 | // get { return _selected; } 29 | // set 30 | // { 31 | // if (value == false) 32 | // { 33 | // if (_selected && arrived && !hidden) 34 | // setShowHidePositions(); 35 | // hidden = true; 36 | // } 37 | // else 38 | // { 39 | // hidden = false; 40 | // Visible = true; 41 | // } 42 | // arrived = false; 43 | // _selected = value; 44 | // } 45 | // } 46 | 47 | // internal void setShowHidePositions() 48 | // { 49 | // shownPosition = Position; 50 | // hiddenPosition = Position; 51 | 52 | // if (Position.X + Width/2 > Main.screenWidth / 2) 53 | // { 54 | // hiddenPosition.X = Main.screenWidth; 55 | // } 56 | // else 57 | // { 58 | // hiddenPosition.X = - Width; 59 | // } 60 | // } 61 | 62 | // public UISlideWindow() 63 | // { 64 | // } 65 | 66 | // protected override bool IsMouseInside() 67 | // { 68 | // if (hidden) return false; 69 | // return base.IsMouseInside(); 70 | // } 71 | 72 | // internal void Hide() 73 | // { 74 | // hidden = true; 75 | // arrived = false; 76 | // setShowHidePositions(); 77 | // } 78 | 79 | // internal void SetDefaultPosition(Vector2 vector2) 80 | // { 81 | // Position = vector2; 82 | // defaultPosition = Position; 83 | // setShowHidePositions(); 84 | // } 85 | 86 | // internal void Show() 87 | // { 88 | // hidden = false; 89 | // arrived = false; 90 | // Visible = true; 91 | 92 | // if(shownPosition.X > Main.screenWidth -25 || shownPosition.Y > Main.screenHeight - 25) 93 | // { 94 | // shownPosition = defaultPosition; 95 | // } 96 | // if (shownPosition.X < -Width+25 || shownPosition.Y < -Height + 25) 97 | // { 98 | // shownPosition = defaultPosition; 99 | // } 100 | // } 101 | 102 | // public override void Update() 103 | // { 104 | // if (!arrived) 105 | // { 106 | // if (this.hidden) 107 | // { 108 | // this.lerpAmount -= .01f * moveSpeed; 109 | // if (this.lerpAmount < 0f) 110 | // { 111 | // this.lerpAmount = 0f; 112 | // arrived = true; 113 | // this.Visible = false; 114 | // } 115 | // base.Position = Vector2.SmoothStep(hiddenPosition, shownPosition, lerpAmount); 116 | // } 117 | // else 118 | // { 119 | // this.lerpAmount += .01f * moveSpeed; 120 | // if (this.lerpAmount > 1f) 121 | // { 122 | // this.lerpAmount = 1f; 123 | // arrived = true; 124 | // } 125 | // base.Position = Vector2.SmoothStep(hiddenPosition, shownPosition, lerpAmount); 126 | // } 127 | // } 128 | 129 | // base.Update(); 130 | // } 131 | 132 | // } 133 | //} -------------------------------------------------------------------------------- /unused/URLHandler.cs: -------------------------------------------------------------------------------- 1 | //using Microsoft.Xna.Framework; 2 | //using Microsoft.Xna.Framework.Graphics; 3 | //using System.Collections.Generic; 4 | //using System.Text; 5 | //using Terraria.UI.Chat; 6 | //using Newtonsoft.Json.Linq; 7 | //using Terraria; 8 | //using ReLogic.Graphics; 9 | //using System.Net; 10 | //using System.IO; 11 | //using System; 12 | 13 | //namespace RecipeBrowser.TagHandlers 14 | //{ 15 | // public class URLTagHandler : ITagHandler 16 | // { 17 | // private class URLSnippet : TextSnippet 18 | // { 19 | // private string imageurl; 20 | // public string sn; 21 | // // index = x*41 + y 22 | 23 | // public URLSnippet(string imageurl) 24 | // : base("") 25 | // { 26 | // this.imageurl = imageurl; 27 | // this.Color = Color.Blue; 28 | // CheckForHover = true; 29 | // } 30 | 31 | // int resolution = 20; 32 | // public override bool UniqueDraw(bool justCheckingString, out Vector2 size, SpriteBatch spriteBatch, Vector2 position = default(Vector2), Color color = default(Color), float scale = 1f) 33 | // { 34 | // size = new Vector2(resolution) * URLTagHandler.GlyphsScale; 35 | // if (!justCheckingString && color != Color.Black) 36 | // { 37 | 38 | // Texture2D texture; 39 | // if (imageCache.TryGetValue(imageurl, out texture)) 40 | // { 41 | // if (texture != null) 42 | // { 43 | // spriteBatch.Draw(texture, position, null, Color.White, 0f, Vector2.Zero, URLTagHandler.GlyphsScale, SpriteEffects.None, 0f); 44 | // //size = texture.Size(); 45 | // } 46 | // else 47 | // { 48 | // Texture2D texture2D = EmojiSupport.instance.GetTexture("sheet_google_" + resolution);// Main.textGlyphTexture[0]; 49 | // spriteBatch.Draw(texture2D, position, new Rectangle?(new Rectangle((0 / 41) * resolution, (0 % 41) * resolution, resolution, resolution)), color, 0f, Vector2.Zero, URLTagHandler.GlyphsScale, SpriteEffects.None, 0f); 50 | // } 51 | // } 52 | // else 53 | // { 54 | // Texture2D texture2D = EmojiSupport.instance.GetTexture("sheet_google_" + resolution);// Main.textGlyphTexture[0]; 55 | // spriteBatch.Draw(texture2D, position, new Rectangle?(new Rectangle((0 / 41) * resolution, (0 % 41) * resolution, resolution, resolution)), color, 0f, Vector2.Zero, URLTagHandler.GlyphsScale, SpriteEffects.None, 0f); 56 | // } 57 | // } 58 | // return true; 59 | // } 60 | 61 | // public override float GetStringLength(DynamicSpriteFont font) 62 | // { 63 | // return resolution * URLTagHandler.GlyphsScale; 64 | // } 65 | 66 | // public override void OnClick() 67 | // { 68 | // ChatManager.AddChatText(FontAssets.MouseText.Value, "[e:" + sn + "]", Vector2.One); 69 | // } 70 | 71 | // public override void OnHover() 72 | // { 73 | // //Main.toolTip = new Item(); 74 | // Main.hoverItemName = $"Emoji code: {sn}"; 75 | // Main.instance.MouseText(Main.hoverItemName, Main.rare, 0); 76 | // } 77 | 78 | // } 79 | 80 | // //private const int GlyphsPerLine = 25; 81 | // //private const int MaxGlyphs = 26; 82 | // public static float GlyphsScale = 1f; 83 | // //private static Dictionary GlyphIndexes = new Dictionary 84 | // //{ 85 | // // { "love", 0 }, 86 | // // { "oh", 1 }, 87 | // // { "tounge", 2 }, 88 | // // { "laugh", 3 }, 89 | // //}; 90 | // internal static Dictionary imageCache; 91 | 92 | 93 | // public static void Initialize() 94 | // { 95 | // if (imageCache == null) 96 | // { 97 | // imageCache = new Dictionary(); 98 | // } 99 | // } 100 | 101 | // TextSnippet ITagHandler.Parse(string text, Color baseColor, string options) 102 | // { 103 | // Initialize(); 104 | 105 | // if (!imageCache.ContainsKey(text)) 106 | // { 107 | // imageCache[text] = null; 108 | // using (WebClient client = new WebClient()) 109 | // { 110 | // client.DownloadDataCompleted += (s, e) => IconDownloadComplete(s, e, text); 111 | // client.DownloadDataAsync(new Uri(text)); 112 | // } 113 | // } 114 | 115 | 116 | // //if (!int.TryParse(text, out num) || num >= 1620) 117 | // //{ 118 | // // return new TextSnippet(text); 119 | // //} 120 | // //if (!URLTagHandler.GlyphIndexes.TryGetValue(text.ToLower(), out num)) 121 | // //{ 122 | // // return new TextSnippet(text); 123 | // //} 124 | // return new URLTagHandler.URLSnippet(text) 125 | // { 126 | // DeleteWhole = true, 127 | 128 | // Text = "[e:" + text.ToLower() + "]", 129 | // sn = text.ToLower() 130 | // }; 131 | // } 132 | 133 | 134 | // private void IconDownloadComplete(object sender, DownloadDataCompletedEventArgs e, string url) 135 | // { 136 | // byte[] data = e.Result; 137 | // using (MemoryStream buffer = new MemoryStream(data)) 138 | // { 139 | // Texture2D imageTexture = Texture2D.FromStream(Main.instance.GraphicsDevice, buffer); 140 | // imageCache[url] = imageTexture; 141 | // } 142 | // } 143 | // } 144 | //} 145 | -------------------------------------------------------------------------------- /unused/checkX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/checkX.png -------------------------------------------------------------------------------- /unused/eyedropper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/eyedropper.png -------------------------------------------------------------------------------- /unused/littleGreenCheckMark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/littleGreenCheckMark.png -------------------------------------------------------------------------------- /unused/tileIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavidPack/RecipeBrowser/6769ae27b33a24ff9d80ec8c4251648c282fc239/unused/tileIcon.png --------------------------------------------------------------------------------