diff --git a/Crawler/CharacterInfoWindow.cpp b/Crawler/CharacterInfoWindow.cpp new file mode 100644 index 00000000..3d7aa3df --- /dev/null +++ b/Crawler/CharacterInfoWindow.cpp @@ -0,0 +1,12 @@ +#pragma once +#include "Crawler.h" +#include "DEFINES.h" +#include "Menu.h" + +INCLUDE_game +typedef Attribute A; + +void Menu::InitializeCharacterInfoWindow(){ + Menu*characterInfoWindow=CreateMenu(INVENTORY,CENTERED,game->GetScreenSize()-vi2d{4,4}); + +} \ No newline at end of file diff --git a/Crawler/Crawler.cpp b/Crawler/Crawler.cpp index 80cec8ae..dd1e06aa 100644 --- a/Crawler/Crawler.cpp +++ b/Crawler/Crawler.cpp @@ -105,6 +105,11 @@ bool Crawler::OnUserCreate(){ camera.EnableWorldBoundary(false); ItemInfo::InitializeItems(); + + InitializeGraphics(); + + Menu::InitializeMenus(); + Inventory::AddItem("Small Health Potion",16); Inventory::AddItem("Large Health Potion",3); Inventory::AddItem("Medium Mana Potion",1); @@ -121,20 +126,9 @@ bool Crawler::OnUserCreate(){ Inventory::AddItem("Dummy Item 11",8); Inventory::AddItem("Dummy Item 12",3); Inventory::AddItem("Dummy Item 13",6); - Inventory::AddItem("Dummy Item 14",192); - Inventory::AddItem("Dummy Item 15",35); - Inventory::AddItem("Dummy Item 16",6); - Inventory::AddItem("Dummy Item 17",3); - Inventory::AddItem("Dummy Item 18",1); - Inventory::AddItem("Dummy Item 19",8); - Inventory::AddItem("Dummy Item 20",4); Inventory::AddItem("Bandages",10); Inventory::AddItem("Blue Slime Remains",22); - InitializeGraphics(); - - Menu::InitializeMenus(); - Monster::InitializeStrategies(); //Animations sig::Animation::InitializeAnimations(); diff --git a/Crawler/Crawler.vcxproj b/Crawler/Crawler.vcxproj index c7e6540e..ce25bf43 100644 --- a/Crawler/Crawler.vcxproj +++ b/Crawler/Crawler.vcxproj @@ -308,6 +308,7 @@ + @@ -378,6 +379,7 @@ + diff --git a/Crawler/Crawler.vcxproj.filters b/Crawler/Crawler.vcxproj.filters index 3cad7224..c61829dc 100644 --- a/Crawler/Crawler.vcxproj.filters +++ b/Crawler/Crawler.vcxproj.filters @@ -55,6 +55,9 @@ {09fc0cbe-06f7-4fdf-944c-9833066bb9c8} + + {bea40439-f3a2-42f8-be1a-c0a815007075} + @@ -326,6 +329,9 @@ Source Files\Interface + + Source Files\Interface + @@ -397,6 +403,9 @@ Resource Files + + Documentation\Menus + diff --git a/Crawler/InventoryWindow.cpp b/Crawler/InventoryWindow.cpp index 2fcac464..71dcca05 100644 --- a/Crawler/InventoryWindow.cpp +++ b/Crawler/InventoryWindow.cpp @@ -1,6 +1,5 @@ #pragma once #include "Crawler.h" -#include "DEFINES.h" #include "olcPixelGameEngine.h" #include "safemap.h" #include "Item.h" @@ -8,39 +7,27 @@ #include "MenuLabel.h" #include "ScrollableWindowComponent.h" -INCLUDE_GFX typedef Attribute A; void Menu::InitializeInventoryWindow(){ - constexpr int invWidth=5; - constexpr int initialInvHeight=3; + int invWidth="ThemeGlobal.InventoryWidth"_I; + int initialInvHeight="ThemeGlobal.InventoryHeight"_I; - constexpr int itemSpacing=8; - constexpr int buttonSize=24; - constexpr int totalSpacing=buttonSize+itemSpacing; + int itemSpacing="ThemeGlobal.InventoryItemSpacing"_I; + int buttonSize="ThemeGlobal.InventoryButtonSize"_I; + int totalSpacing=buttonSize+itemSpacing; - vf2d windowSize={totalSpacing*invWidth-itemSpacing+2+24,totalSpacing*(3+1)-itemSpacing+24}; //Need space for the button. + vf2d windowSize={float(totalSpacing*invWidth-itemSpacing+2+24),float(totalSpacing*(3+1)-itemSpacing+24)}; //Need space for the button. Menu*inventoryWindow=CreateMenu(INVENTORY,CENTERED,windowSize); - ScrollableWindowComponent*inventory=new ScrollableWindowComponent(INVENTORY,{{1,0},{windowSize.x,totalSpacing*3-itemSpacing}},nullptr,[](MenuFuncData data){}); + ScrollableWindowComponent*inventory=new ScrollableWindowComponent(INVENTORY,{{1,0},{windowSize.x,float(totalSpacing*3-itemSpacing)}},nullptr,[](MenuFuncData data){}); inventoryWindow->AddComponent("inventory",inventory); - MenuFunc useItemFunc=[](MenuFuncData data){ - MenuItemButton*button=(MenuItemButton*)data.component; - button->UseItem(); - }; + //We don't have to actually populate the inventory list because now when an item gets added, it will automatically add the correct component in for us. - for(int i=0;iAddComponent(inventoryWindow,"item"+std::to_string(itemIndex),button); - } - - MenuLabel*itemNameLabel=new MenuLabel{INVENTORY,geom2d::rect(vf2d{2,initialInvHeight*totalSpacing-4},windowSize),"",false,true}; + MenuLabel*itemNameLabel=new MenuLabel{INVENTORY,geom2d::rect(vf2d{2,float(initialInvHeight*totalSpacing-4)},windowSize),"",false,true}; inventoryWindow->AddComponent("itemName",itemNameLabel); - MenuLabel*itemDescriptionLabel=new MenuLabel{INVENTORY,geom2d::rect(vf2d{2,initialInvHeight*totalSpacing+itemSpacing},{windowSize.x-4,windowSize.y-108}),"",true,true}; + MenuLabel*itemDescriptionLabel=new MenuLabel{INVENTORY,geom2d::rect(vf2d{2,float(initialInvHeight*totalSpacing+itemSpacing)},{windowSize.x-4,windowSize.y-108}),"",true,true}; inventoryWindow->AddComponent("itemDescription",itemDescriptionLabel); } \ No newline at end of file diff --git a/Crawler/Item.cpp b/Crawler/Item.cpp index 5ca66934..b52ebc38 100644 --- a/Crawler/Item.cpp +++ b/Crawler/Item.cpp @@ -3,6 +3,7 @@ #include "safemap.h" #include "DEFINES.h" #include "Crawler.h" +#include "Menu.h" INCLUDE_game INCLUDE_DATA @@ -159,6 +160,9 @@ void Inventory::RemoveItem(IT it,uint32_t amt){ } sortedList.erase(sortedList.begin()+count); _inventory.erase(it); + ITCategory cat=ITEM_DATA[it].category; + //Callback for GUI inventories. + Menu::InventorySlotsUpdated(cat); }else{ _inventory.at(it).amt-=amt; } @@ -170,6 +174,8 @@ std::vector&Inventory::get(ITCategory itemCategory){ void Inventory::InsertIntoSortedInv(IT item){ sortedInv.at(ITEM_DATA[item].category).push_back(item); + //This should be a callback to menus that we need to update the interface with another item slot since a new one has appeared. + Menu::InventorySlotsUpdated(ITEM_DATA[item].category); } bool Inventory::ExecuteAction(IT item){ diff --git a/Crawler/Menu.cpp b/Crawler/Menu.cpp index 0e590de6..90bb8d33 100644 --- a/Crawler/Menu.cpp +++ b/Crawler/Menu.cpp @@ -2,6 +2,9 @@ #include "MenuComponent.h" #include "DEFINES.h" #include "safemap.h" +#include "Item.h" +#include "MenuItemButton.h" +#include "ScrollableWindowComponent.h" bool Menu::MOUSE_NAVIGATION=true; std::vectorMenu::stack; @@ -32,11 +35,13 @@ void Menu::InitializeMenus(){ std::cout<<"WARNING! Menu Type "<buttons.SetInitialized(); + menus[type]->keyboardButtons.SetInitialized(); for(auto&key:menus[type]->components){ MenuComponent*component=key.second; component->AfterCreate(); } - menus[type]->components.SetInitialized(); //Lock all known components to prevent invalid access. } } @@ -47,9 +52,33 @@ Menu*Menu::CreateMenu(MenuType type,vf2d pos,vf2d size){ void Menu::AddComponent(std::string key,MenuComponent*button){ if(button->selectable){ - buttons[button->rect.pos.y].push_back(button); + buttons.Unlock(); + if(buttons.count(button->rect.pos.y)){ + buttons.at(button->rect.pos.y).push_back(button); + }else{ + buttons[button->rect.pos.y].push_back(button); + } if(button->selectableViaKeyboard){ - keyboardButtons[button->rect.pos.y].push_back(button); + keyboardButtons.Unlock(); + if(keyboardButtons.count(button->rect.pos.y)){ + keyboardButtons.at(button->rect.pos.y).push_back(button); + }else{ + keyboardButtons[button->rect.pos.y].push_back(button); + } + } + + //We must lock the values before calling sort. Sort seems to try and create new accesses. + buttons.SetInitialized(); + keyboardButtons.SetInitialized(); + + //We make an assumption that menu components are supposed to be in left-to-right order. Sometimes we may add things out-of-order, so this fixes the problem by sorting the items afterwards. + std::sort(buttons[button->rect.pos.y].begin(),buttons[button->rect.pos.y].end(),[](MenuComponent*c1,MenuComponent*c2){ + return c1->GetPos().xGetPos().x; + }); + if(keyboardButtons.count(button->rect.pos.y)){ //Keyboard buttons may not necessarily contain this key...Let's be sure. + std::sort(keyboardButtons[button->rect.pos.y].begin(),keyboardButtons[button->rect.pos.y].end(),[](MenuComponent*c1,MenuComponent*c2){ + return c1->GetPos().xGetPos().x; + }); } }else{ displayComponents.push_back(button); @@ -59,7 +88,9 @@ void Menu::AddComponent(std::string key,MenuComponent*button){ throw; } button->name=key; + components.Unlock(); //It's possible we can add a component later on, so we will make sure we remove the lock first. components[key]=button; + components.SetInitialized(); } void Menu::CheckClickAndPerformMenuSelect(Crawler*game){ @@ -84,7 +115,6 @@ void Menu::HoverMenuSelect(Crawler*game){ void Menu::MenuSelect(Crawler*game){ if(selection==vi2d{-1,-1}||buttons[selection.y][selection.x]->disabled)return; - buttons[selection.y][selection.x]->onClick(MenuFuncData{*this,game,buttons[selection.y][selection.x]}); if(buttons[selection.y][selection.x]->menuDest!=MenuType::ENUM_END){ if(stack.size()<32){ stack.push_back(menus[buttons[selection.y][selection.x]->menuDest]);//Navigate to the next menu. @@ -93,6 +123,7 @@ void Menu::MenuSelect(Crawler*game){ throw; } } + buttons[selection.y][selection.x]->onClick(MenuFuncData{*this,game,buttons[selection.y][selection.x]}); } void Menu::Update(Crawler*game){ @@ -315,9 +346,9 @@ void Menu::KeyboardButtonNavigation(Crawler*game,vf2d menuPos){ selectedItem=true; break; } - if(key.first==selection.y&& + if(key.first==selection.y //It's entirely possible this button was selected from the button selection list and may be out-of-bounds here. - selection.x>=0&&selection.x=0&&selection.xGetMousePos(); } MOUSE_NAVIGATION=mouseNavigation; -}; \ No newline at end of file +}; + +void Menu::InventorySlotsUpdated(ITCategory cat){ + //Update the inventory with a new inventory slot, since there's one additional item to interact with now. + std::vector&inv=Inventory::get(cat); + MenuType inventoryWindow; + if(cat=="Consumables"){ + inventoryWindow=INVENTORY; + }else + if(cat=="Equipment"){ + //TODO: Update different inventories depending on what the item's category is. + std::cout<<"WARNING! Unimplemented inventory slot addition for category "<components["inventory"]); + //We only want to refresh the inventory slots if the component count no longer matches what's actually in our inventory. + if(inventory->ComponentCount()UseItem(); + }; + + MenuItemButton*button=new MenuItemButton{inventoryWindow,{{float(totalSpacing*x),float(totalSpacing*y)},{float(buttonSize),float(buttonSize)}},Inventory::get("Consumables"),itemIndex,useItemFunc}; + inventory->AddComponent(menus[inventoryWindow],"item"+std::to_string(itemIndex),button); + }else + if(inventory->ComponentCount()>inv.size()){ //There are empty spots, so let's clean up. + inventory->RemoveEmptySlots(); + } +} \ No newline at end of file diff --git a/Crawler/Menu.h b/Crawler/Menu.h index 87891d44..1625cc76 100644 --- a/Crawler/Menu.h +++ b/Crawler/Menu.h @@ -1,11 +1,10 @@ #pragma once -#include "olcPixelGameEngine.h" +#include "Item.h" #include #include "safemap.h" #include "Theme.h" #include "Attributable.h" #include "olcUTIL_Geometry2D.h" -#include class Crawler; class MenuComponent; @@ -25,8 +24,6 @@ class Menu:IAttributable{ friend class Player; float buttonHoldTime=0; - std::map>buttons; //Buttons are stored in rows followed by their column order. - std::map>keyboardButtons; //Button ordered storage for keyboard/menu std::vectordisplayComponents; //Components that are only for displaying purposes. vi2d selection={-1,-1}; vi2d lastActiveMousePos={}; @@ -50,11 +47,13 @@ public: static std::mapmenus; vf2d pos; //Specify the upper-left corner of the window. Using CENTERED will always put this where the upper-left corner would center the window. vf2d size; //Size in tiles (24x24), every menu will be tile-based + safemap>buttons; //Buttons are stored in rows followed by their column order. + safemap>keyboardButtons; //Button ordered storage for keyboard/menu static Theme&GetCurrentTheme(); bool UsingMouseNavigation(); void SetMouseNavigation(bool mouseNavigation); - + static void InventorySlotsUpdated(ITCategory cat); //Called whenever an inventory item gets added to the player's inventory, thus increasing the total number of slots in our bag. private: Menu(vf2d pos,vf2d size); void HoverMenuSelect(Crawler*game); @@ -65,6 +64,7 @@ private: static void InitializeTestMenu(); static void InitializeTestSubMenu(); static void InitializeInventoryWindow(); + static void InitializeCharacterInfoWindow(); //X (0-3), Y (0-2) for specific 9-patch tile (tiled version). static Renderable&GetPatchPart(int x,int y); diff --git a/Crawler/MenuComponent.cpp b/Crawler/MenuComponent.cpp index 6f11b9b4..7fa8c661 100644 --- a/Crawler/MenuComponent.cpp +++ b/Crawler/MenuComponent.cpp @@ -1,6 +1,8 @@ #include "Crawler.h" #include "MenuComponent.h" +typedef Attribute A; + MenuComponent::MenuComponent(MenuType parent,geom2d::rectrect,std::string label,MenuFunc onClick,bool selectable,bool selectableViaKeyboard) :parentMenu(parent),rect(rect),label(label),menuDest(MenuType::ENUM_END),onClick(onClick),hoverEffect(0),selectable(selectable),selectableViaKeyboard(selectableViaKeyboard){} @@ -78,4 +80,8 @@ bool MenuComponent::PointWithinParent(MenuComponent*child,vi2d drawPos){ bool MenuComponent::HandleOutsideDisabledButtonSelection(MenuComponent*disabledButton){ return false; -}; \ No newline at end of file +}; + +vf2d MenuComponent::GetPos(){ + return rect.pos; +} \ No newline at end of file diff --git a/Crawler/MenuComponent.h b/Crawler/MenuComponent.h index 5f2cefd9..04db8b61 100644 --- a/Crawler/MenuComponent.h +++ b/Crawler/MenuComponent.h @@ -25,6 +25,7 @@ protected: bool selectableViaKeyboard=true; bool disabled=false; //If set to true, this component will not be rendered or updated. bool renderInMain=true; //If set to false, this component is the responsibility of some other windowing system and won't be rendered or updated via the main window loop. + bool valid=true; //If set to false, this would cause the component to be removed. virtual void Update(Crawler*game); virtual void Draw(Crawler*game,vf2d parentPos,bool focused); virtual void DrawDecal(Crawler*game,vf2d parentPos,bool focused); @@ -36,6 +37,7 @@ public: void _Update(Crawler*game); void _Draw(Crawler*game,vf2d parentPos,bool focused); void _DrawDecal(Crawler*game,vf2d parentPos,bool focused); + vf2d GetPos(); //We picked up a draggable component, we should make a copy and return it here. If a nullptr is returned here, the pickup is not allowed. //WARNING!!! This allocates a brand new component when successful!!! Be prepared to clear it! virtual MenuComponent*PickUpDraggableItem(); diff --git a/Crawler/MenuItemButton.h b/Crawler/MenuItemButton.h index 15f1c363..88353b29 100644 --- a/Crawler/MenuItemButton.h +++ b/Crawler/MenuItemButton.h @@ -13,7 +13,6 @@ class MenuItemButton:public MenuIconButton{ private: std::vector&invRef; int inventoryIndex=0; - bool valid=false; public: inline MenuItemButton(MenuType parent,geom2d::rectrect,std::vector&invRef,int invIndex,MenuFunc onClick) :MenuIconButton(parent,rect,invRef.size()>invIndex?ITEM_DATA.at(invRef[invIndex]).Decal():nullptr,onClick),invRef(invRef),inventoryIndex(invIndex){ diff --git a/Crawler/ScrollableWindowComponent.h b/Crawler/ScrollableWindowComponent.h index cbeeff7f..7bf49831 100644 --- a/Crawler/ScrollableWindowComponent.h +++ b/Crawler/ScrollableWindowComponent.h @@ -127,6 +127,31 @@ protected: virtual bool GetHoverState(Crawler*game,MenuComponent*child)override{ return geom2d::overlaps(geom2d::rect{Menu::menus[parentMenu]->pos+rect.pos+child->rect.pos+V(A::SCROLL_OFFSET),child->rect.size},game->GetMousePos()); } + //Calculates the bounds of all components. + geom2d::rect inline CalculateBounds(){ + geom2d::rectbounds; + for(MenuComponent*component:components){ + if(component->rect.pos.xrect.pos.x; + bounds.size.x+=sizeIncrease; + bounds.pos.x=component->rect.pos.x; + } + if(component->rect.right().start.x>bounds.right().start.x){ + float sizeIncrease=component->rect.right().start.x-bounds.right().start.x; + bounds.size.x+=sizeIncrease; + } + if(component->rect.pos.yrect.pos.y; + bounds.size.y+=sizeIncrease; + bounds.pos.y=component->rect.pos.y; + } + if(component->rect.bottom().start.y>bounds.bottom().start.y){ + float sizeIncrease=component->rect.bottom().start.y-bounds.bottom().start.y; + bounds.size.y+=sizeIncrease; + } + } + return bounds; + } public: void inline AddComponent(Menu*parentMenu,std::string key,MenuComponent*button){ components.push_back(button); @@ -163,4 +188,53 @@ public: V(A::SCROLL_OFFSET).y+=rect.size.y/2; return true; }; + virtual inline size_t ComponentCount(){ + return components.size(); + } + virtual void RemoveButton(MenuComponent*button){ + std::vector&buttonList=Menu::menus[button->parentMenu]->buttons.at(button->GetPos().y); + std::vector&keyboardButtonList=Menu::menus[button->parentMenu]->keyboardButtons.at(button->GetPos().y); + size_t removedCount=0; + removedCount+=std::erase(buttonList,button); + removedCount+=std::erase(keyboardButtonList,button); + if(removedCount!=2){ + std::cout<<"WARNING! Attempted to remove buttons from button listing, but not found!"; + throw; + } + if(buttonList.size()==0){ + if(!Menu::menus[button->parentMenu]->buttons.erase(button->GetPos().y)){ + std::cout<<"WARNING! Attempted to erase key "<GetPos().y<<" from button map, but the list still exists!"; + throw; + } + } + if(keyboardButtonList.size()==0){ + if(!Menu::menus[button->parentMenu]->keyboardButtons.erase(button->GetPos().y)){ + std::cout<<"WARNING! Attempted to erase key "<GetPos().y<<" from button map, but the list still exists!"; + throw; + } + } + } + virtual void RemoveEmptySlots(){ + //Algorithm will iterate through all slots, finding blank slots. Each time a blank slot is found, all items will shift over by one, and then the last item will be removed. Repeat until all slots iterated through. + for(int i=0;iUpdate(game); //We have to call update to update the validation state. + //HACK ALERT!! This only gets called on inventories...And only on inventory items, which would have the valid flag set. We only care about components that are inventory slots. + if(!button->valid){ + for(int j=i;jparentMenu]->components.at(components[j]->name)=components[j+1]; + components[j]=components[j+1]; + } + MenuComponent*lastButton=Menu::menus[components[components.size()-1]->parentMenu]->components.at(components[components.size()-1]->name); + //Now we have to fix up the keyboard button list. + RemoveButton(lastButton); + Menu::menus[components[components.size()-1]->parentMenu]->components.erase(components[components.size()-1]->name); + //Now delete the last slot. + components.erase(components.end()-1); + i--; //Subtract one from the index so we don't accidently skip slots. + } + } + bounds=CalculateBounds(); //Recalculate the bounds as it's possible the width/height of the component has changed. + } }; \ No newline at end of file diff --git a/Crawler/State_GameRun.cpp b/Crawler/State_GameRun.cpp index 2fddab9c..9502e6c5 100644 --- a/Crawler/State_GameRun.cpp +++ b/Crawler/State_GameRun.cpp @@ -2,6 +2,7 @@ #include "Crawler.h" #include "DEFINES.h" #include "Menu.h" +#include "Item.h" INCLUDE_MONSTER_LIST @@ -13,6 +14,21 @@ void State_GameRun::OnUserUpdate(Crawler*game){ if(game->encounterStarted&&game->totalBossEncounterMobs>0){ game->encounterDuration+=game->GetElapsedTime(); } + + + if(game->GetKey(O).bPressed){ + Inventory::AddItem("Dummy Item 14",192); + Inventory::AddItem("Dummy Item 15",35); + Inventory::AddItem("Dummy Item 16",6); + Inventory::AddItem("Dummy Item 17",3); + Inventory::AddItem("Dummy Item 18",1); + Inventory::AddItem("Dummy Item 19",8); + Inventory::AddItem("Dummy Item 20",4); + } + if(game->GetKey(P).bPressed){ + Inventory::AddItem("Medium Mana Potion",1); + } + game->HandleUserInput(game->GetElapsedTime()); if(game->GetKey(T).bPressed){ diff --git a/Crawler/Version.h b/Crawler/Version.h index f2a7629d..89f98017 100644 --- a/Crawler/Version.h +++ b/Crawler/Version.h @@ -2,7 +2,7 @@ #define VERSION_MAJOR 0 #define VERSION_MINOR 2 #define VERSION_PATCH 0 -#define VERSION_BUILD 2127 +#define VERSION_BUILD 2167 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Crawler/assets/config/gfx/themes.txt b/Crawler/assets/config/gfx/themes.txt index 31100bf1..9770235c 100644 --- a/Crawler/assets/config/gfx/themes.txt +++ b/Crawler/assets/config/gfx/themes.txt @@ -13,6 +13,15 @@ ThemeGlobal # How far the mouse has to move before mouse mode becomes active from keyboard/controller input mode. MouseActivationDistance = 8 + + # How many slots wide the inventory list is. + InventoryWidth = 5 + # How tall the view is for the inventory window (before scrolling) + InventoryHeight = 3 + # Space between each item slot. + InventoryItemSpacing = 8 + # Size of each button. + InventoryButtonSize = 24 } Themes diff --git a/Crawler/assets/menus/Character_Info.png b/Crawler/assets/menus/Character_Info.png new file mode 100644 index 00000000..20a7a6c7 Binary files /dev/null and b/Crawler/assets/menus/Character_Info.png differ diff --git a/Crawler/safemap.h b/Crawler/safemap.h index 4e99745d..5cab0820 100644 --- a/Crawler/safemap.h +++ b/Crawler/safemap.h @@ -6,7 +6,7 @@ class safemap{ std::mapmap; bool initialized=false; public: - O&operator[](T key){ + O&operator[](T key){ if(initialized&&map.count(key)==0){ std::cout<<"WARNING! Trying to get non-existent key "<