/**************************************************** * This is a bit of a "reverse-quick-stack" in that only items that add to * stacks currently in the player's inventory will be pulled from the chest. * * The code actually works out to be a bit of a combination of the * QuickStack and LootAll methods. * Also based a fair amount on Player.GetItem() * !ref:Player:#4497.00# */ public static void SmartLoot() { if (Main.localPlayer.chest == -1) { return; } var pInventory = Main.localPlayer.inventory; //Item[] var chestItems = Main.localPlayer.chestItems; //Item[] var sendNetMsg = Main.localPlayer.chest > -1; //bool int index; //for each item in inventory (including coins & hotbar)... for (int i = -8; i < 50; i++) //this trick from the vanilla code { index = i < 0 ? 58 + i : i; //do ammo and coins first //...if item is not blank && not a full stack... if (!pInventory[index].IsBlank() && pInventory[index].stack < pInventory[index].maxStack) { //...check every item in chest... int j = -1; // quit if we max out this stack or reach the end of the chest; // also note that the DoCoins() call may reduce this stack to 0, so check that too while (pInventory[index].stack < pInventory[index].maxStack && ++j < Chest.maxItems && pInventory[index].stack > 0) { //...for a matching item stack... if (!chestItems[j].IsBlank() && chestItems[j].IsTheSameAs(pInventory[index])) { Sound.ItemMoved.Play(); //...and merge it to the Player's inventory // I *think* this ItemText.NewText command just makes the text pulse... // I don't entirely grok how it works, but included for parity w/ vanilla ItemText.NewText(chestItems[j], IHUtils.StackMergeD(ref chestItems[j], ref pInventory[index])); Main.localPlayer.DoCoins(index); if (chestItems[j].stack <= 0) { chestItems[j] = new Item(); //reset this item if all stack transferred // if (sendNetMsg) IHUtils.SendNetMessage(j); //only for non-bank chest break; } // if (sendNetMsg) IHUtils.SendNetMessage(j); } } } } //when all is said and done, check for newly available recipes. Recipe.FindRecipes(); }
protected TexturedButton(ButtonSlot <TexturedButton> parent, TIH action, string label, string tooltip = "", Color?bg_color = null, Texture2D texture = null, Rectangle?inactive_rect = null, Rectangle?active_rect = null ) : base(parent, action, label) { Tooltip = tooltip; // this should set ShowTooltip = true automatically if not "" BackgroundColor = bg_color ?? Color.White; Texture = (texture == null) ? IHBase.ButtonGrid : texture; InactiveRect = inactive_rect.HasValue ? inactive_rect : IHUtils.GetSourceRect(action); ActiveRect = active_rect.HasValue ? active_rect : IHUtils.GetSourceRect(action, true); }
/**************************************************** * This will compare the categories of items in the player's * inventory to those of items in the open container and * deposit any items of matching categories. */ public static void SmartDeposit() { if (Main.localPlayer.chest == -1) { return; } var pInventory = Main.localPlayer.inventory; //Item[] var chestItems = Main.localPlayer.chestItems; //Item[] var sendNetMsg = Main.localPlayer.chest > -1; //bool // define a query that creates category groups for the items in the chests, // then pulls out the category keys into a distinct list (List<ItemCat>) var catList = (from item in chestItems where !item.IsBlank() group item by item.GetCategory() into catGroup from cat in catGroup select catGroup.Key).Distinct() //no duplicates .ToList(); //store the query results if (IHBase.ModOptions["LockingEnabled"]) //slot locking on { for (int i = 49; i >= 10; i--) // reverse through player inv { if (!pInventory[i].IsBlank() && !IHPlayer.SlotLocked(i) && catList.Contains(pInventory[i].GetCategory())) { IHUtils.MoveItemToChest(i, sendNetMsg); } } } else //no locking { for (int i = 49; i >= 10; i--) { // if chest contains a matching category if (!pInventory[i].IsBlank() && catList.Contains(pInventory[i].GetCategory())) { IHUtils.MoveItemToChest(i, sendNetMsg); } } } Recipe.FindRecipes(); }
/// <summary> /// Takes an Action and will perform it wrapped in some net update code if we are a client. Otherwise it just does whatever it is. /// </summary> /// <param name="action">An Action (a lambda with no output)</param> public static void DoChestUpdateAction(Action action) { var player = Main.localPlayer; // check net status and make sure a non-bank chest is open // (bank-chests, i.e. piggy-bank & safe, are handled solely client-side) if (Main.netMode == 1 && player.chest > -1) { Item[] oldItems = new Item[player.chestItems.Length]; // make an exact copy of the chest's original contents for (int i = 0; i < oldItems.Length; i++) { oldItems[i] = player.chestItems[i].Clone(); } // perform the requested action action(); // compare each item in the old copy of the original contents // to the chest's new contents and send net-update message // if they do not match. for (int i = 0; i < oldItems.Length; i++) { var oldItem = oldItems[i]; var newItem = player.chestItems[i]; if (oldItem.IsNotTheSameAs(newItem) || oldItem.stack != newItem.stack) { IHUtils.SendNetMessage(i); } } } else // And this is important... { action(); } }
// TODO: Here are my thoughts on making sure shift-click syncs correctly with // the server: obviously, blindly placing the NetMessage update calls // wherever it seemed like Vanilla indicated they should go didn't work. // Now that I have a better understanding of what all that rigamarole is // supposed to do, I think that the most efficient way to handle this // would be to have any function that modifies a slot or slots in a // container record or return a list of the indices of those slots. // Numerous methods in IHUtils already return an int to indicate the // effect of their run, but only under certain conditions does that int // indicate the affected index. Other return statuses (statii?) could // indicate a modified slot or even multiple slots, but since I didn't // think I had any immediate need to know which slots those were, that // information was not recorded. // Now, it wouldn't be too hard to whip up a small data-transfer-object // that can be passed between method calls and hold both the return // status as it is now and a record of any slots which were modified // along the way; this would prevent us from having to take a // sledgehammer approach like we did with the calls that modify // chest-item-slot en-masse. While the sledge may be the best or // most-effective tool when many slots are likely to be modified at // once, this shift-click isn't likely to affect more than a few during // any one run, and looping over the entire container atleast twice for // each click is probably something we should avoid if we can. // Of course this is all obvious and I'm just being verbose for // absolute-clarity's sake. My point--and the catch--is that even // though the DTO would be simple to create and use, the code in IHUtils // is much more of a tangled mass of spaghetti-code than I realized, and // tracing down every spot where we'd need to make changes (though // likely small) to the code to integrate the object will be a delicate // job very prone to breaking things in unexpected ways. At least with // my luch it would be. Anyway, that's why, for now, I'm going to take // the sledgehammer to this and hope it doesn't affect performance too // badly. /// <summary> /// Shift + Left Click on item slot to move it between inventory and chest /// </summary> /// <param name="slot"> </param> /// <param name="release"> </param> /// <returns>True if shift not held while left-clicking, or if no applicable /// recipient is present to move item into. </returns> public override bool PreItemSlotLeftClick(ItemSlot slot, ref bool release) { if (!(bool)IHBase.Instance.options["enableShiftMove"].Value) { return(true); } if (slot.modBase == null && release && KState.Special.Shift.Down()) { if (Main.localPlayer.chestItems != null && !slot.MyItem.IsBlank()) //chests and banks { // Moving inventory item -> chest if (slot.type == "Inventory" || slot.type == "Coin" || slot.type == "Ammo") { IHPlayer.DoChestUpdateAction( () => { if (IHUtils.ShiftToChest(ref slot)) { Recipe.FindRecipes(); // !ref:Main:#22640.36# } }); } // Moving chest item -> inventory else if (slot.type == "Chest") { if (IHUtils.ShiftToPlayer(ref slot, Main.localPlayer.chest > -1)) { Recipe.FindRecipes(); // We can take the easy route here since there's only // one chest slot that could have been affected. if (Main.netMode == 1 && Main.localPlayer.chest > -1) //non-bank { IHUtils.SendNetMessage(slot.index); } } } return(false); } if (Main.craftGuide) //the Guide's crafting info slot { if (Main.guideItem.IsBlank() && !slot.MyItem.IsBlank() && (slot.type == "Inventory" || slot.type == "Coin" || slot.type == "Ammo")) { if (slot.MyItem.material && !slot.MyItem.notMaterial) { Sound.ItemMoved.Play(); Main.guideItem = slot.MyItem.Clone(); slot.MyItem = new Item(); Recipe.FindRecipes(); } } else if (!Main.guideItem.IsBlank() && slot.type == "CraftGuide") { if (IHUtils.ShiftToPlayer(ref slot, false)) { Main.guideItem = new Item(); } Recipe.FindRecipes(); } return(false); } if (Main.reforge) //Item reforging { if (Main.reforgeItem.IsBlank() && slot.type == "Inventory" && !slot.MyItem.IsBlank()) { if (slot.MyItem.maxStack == 1 && Prefix.CanHavePrefix(slot.MyItem)) { Sound.ItemMoved.Play(); Main.reforgeItem = slot.MyItem.Clone(); slot.MyItem = new Item(); Recipe.FindRecipes(); } } else if (!Main.reforgeItem.IsBlank() && slot.type == "Reforge") { if (IHUtils.ShiftToPlayer(ref slot, false)) { Main.reforgeItem = new Item(); } Recipe.FindRecipes(); } return(false); } } return(true); }
private void addIconButtons() { // offset of lock indicator var lockOffset = new Vector2(-(float)(int)((float)Constants.ButtonW / 2) - 4, -(float)(int)((float)Constants.ButtonH / 2) + 8); Func <TIH, string> getLabel = a => Constants.DefaultButtonLabels[a]; Func <TIH, Color> getBGcol = (a) => (a == TIH.SaveName) ? Constants.EquipSlotColor * 0.85f : Constants.ChestSlotColor * 0.85f; Func <TIH, string> getTtip; if (IHBase.ModOptions["ShowTooltips"]) { if (IHBase.ModOptions["ShowKeyBind"]) { getTtip = a => getLabel(a) + IHUtils.GetKeyTip(a); } else { getTtip = a => getLabel(a); } } else { getTtip = a => ""; } Func <TIH, TIH, TexturedButton> getButton = (base_by_action, a) => TexturedButton.New((ButtonSlot <TexturedButton>)ButtonBases[base_by_action], action: a, label: getLabel(a), tooltip: getTtip(a), bg_color: getBGcol(a), texture: IHBase.ButtonGrid, inactive_rect: IHUtils.GetSourceRect(a), active_rect: IHUtils.GetSourceRect(a, true) ); // Btn obj Socket Action Button Action // ------- ------------- ------------- var sort = getButton(TIH.Sort, TIH.Sort); var rsort = getButton(TIH.Sort, TIH.ReverseSort); var loot = getButton(TIH.LootAll, TIH.LootAll); var depo = getButton(TIH.DepositAll, TIH.DepositAll); var sdep = getButton(TIH.DepositAll, TIH.SmartDeposit); var qstk = getButton(TIH.QuickStack, TIH.QuickStack); var sloo = getButton(TIH.QuickStack, TIH.SmartLoot); var rena = getButton(TIH.Rename, TIH.Rename); var save = getButton(TIH.Rename, TIH.SaveName); var cancel = TextButton.New(CancelEditBase, TIH.CancelEdit, getLabel(TIH.CancelEdit)); // Add Services // // sort enables default action for sort/rsort by ... default. sort.AddSortToggle(rsort); //for funsies let's just make that whole toggle thing pointless sort.Hooks.OnRightClick += () => IHPlayer.Sort(true); rsort.Hooks.OnRightClick += () => IHPlayer.Sort(); // add default click, let rClick lock it, and make shift switch buttons depo.EnableDefault().MakeLocking(lockOffset, Color.Firebrick).AddToggle(sdep.EnableDefault()); qstk.EnableDefault().MakeLocking(lockOffset, Color.Firebrick).AddToggle(sloo.EnableDefault()); // these just need their default actions enabled. loot.EnableDefault().Hooks.OnRightClick += () => IHPlayer.CleanStacks(); //why not cancel.EnableDefault(); // this prevents the "Cancel" text from being too big when the player // goes back into the rename interface (though it seems the vanilla // "Cancel" text behaves the same way...improvement!) cancel.Hooks.OnClick += CancelEditBase.ResetScale; // make Rename Chest button change to Save Name button when // clicked, and vice-versa. Well, technically, the buttons will // switch automatically when Main.editChest changes state, but // since that's what clicking these buttons does... save.EnableDefault().AddDynamicToggle(rena.EnableDefault(), () => Main.editChest); }