Hiya folks and folkettes,
Today was a very busy day, mostly it was bartwe and I dueling bugfixes on stream. And that’s boring. Here’s a short list before I talk about more interesting stuff.
I lied when I said it was all bug fixes. I also added a secondary inventory bag, materials get auto sorted into it. So now you have a total of 92 available slots, + equipment slots. I honestly had completely forgot that I pushed that this morning. Long day…
Ok, here goes.
First, bartwe did a few changes to error handling in Windows, trying to make the automatic stack backtraces that print when the game crashes under windows better. This helps us debug in the future, but isn’t really a bug fix. Next, he fixed an issue with tiny damage numbers being shown on hit. After that, he changed some of the environmental hazard code to allow for non-100% of your health damage values to be used. Then, he fixed a few oversights with the aforementioned feature addition. I had a few logical oversights/missing functionality. Also, he fixed some minor placement issues with enemy skid particles. They were not being generated at the correct offset. Finally, he fixed a few typos/code prettiment/general cleanup.
I started out the day adding that 2nd bag feature. I then moved on to fixing a few bugs dealing with how the BeamAxe was rendering its laser. The offset was wrong. Then, I fixed an oversight in the Gui code that could lead to Panes being registered to the PaneManager without actually ever formally being registered, causing some issues. Later, I fixed some issues with the Crafting system not sourcing from the new bag at all, and after I fixed that, I had to fix another bug where it didn’t actually remove the items from the bag after crafting. Derp!
Have you ever noticed in the streams that when someone uses the beam axe the hand kinda does weird things when turning the player around, it kinda jerks up or down and the slowly moves back down or up? Fixed that. Also, when you moved the cursor too close to the player the hand went all spazzy and flailed around more wildly than a cat thrown into a bathtub? Yeah, fixed that too. Next, I fixed a few edge cases of inventory handling that I’m going to talk about in more detail a little bit later, and fixed another issue where the UI wasn’t loading the “Held slots” correctly when you restarted the game from a player save file.
Then, I got a few bugs from George, who told me that the nav window wasn’t dismissing/coming back correctly. So I took some time to fix that. The pane wasn’t marked as persistent, and also it was erroneously eating keystrokes. Two bugs fixed there. Finally, I took some time to put in a feature that had been bothering me, I added shift clicking to move from the inventory to the actionbar and vica versa. I still need to come up with a good key shortcut to move the selected item into the correct hand. Any ideas?
Long day. Whew.
So… to make things less bland and listy, let’s talk about the Action Bar and Inventory handling at large. It’s recently gone through some very major changes to be far more deliberate. Because it’s so delicate and edge case filled, it’s difficult to get it to do the “right thing.”
So this is my Action Bar. There are many like it, but this one is mine.
One thing that you will notice that has changed is now L and R are real item slots, rather than a reflection of something else on the bar. However, if you’ll remember, we also had two handed items before, how do those work with this system? Well! We simply disable the appropriate slot and reflect that visually by greying out the appropriate item slot!
This solution seemed far more elegant than forcing the opposite handed item completely out of the slot and into a bag that may or may not actually have space for it.
“But!” I hear you cry, “This way seems far more cumbersome. I lose the ability to quickly jump between slots if I need to physically move the item to the L and R slots.”
“Not so fast!” I reply, “You still can quickly jump to other slots on your action bar.”
And it works with two handed items too, of course.
So let’s talk about how all of this works under the hood, shall we?
The ActionBar itself is controlled by /source/frontend/StarActionBar.hpp, this file, along with its associated cpp file, defines an object, which is held by the MainInterface, /source/frontend/StarMainInterface.hpp. The MainInterface controls the entire GUI pretty much. Also relevant to this discussion is the player inventory class, located in /source/game/StarPlayerInventory.hpp, which required extensive modification to make this all possible.
Let’s go from changes at the most basic level up to changes at the highest level. So we’re going to start with the Player Inventory.
This class got two significant changes to make this happen. First, it obvious had to get two extra slots. These exist outside of the rest of the actionbar inventory slots for ease of implementation. They’re called simply “LeftHand” and “RightHand.” Second, it got the concept of a “Held” slot. Prior, we did keep track of which item was selected in the hotbar, but it was only using a size_t offset from the bag, rather than the InventorySlot class designed to designate a unique place anywhere in the inventory. Although it’s currently not used, this change allows any InventorySlot to be marked as “Held,” rather than a potentially invalid offset somewhere in a specific bag.
So! Now we have 4 different items that can be equipped in various ways, along with 3 states possible for 2 of the items and 4 states possible for the other two. The Left and Right “Hand” slots may be either two-handed, one-handed, or empty. And the Left and Right “Held” slots can be two-handed, one-handed, selected but empty, or not selected.
Which leads me to the next question. How do we figure out which item ought to be shown in the left hand, and which item ought to be shown in the right hand? Well, this is actually a very complicated scenario, and we have 144 different possible combinations of two-handed, one-handed, empty, not selected, etc. So the only way to possibly make sense out of this mess is to enumerate what needs to happen in each case. Making this also helps figure out bugs. Because it’s completely clear what the intended behavior is. So here is the current revision of the Alt Hand decision matrix.
// The following is a grid of the possibilities here // Source: // AE = Alt hEld Item, AH = Alt Hand Item // PE = Pri hEld Item, PH = Pri Hand Item // 2 = two handed item // 1 = one handed item // 0 = empty slot (selected) // N = not set (Held items only) // // Result: // X = invalid / don't care // P = primaryHeldSlot(); // p = leftHand(); // A = altHeldSlot(); // a = rightHand(); // 0 = none(); // /////////////////////////////////////////////////////////// // XX |AE | 2 | 2 | 2 | 1 | 1 | 1 | 0 | 0 | 0 | N | N | N | // XX |AH | 2 | 1 | 0 | 2 | 1 | 0 | 2 | 1 | 0 | 2 | 1 | 0 | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // PP | | | | | | | | | | | | | | // EH | | | | | | | | | | | | | | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 22 | | X | X | X | X | X | X | X | X | X | P | P | P | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 21 | | X | X | X | X | X | X | X | X | X | P | P | P | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 20 | | X | X | X | X | X | X | X | X | X | P | P | P | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 12 | | X | X | X | A | A | A | 0 | 0 | 0 | 0 | a | a | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 11 | | X | X | X | A | A | A | 0 | 0 | 0 | 0 | a | a | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 10 | | X | X | X | A | A | A | 0 | 0 | 0 | 0 | a | a | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 02 | | X | X | X | A | A | A | 0 | 0 | 0 | a | a | a | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 01 | | X | X | X | A | A | A | 0 | 0 | 0 | a | a | a | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // 00 | | X | X | X | A | A | A | 0 | 0 | 0 | a | a | a | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // N2 | | A | A | A | A | A | A | p | p | p | p | p | p | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // N1 | | A | A | A | A | A | A | 0 | 0 | 0 | 0 | a | a | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // N0 | | A | A | A | A | A | A | 0 | 0 | 0 | a | a | a | // ---+---+---+---+---+---+---+---+---+---+---+---+---+---+
The legend is included. Hopefully this isn’t too difficult to figure out. Pretty much this is a look up table. Down the side is the primary hand and primary held state, and along the top is the alt hand and alt held state.
This translates into code like so:
auto priHand = underlyingPrimaryHandItem(); auto altHand = underlyingAltHandItem(); // If we have a two handed primary held item, return it // Covers 0,0 to 2,11 submatrix if (safeTwoHanded(primaryHeldItem())) return primaryHeldSlot(); // If we have a alt held item, it takes priority // Covers 0,0 to 11,5 submatrix if (altHeldItem()) return altHeldSlot(); // if we're flagged as holding an empty item in altHeld // and a two-handed item in our priHand, return priHand // Covers 9,6 to 9,8 submatrix if (validInventorySlot(altHeldSlot()) && !altHeldItem() && !validInventorySlot(primaryHeldSlot()) && safeTwoHanded(priHand)) return InventorySlot::leftHand(); // If we have a alt held slot selected, but not item... // Covers 3,6 to 11,8 submatrix if (validInventorySlot(altHeldSlot())) // implicit !altHeldItem() return InventorySlot::none(); // If we have a no selected primary held item and primaryHandItem is two handed, use that // Covers 9,9 to 9,11 submatrix if (!validInventorySlot(primaryHeldSlot()) && safeTwoHanded(priHand)) return InventorySlot::leftHand(); // If we have no item in primaryHeldSlot, but it's selected, and we're two handed, use rightHand // Covers 6,9 to 8,9 submatrix if (validInventorySlot(primaryHeldSlot()) && !primaryHandItem() && safeTwoHanded(altHand)) return InventorySlot::rightHand(); // If we have have a valid one handed primary hand item, and we're two handed, return none // Covers 3,9 to 5,9 submatrix if (validInventorySlot(primaryHeldSlot()) && safeOneHanded(primaryHeldItem()) && safeTwoHanded(altHand)) return InventorySlot::none(); // If we have a one handed primary hand item, and a two handed alt hand item, return none // Covers 10,9 cell if (safeTwoHanded(altHand) && safeOneHanded(priHand)) return InventorySlot::none(); // Covers remainder return InventorySlot::rightHand();
What a mouthful! It’s a bit more difficult to just “do the right thing” especially when the right thing isn’t always clear in edge cases. For instance, in the case where you have a Primary Held Slot selected, but nothing in it, and a two handed item in your Alt Hand Slot. It seems like the correct thing to do would be disable both the Alt hand slot and the Primary hand slot. But I figured that was probably bad design, so instead I made the Alt *Hand* slot overrule the Primary Held Slot, even though this seems to violate clear precedence rules, because it seemed like the “right thing to do.”
Next, let’s talk about the ActionBar area. Now that we have legitimacy and backing within the Inventory for this sort of thing, we have a few extra things to add to the ActionBar. First, we need to actually add interactive item slots to the L and R slots. They should do things when clicked. In this case, they deselect the currently selected item, or fire the item in the slot if it’s enabled.
But what about the other items in the bar? Well, that’s a little bit more complicated. There’s three ways to select an item as a held item. But only ONE of them actually goes through the ActionBar interface. The other two go through MainInterface, because it deals with intercepting keystrokes and mouse events that don’t actually have to go anywhere near the ActionBar.
But for the method that does actually use the code in StarActionBar.cpp, it’s a bit complicated! There’s three different things that the UI needs to keep track of. We have three different overlays:
This counts as one thing. Next, we have the wheel scroll positions of both the L and the R slot. These values are stored in MainInterface, because that’s who references them. Finally, we have which InventorySlot is selected to live on the cursor. This value is also stored in MainInterface.
All three of these values are similar but subtly different, and follow sightly different rules. So they need to be keeps track of separately. So every time I say update to blah blah, there’s logic within that controls all three of those things, but only if it’s appropriate for that particular situation. If you read this far into the post, and you are actually still paying attention, good on you. I like your grit. Reply to the thread with “AltHandSmock.” If you’re the first one, I’ll PM you a giftable pixel key. (Already claimed.) I haven’t come up with a very good way of keeping track of these dependencies yet. But the good news is, I pretty much did this part right the first time, so all’s well. Until maintenance of course.
Thus, when you click on a slot, you have several options. If you shift click, then it will try to send the item to an open container, if such a container exists within the world. If it doesn’t, then it tries to send it to your inventory. If you don’t have shift held, then it checks if the inventory is open. If it’s open it simply picks the item up off of the ActionBar into your “SwapSlot” which is yet another place in the Inventory that I haven’t yet talked about, but contributes to which item is used. Unless you’re right clicking, in which case it tries to apply the item already in your SwapSlot to the item you just clicked on. This is how you repair things like pickaxes. If it’s not open, it sets that item as your PrimaryHeldSlot in your inventory. Unless you right clicked, in which case it sets that item as your AltHeldSlot. Then it checks if the item you just touched is two Handed, if it is, it does the appropriate thing and updates all three pieces of information I mentioned before. If it’s not, it does the appropriate thing for that situation.
When you wheel up and down or press the 1-9+0 keys on your keyboard, the MainInterface takes care of it. The code is similar, but simpler, because there is less possible actions. It still needs to update all three values, depending on what’s relevant.
And I think that’s about it to talk about as far as gritty implementation details go. This particular problem wound up taking me far more time than I expected, unfortunately. If you remember watching my stream last week, this is probably 90% of what you saw. I had a few false starts, which is why it took me so long. But I really shaped up how the PlayerInventory worked, and it wound up meaning that everything eventually worked much better. Which is very nice at the end of the day, but it caused me no end of stress while it was going.
Anyway, I’ll shut up. I hope you enjoy your tome. Here’s a reward picture.