From be906195248b21da6b691dd7c2263cc805edc3be Mon Sep 17 00:00:00 2001 From: JavidPack Date: Fri, 12 Jan 2018 14:18:25 -0700 Subject: [PATCH] 0.5.1, icon, tile selection, todos --- Images/uniqueTile.png | Bin 0 -> 884 bytes LootCache.cs | 2 +- RecipeBrowser.cs | 7 + RecipeBrowserUI.cs | 10 + RecipeCatalogueUI.cs | 182 ++++++++++++++++--- UIElements/UICycleImage.cs | 74 ++++++++ UIElements/UIRecipeCatalogueQueryItemSlot.cs | 2 + UIElements/UITileSlot.cs | 144 +++++++++++++++ build.txt | 2 +- icon.png | Bin 6982 -> 3551 bytes 10 files changed, 399 insertions(+), 24 deletions(-) create mode 100644 Images/uniqueTile.png create mode 100644 UIElements/UICycleImage.cs create mode 100644 UIElements/UITileSlot.cs diff --git a/Images/uniqueTile.png b/Images/uniqueTile.png new file mode 100644 index 0000000000000000000000000000000000000000..acef8de469a2cadeb0e54559339f4233df109f21 GIT binary patch literal 884 zcmV-)1B?8LP);&NktUwJ2GuPo$eUEhf*rfGia;-6#|)v9U52H| z5)Hako6w)ZTc+DpFO|t*z*Nh!$7XWuVH|? zpN+xmH(!$8%u)%ZH?usr=LHz419F4cX8E4lMqEi(1gN2*j`ieMrk|dt0U(jgAgyI8 z0t2wUog=fE0HD5N6b5Q2=VHO7fnCk>x<=KuA&?OAmAs(a^K0zgk?Q~)?>tg;&itF# zYI|sKfKKy`LLe^B6qL#7YEw7wJ=4wPvnkdjk>#aG z0f>=;OUjACxA`xe+J8=WTtbR4J9#C)WIH!d0NXoG0H`pffJ_R0Onct~@cdRc{wFg$ zo`;#0G3vJhx~Bsrn%dLhCKML(`ienK3Vy8J$LQ~pzli_I3;_F%9{`}g>k^OekLjM4 zE|3uN;dHeDvR@SQD&=do+EpVPY2)K66SU~|jXe^{zMF?qfdJJf%S#c}ygU1hRKNI{ z?BZt09L0F${905K+C zYqclzx2Zz~vbOdi#sY{jfe#<#&n%V}b#sTqMsht)I<=t-qLp@|asL1l+SFlixi0|H z+dwIy-F^^&bZUcSa@7z-tFosKt)CnY8_{Syudf(X*;9uU3t+LxiNR!URL!fD&tz^? zjclZ{r;cQDRee`Hv$M}gWltTYc%+zWspDU&mO6}7wW*_~-{}XF`#-~`O+eBB0000< KMNUMnLSTZI8>PGe literal 0 HcmV?d00001 diff --git a/LootCache.cs b/LootCache.cs index 712a816..067463a 100644 --- a/LootCache.cs +++ b/LootCache.cs @@ -311,7 +311,7 @@ internal static void Setup(Mod recipeBrowserMod) for (int i = 1; i < NPCLoader.NPCCount; i++) // for every npc... { npc.SetDefaults(i); - npc.value = 0; + npc.value = 0; // Causes some drops to be missed, why is this here? string currentMod = npc.modNPC?.mod.Name ?? "Terraria"; if (!modsThatNeedRecalculate.Contains(currentMod)) continue; diff --git a/RecipeBrowser.cs b/RecipeBrowser.cs index a90908b..2462e58 100644 --- a/RecipeBrowser.cs +++ b/RecipeBrowser.cs @@ -7,6 +7,13 @@ namespace RecipeBrowser { + // Magic storage: item checklist support? + // Any iron bar not working for starred + // TODO: Autostar items needed for starred recipes. + // TODO: Save starred recipes. Also, crafting check off starred last time, look into it. + // TODO: Hide Items, items not interested in crafting. Only show if query item is that item (so you can still know how to craft if needed in craft chain.) + // TODO: Star Loot + // TODO: Invesitgate Update placement. (Tiles purple flash) internal class RecipeBrowser : Mod { internal static RecipeBrowser instance; diff --git a/RecipeBrowserUI.cs b/RecipeBrowserUI.cs index e826964..8f6b6d3 100644 --- a/RecipeBrowserUI.cs +++ b/RecipeBrowserUI.cs @@ -200,6 +200,7 @@ public override void OnInitialize() modFilterButton.Top.Set(-0, 0f); modFilterButton.OnClick += ModFilterButton_OnClick; modFilterButton.OnRightClick += ModFilterButton_OnRightClick; + modFilterButton.OnMiddleClick += ModFilterButton_OnMiddleClick; button.Append(modFilterButton); Texture2D texture = RecipeBrowser.instance.GetTexture("UIElements/closeButton"); @@ -254,6 +255,15 @@ private void ModFilterButton_OnRightClick(UIMouseEvent evt, UIElement listeningE AllUpdateNeeded(); } + private void ModFilterButton_OnMiddleClick(UIMouseEvent evt, UIElement listeningElement) + { + UIHoverImageButtonMod button = (evt.Target as UIHoverImageButtonMod); + modIndex = mods.Length - 1; + button.hoverText = "Mod Filter: All"; + UpdateModHoverImage(button); + AllUpdateNeeded(); + } + private void UpdateModHoverImage(UIHoverImageButtonMod button) { button.texture = null; diff --git a/RecipeCatalogueUI.cs b/RecipeCatalogueUI.cs index 6804d9b..ef493e9 100644 --- a/RecipeCatalogueUI.cs +++ b/RecipeCatalogueUI.cs @@ -19,6 +19,25 @@ internal class RecipeCatalogueUI internal UICheckbox TileLookupRadioButton; internal Item queryLootItem; + + private int tile = -1; + internal int Tile + { + get { return tile; } + set + { + if (tile != value) + updateNeeded = true; + tile = value; + foreach (var tileSlot in tileSlots) + { + tileSlot.selected = false; + if (tileSlot.tile == value) + tileSlot.selected = true; + } + } + } + // internal UICheckbox inventoryFilter; internal NewUITextBox itemNameFilter; @@ -26,9 +45,13 @@ internal class RecipeCatalogueUI // internal UIHoverImageButton clearNameFilterButton; internal NewUITextBox itemDescriptionFilter; + internal UIPanel mainPanel; internal UIPanel recipeGridPanel; internal UIGrid recipeGrid; internal UIGrid lootSourceGrid; + internal UIPanel tileChooserPanel; + internal UICycleImage uniqueCheckbox; + internal UIGrid tileChooserGrid; internal UIRecipeInfo recipeInfo; internal UIRadioButton NearbyIngredientsRadioBitton; @@ -38,6 +61,7 @@ internal class RecipeCatalogueUI internal int selectedIndex = -1; internal int newestItem = 0; internal List recipeSlots; + internal List tileSlots; internal bool updateNeeded; @@ -48,7 +72,7 @@ public RecipeCatalogueUI() internal UIElement CreateRecipeCataloguePanel() { - UIPanel mainPanel = new UIPanel(); + mainPanel = new UIPanel(); mainPanel.SetPadding(6); // mainPanel.Left.Set(400f, 0f); // mainPanel.Top.Set(400f, 0f); @@ -68,15 +92,14 @@ internal UIElement CreateRecipeCataloguePanel() queryItem = new UIRecipeCatalogueQueryItemSlot(new Item()); queryItem.Top.Set(2, 0f); queryItem.Left.Set(2, 0f); - queryItem.OnItemChanged += () => { TileLookupRadioButton.SetDisabled(queryItem.item.createTile <= -1); }; + //queryItem.OnItemChanged += () => { Main.NewText("Item changed?"); TileLookupRadioButton.SetDisabled(queryItem.item.createTile <= -1); }; mainPanel.Append(queryItem); TileLookupRadioButton = new UICheckbox("Tile", ""); TileLookupRadioButton.Top.Set(42, 0f); TileLookupRadioButton.Left.Set(0, 0f); TileLookupRadioButton.SetText(" Tile"); - TileLookupRadioButton.OnSelectedChanged += (s, e) => { updateNeeded = true; }; - TileLookupRadioButton.SetDisabled(true); + TileLookupRadioButton.OnSelectedChanged += (s, e) => { ToggleTileChooser(!mainPanel.HasChild(tileChooserPanel)); updateNeeded = true; }; mainPanel.Append(TileLookupRadioButton); RadioButtonGroup = new UIRadioButtonGroup(); @@ -172,13 +195,79 @@ internal UIElement CreateRecipeCataloguePanel() lootSourcePanel.Append(lootSourceScrollbar); lootSourceGrid.SetScrollbar(lootSourceScrollbar); + // Tile Chooser + tileChooserPanel = new UIPanel(); + tileChooserPanel.SetPadding(6); + tileChooserPanel.Top.Pixels = 60; + tileChooserPanel.Width.Set(50, 0f); + tileChooserPanel.Height.Set(-60 - 121, 1f); + tileChooserPanel.BackgroundColor = Color.CornflowerBlue; + + uniqueCheckbox = new UICycleImage(RecipeBrowser.instance.GetTexture("Images/uniqueTile") /* Thanks MiningdiamondsVIII */, 2, new string[] { "Show inherited recipes", "Show unique recipes" }, 36, 20); + uniqueCheckbox.Top.Set(0, 0f); + uniqueCheckbox.Left.Set(1, 0f); + uniqueCheckbox.CurrentState = 1; + uniqueCheckbox.OnStateChanged += (s, e) => { updateNeeded = true; }; + tileChooserPanel.Append(uniqueCheckbox); + + tileChooserGrid = new UIGrid(); + tileChooserGrid.Width.Set(0, 1f); + tileChooserGrid.Height.Set(-24, 1f); + tileChooserGrid.Top.Set(24, 0f); + tileChooserGrid.ListPadding = 2f; + tileChooserPanel.Append(tileChooserGrid); + + var tileChooserScrollbar = new InvisibleFixedUIScrollbar(RecipeBrowserUI.instance.userInterface); + tileChooserScrollbar.SetView(100f, 1000f); + tileChooserScrollbar.Height.Set(0, 1f); + tileChooserScrollbar.Left.Set(-20, 1f); + tileChooserPanel.Append(tileChooserScrollbar); + tileChooserGrid.SetScrollbar(tileChooserScrollbar); + recipeSlots = new List(); + tileSlots = new List(); updateNeeded = true; return mainPanel; } + internal void ToggleTileChooser(bool show = true) + { + if (show) + { + recipeGridPanel.Width.Set(-113, 1f); + recipeGridPanel.Left.Set(53, 0f); + mainPanel.Append(tileChooserPanel); + } + else + { + recipeGridPanel.Width.Set(-60, 1f); + recipeGridPanel.Left.Set(0, 0f); + mainPanel.RemoveChild(tileChooserPanel); + Tile = -1; + } + recipeGridPanel.Recalculate(); + } + + internal void ShowCraftInterface() + { + // make smaller? bigger? + //throw new NotImplementedException(); + Main.NewText("ShowCraftInterface"); + if (Main.rand.NextBool(2)) + { + recipeGridPanel.Width.Set(-120, 1f); + recipeGridPanel.Left.Set(60, 0f); + } + else + { + recipeGridPanel.Width.Set(-60, 1f); + recipeGridPanel.Left.Set(0, 0f); + } + recipeGridPanel.Recalculate(); + } + internal void CloseButtonClicked() { // we should have a way for the button itself to be unclicked and notify parent. @@ -231,13 +320,35 @@ private void UpdateGrid() { recipeSlots.Add(new UIRecipeSlot(i)); } + + tileChooserGrid.Clear(); + var tileUsageCounts = new Dictionary(); + int currentCount; + for (int i = 0; i < Recipe.numRecipes; i++) + { + for (int j = 0; j < 15; j++) + { + if (Main.recipe[i].requiredTile[j] == -1) + break; + tileUsageCounts.TryGetValue(Main.recipe[i].requiredTile[j], out currentCount); + tileUsageCounts[Main.recipe[i].requiredTile[j]] = currentCount + 1; + } + } + // sort + var sorted = tileUsageCounts.OrderBy(kvp => kvp.Value); + foreach (var tileUsage in sorted) + { + var tileSlot = new UITileSlot(tileUsage.Key, tileUsage.Value); + tileChooserGrid.Add(tileSlot); + tileSlots.Add(tileSlot); + } } if (!updateNeeded) { return; } updateNeeded = false; List groups = new List(); - if (queryItem.item.stack > 0 && !TileLookupRadioButton.Selected) + if (queryItem.item.stack > 0) { int type = queryItem.item.type; @@ -358,31 +469,58 @@ private bool PassRecipeFilters(Recipe recipe, List groups) Main.NewText("How is this happening??"); } } - //if (inventoryFilter.Selected) - //{ - //} - if (!queryItem.item.IsAir) + + // Filter out recipes that don't use selected Tile + if (Tile > -1) { - if (TileLookupRadioButton.Selected) + List adjTiles = new List(); + adjTiles.Add(Tile); + if (uniqueCheckbox.CurrentState == 0) { - int type = queryItem.item.createTile; - if (!recipe.requiredTile.Any(ing => ing == type)) + Terraria.ModLoader.ModTile modTile = Terraria.ModLoader.TileLoader.GetTile(Tile); + if (modTile != null) { - return false; + adjTiles.AddRange(modTile.adjTiles); + } + if (Tile == 302) + adjTiles.Add(17); + if (Tile == 77) + adjTiles.Add(17); + if (Tile == 133) + { + adjTiles.Add(17); + adjTiles.Add(77); } + if (Tile == 134) + adjTiles.Add(16); + if (Tile == 354) + adjTiles.Add(14); + if (Tile == 469) + adjTiles.Add(14); + if (Tile == 355) + { + adjTiles.Add(13); + adjTiles.Add(14); + } + // TODO: GlobalTile.AdjTiles support (no player object, reflection needed since private) } - else + if (!recipe.requiredTile.Any(t => adjTiles.Contains(t))) { - int type = queryItem.item.type; - bool inGroup = recipe.acceptedGroups.Intersect(groups).Any(); + return false; + } + } - inGroup |= recipe.useWood(type, type) || recipe.useSand(type, type) || recipe.useFragment(type, type) || recipe.useIronBar(type, type) || recipe.usePressurePlate(type, type); - if (!inGroup) + if (!queryItem.item.IsAir) + { + int type = queryItem.item.type; + bool inGroup = recipe.acceptedGroups.Intersect(groups).Any(); + + inGroup |= recipe.useWood(type, type) || recipe.useSand(type, type) || recipe.useFragment(type, type) || recipe.useIronBar(type, type) || recipe.usePressurePlate(type, type); + if (!inGroup) + { + if (!(recipe.createItem.type == type || recipe.requiredItem.Any(ing => ing.type == type))) { - if (!(recipe.createItem.type == type || recipe.requiredItem.Any(ing => ing.type == type))) - { - return false; ; - } + return false; ; } } } diff --git a/UIElements/UICycleImage.cs b/UIElements/UICycleImage.cs new file mode 100644 index 0000000..e10c290 --- /dev/null +++ b/UIElements/UICycleImage.cs @@ -0,0 +1,74 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using Terraria; +using Terraria.UI; + +namespace RecipeBrowser.UIElements +{ + internal class UICycleImage : UIElement + { + private Texture2D texture; + private int _drawWidth; + private int _drawHeight; + private int padding; + private int textureOffsetX; + private int textureOffsetY; + private int states; + internal string[] hoverTexts; + + public event EventHandler OnStateChanged; + + private int currentState = 0; + public int CurrentState + { + get { return currentState; } + set + { + if (value != currentState) + { + currentState = value; + OnStateChanged?.Invoke(this, EventArgs.Empty); + } + } + } + + public UICycleImage(Texture2D texture, int states, string[] hoverTexts, int width, int height, int textureOffsetX = 0, int textureOffsetY = 0, int padding = 2) + { + this.texture = texture; + this._drawWidth = width; + this._drawHeight = height; + this.textureOffsetX = textureOffsetX; + this.textureOffsetY = textureOffsetY; + this.Width.Set((float)width, 0f); + this.Height.Set((float)height, 0f); + this.states = states; + this.padding = padding; + this.hoverTexts = hoverTexts; + } + + protected override void DrawSelf(SpriteBatch spriteBatch) + { + CalculatedStyle dimensions = base.GetDimensions(); + Point point = new Point(textureOffsetX, textureOffsetY + ((padding + _drawHeight) * currentState)); + Color color = base.IsMouseHovering ? Color.White : Color.Silver; + spriteBatch.Draw(texture, 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); + if (IsMouseHovering) + { + Main.hoverItemName = hoverTexts[CurrentState]; + } + } + + public override void Click(UIMouseEvent evt) + { + CurrentState = (currentState + 1) % states; + base.Click(evt); + } + + public override void RightClick(UIMouseEvent evt) + { + CurrentState = (currentState + states - 1) % states; + base.RightClick(evt); + } + } +} diff --git a/UIElements/UIRecipeCatalogueQueryItemSlot.cs b/UIElements/UIRecipeCatalogueQueryItemSlot.cs index 1485512..bb9ba50 100644 --- a/UIElements/UIRecipeCatalogueQueryItemSlot.cs +++ b/UIElements/UIRecipeCatalogueQueryItemSlot.cs @@ -21,6 +21,8 @@ internal override void ReplaceWithFake(int type) base.ReplaceWithFake(type); RecipeCatalogueUI.instance.queryLootItem = item; RecipeCatalogueUI.instance.updateNeeded = true; + RecipeCatalogueUI.instance.Tile = -1; + RecipeCatalogueUI.instance.TileLookupRadioButton.Selected = false; } } } \ No newline at end of file diff --git a/UIElements/UITileSlot.cs b/UIElements/UITileSlot.cs new file mode 100644 index 0000000..91bc127 --- /dev/null +++ b/UIElements/UITileSlot.cs @@ -0,0 +1,144 @@ +using Microsoft.Xna.Framework.Graphics; +using System.Linq; +using Terraria; +using Terraria.UI; +using Microsoft.Xna.Framework; +using System; +using Terraria.ObjectData; +using Terraria.Map; +using Terraria.ID; + +namespace RecipeBrowser.UIElements +{ + class UITileSlot : UIElement + { + public Texture2D backgroundTexture => Main.inventoryBack9Texture; + public Texture2D selectedTexture => UIRecipeSlot.selectedBackgroundTexture; + internal float scale = .75f; + public int order; // usage count + public int tile; + public bool selected; + Texture2D texture; + + public UITileSlot(int tile, int order, float scale = 0.75f) + { + this.scale = scale; + this.order = order; + this.tile = tile; + this.Width.Set(backgroundTexture.Width * scale, 0f); + this.Height.Set(backgroundTexture.Height * scale, 0f); + } + + public override void Click(UIMouseEvent evt) + { + //RecipeCatalogueUI.instance.ToggleTileChooser(false); + //RecipeCatalogueUI.instance.queryItem.ReplaceWithFake(item.type); + //RecipeCatalogueUI.instance.TileLookupRadioButton.SetDisabled(false); + //RecipeCatalogueUI.instance.TileLookupRadioButton.Selected = true; + if (selected) + RecipeCatalogueUI.instance.Tile = -1; + else + RecipeCatalogueUI.instance.Tile = tile; + } + + public override void DoubleClick(UIMouseEvent evt) + { + //RecipeCatalogueUI.instance.queryItem.ReplaceWithFake(0); + //RecipeCatalogueUI.instance.Tile = tile; + //RecipeCatalogueUI.instance.ToggleTileChooser(false); + } + + public override int CompareTo(object obj) + { + UITileSlot other = obj as UITileSlot; + return -order.CompareTo(other.order); + } + + // TODO, move this to a real update to prevent purple flash + public override void Update(GameTime gameTime) + { + //texture = null; + if (texture == null) + { + Main.instance.LoadTiles(tile); + + var tileObjectData = TileObjectData.GetTileData(tile, 0, 0); + if (tileObjectData == null) + { + texture = Main.magicPixel; + return; + } + + int width = tileObjectData.Width; + int height = tileObjectData.Height; + int padding = tileObjectData.CoordinatePadding; + + Main.spriteBatch.End(); + RenderTarget2D renderTarget = new RenderTarget2D(Main.graphics.GraphicsDevice, width * 16, height * 16); + Main.instance.GraphicsDevice.SetRenderTarget(renderTarget); + Main.instance.GraphicsDevice.Clear(Color.Transparent); + Main.spriteBatch.Begin(); + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + Main.spriteBatch.Draw(Main.tileTexture[tile], 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); + } + } + + Main.spriteBatch.End(); + Main.instance.GraphicsDevice.SetRenderTarget(null); + Main.spriteBatch.Begin(); + texture = renderTarget; + } + } + + protected override void DrawSelf(SpriteBatch spriteBatch) + { + if (texture == null) + return; + + CalculatedStyle dimensions = base.GetInnerDimensions(); + Rectangle rectangle = dimensions.ToRectangle(); + spriteBatch.Draw(backgroundTexture, dimensions.Position(), null, Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); + + if (selected) + spriteBatch.Draw(selectedTexture, dimensions.Position(), null, Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f); + + int height = texture.Height; + int width = texture.Width; + float drawScale = 1f; // larger, uncomment below + float availableWidth = (float)backgroundTexture.Width * scale; + if (width /** drawScale*/ > availableWidth || height /** drawScale*/ > availableWidth) + { + if (width > height) + { + drawScale = availableWidth / width; + } + else + { + drawScale = availableWidth / height; + } + } + drawScale *= scale; + Vector2 vector = backgroundTexture.Size() * scale; + Vector2 position2 = dimensions.Position() + vector / 2f - texture.Size() * drawScale / 2f; + //Vector2 origin = texture.Size() * (1f / 2f - 0.5f); + spriteBatch.Draw(texture, position2, null, Color.White, 0f, Vector2.Zero, drawScale, SpriteEffects.None, 0f); + + if (IsMouseHovering) + { + string tileName = Lang.GetMapObjectName(MapHelper.TileToLookup(tile, 0)); + if (tileName == "") + { + if (tile < TileID.Count) + tileName = $"Tile {tile}"; + else + tileName = Terraria.ModLoader.TileLoader.GetTile(tile).Name + " (err no entry)"; + } + Main.hoverItemName = tileName; + } + } + } +} diff --git a/build.txt b/build.txt index b3d644d..2d6af69 100644 --- a/build.txt +++ b/build.txt @@ -1,5 +1,5 @@ author = jopojelly -version = 0.5 +version = 0.5.1 displayName = Recipe Browser homepage = https://forums.terraria.org/index.php?threads/recipe-browser.62462/ buildIgnore = .vs\*, Properties\*, *.csproj, *.user, obj\*, bin\*, *.config, unused\*, .git\*, diff --git a/icon.png b/icon.png index 5b755538328c40685f4afc44f7baf1cb0adfb643..cda24fba767b16c04d12181e5754563c49e0bc50 100644 GIT binary patch delta 3549 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