Refactored dynamic cast to do internal error checking. Refactored all inventory and button slot update functions in menu components so they no longer require virtual dispatch when all we require is some passing of lamba functions to update inventories.

pull/28/head
sigonasr2 11 months ago
parent f2e3405b5b
commit bd066ee787
  1. 15
      Crawler/BlacksmithCraftingWindow.cpp
  2. BIN
      Crawler/C++ Header File (OLC-3).zip
  3. 6
      Crawler/CharacterMenuWindow.cpp
  4. 9
      Crawler/Crawler.vcxproj
  5. 9
      Crawler/Crawler.vcxproj.filters
  6. 9
      Crawler/Error.h
  7. 4
      Crawler/InventoryConsumableWindow.cpp
  8. 219
      Crawler/InventoryCreator.cpp
  9. 65
      Crawler/InventoryCreator.h
  10. 71
      Crawler/InventoryScrollableWindowComponent.h
  11. 6
      Crawler/InventoryWindow.cpp
  12. 41
      Crawler/Item.cpp
  13. 7
      Crawler/Item.h
  14. 4
      Crawler/LevelCompleteWindow.cpp
  15. 8
      Crawler/Menu.cpp
  16. 7
      Crawler/Menu.h
  17. 2
      Crawler/MenuComponent.cpp
  18. 4
      Crawler/MenuComponent.h
  19. 22
      Crawler/MerchantWindow.cpp
  20. 47
      Crawler/RowInventoryScrollableWindowComponent.h
  21. 78
      Crawler/RowMerchantInventoryScrollableWindowComponent.h
  22. 2
      Crawler/Version.h

@ -40,6 +40,7 @@ All rights reserved.
#include "Crawler.h" #include "Crawler.h"
#include "MenuItemItemButton.h" #include "MenuItemItemButton.h"
#include "PlayerMoneyLabel.h" #include "PlayerMoneyLabel.h"
#include "RowInventoryScrollableWindowComponent.h"
INCLUDE_game INCLUDE_game
INCLUDE_ITEM_CATEGORIES INCLUDE_ITEM_CATEGORIES
@ -83,6 +84,20 @@ void Menu::InitializeBlacksmithCraftingWindow(){
})END; })END;
armorTab->selectionType=SelectionType::HIGHLIGHT; armorTab->selectionType=SelectionType::HIGHLIGHT;
auto inventoryDisplay=blacksmithWindow->ADD("Weapon Inventory Display",RowInventoryScrollableWindowComponent)({{2,28},{220,blacksmithWindow->size.y-44}},"Item Name Label","Item Description Label",
[](MenuFuncData data){
return true;
},
[](MenuFuncData data){
return true;
},
[](MenuFuncData data){
return true;
},
InventoryCreator::RowPlayerWeapons_InventoryUpdate,
{.padding=1,.size={207,28}}
)END;
#pragma region Inventory Description #pragma region Inventory Description
float inventoryDescriptionWidth=blacksmithWindow->pos.x+blacksmithWindow->size.x-26-224; float inventoryDescriptionWidth=blacksmithWindow->pos.x+blacksmithWindow->size.x-26-224;
blacksmithWindow->ADD("Item Description Outline",MenuLabel)({{224,28},{inventoryDescriptionWidth,blacksmithWindow->size.y-44}},"",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END; blacksmithWindow->ADD("Item Description Outline",MenuLabel)({{224,28},{inventoryDescriptionWidth,blacksmithWindow->size.y-44}},"",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;

@ -136,11 +136,11 @@ void Menu::InitializeCharacterMenuWindow(){
std::weak_ptr<Item>itemInvRef=Inventory::GetItem(it->ActualName()); std::weak_ptr<Item>itemInvRef=Inventory::GetItem(it->ActualName());
auto equip=equipList->ADD("Equip Item "+std::to_string(counter),RowItemDisplay)({{2,2+counter*29.f},{120-15,28}},itemInvRef, auto equip=equipList->ADD("Equip Item "+std::to_string(counter),RowItemDisplay)({{2,2+counter*29.f},{120-15,28}},itemInvRef,
[](MenuFuncData data){ [](MenuFuncData data){
RowItemDisplay*comp=dynamic_cast<RowItemDisplay*>(data.component); RowItemDisplay*comp=DYNAMIC_CAST<RowItemDisplay*>(data.component);
if(comp!=nullptr){ if(comp!=nullptr){
Inventory::EquipItem(comp->GetItem(),EquipSlot(comp->I(Attribute::EQUIP_TYPE))); Inventory::EquipItem(comp->GetItem(),EquipSlot(comp->I(Attribute::EQUIP_TYPE)));
for(MenuComponent*button:((ScrollableWindowComponent*)data.parentComponent)->GetComponents()){ for(MenuComponent*button:((ScrollableWindowComponent*)data.parentComponent)->GetComponents()){
RowItemDisplay*comp=dynamic_cast<RowItemDisplay*>(button); RowItemDisplay*comp=DYNAMIC_CAST<RowItemDisplay*>(button);
if(comp!=nullptr){ if(comp!=nullptr){
comp->SetSelected(false); comp->SetSelected(false);
}else{ }else{
@ -162,7 +162,7 @@ void Menu::InitializeCharacterMenuWindow(){
equip->SetHoverFunc( equip->SetHoverFunc(
[&](MenuFuncData data){ [&](MenuFuncData data){
RowItemDisplay*button=dynamic_cast<RowItemDisplay*>(data.component); RowItemDisplay*button=DYNAMIC_CAST<RowItemDisplay*>(data.component);
if(button!=nullptr){ if(button!=nullptr){
const std::weak_ptr<Item>buttonItem=button->GetItem(); const std::weak_ptr<Item>buttonItem=button->GetItem();
std::vector<float>statsBeforeEquip; std::vector<float>statsBeforeEquip;

@ -343,6 +343,10 @@
</SubType> </SubType>
</ClInclude> </ClInclude>
<ClInclude Include="GameState.h" /> <ClInclude Include="GameState.h" />
<ClInclude Include="InventoryCreator.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="PlayerMoneyLabel.h"> <ClInclude Include="PlayerMoneyLabel.h">
<SubType> <SubType>
</SubType> </SubType>
@ -407,7 +411,6 @@
<SubType> <SubType>
</SubType> </SubType>
</ClInclude> </ClInclude>
<ClInclude Include="RowMerchantInventoryScrollableWindowComponent.h" />
<ClInclude Include="safemap.h" /> <ClInclude Include="safemap.h" />
<ClInclude Include="ScrollableWindowComponent.h" /> <ClInclude Include="ScrollableWindowComponent.h" />
<ClInclude Include="InventoryScrollableWindowComponent.h" /> <ClInclude Include="InventoryScrollableWindowComponent.h" />
@ -488,6 +491,10 @@
<ClCompile Include="FireBolt.cpp" /> <ClCompile Include="FireBolt.cpp" />
<ClCompile Include="GameState.cpp" /> <ClCompile Include="GameState.cpp" />
<ClCompile Include="InventoryConsumableWindow.cpp" /> <ClCompile Include="InventoryConsumableWindow.cpp" />
<ClCompile Include="InventoryCreator.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="InventoryWindow.cpp"> <ClCompile Include="InventoryWindow.cpp">
<SubType> <SubType>
</SubType> </SubType>

@ -375,12 +375,12 @@
<ClInclude Include="PlayerMoneyLabel.h"> <ClInclude Include="PlayerMoneyLabel.h">
<Filter>Header Files\Interface</Filter> <Filter>Header Files\Interface</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="RowMerchantInventoryScrollableWindowComponent.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="CraftingRequirement.h"> <ClInclude Include="CraftingRequirement.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="InventoryCreator.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="Player.cpp"> <ClCompile Include="Player.cpp">
@ -632,6 +632,9 @@
<ClCompile Include="BlacksmithCraftingWindow.cpp"> <ClCompile Include="BlacksmithCraftingWindow.cpp">
<Filter>Source Files\Interface</Filter> <Filter>Source Files\Interface</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="InventoryCreator.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="cpp.hint" /> <None Include="cpp.hint" />

@ -38,6 +38,8 @@ All rights reserved.
#pragma once #pragma once
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <format>
#include <any>
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
#include <source_location> #include <source_location>
#endif #endif
@ -74,3 +76,10 @@ All rights reserved.
#define _CrtDumpMemoryLeaks() ((int)0) #define _CrtDumpMemoryLeaks() ((int)0)
#endif #endif
template<typename type>
type DYNAMIC_CAST(auto variable){
type pointer=dynamic_cast<type>(variable);
if(pointer==nullptr)ERR("Could not dynamic cast to type "<<typeid(variable).name()<<"!");
return pointer;
}

@ -61,7 +61,7 @@ void Menu::InitializeConsumableInventoryWindow(){
data.game->ClearLoadoutItem(data.menu.I(A::LOADOUT_SLOT)); data.game->ClearLoadoutItem(data.menu.I(A::LOADOUT_SLOT));
for(MenuComponent*component:data.parentComponent->GetComponents()){ //HACK ALERT! If we are accessing a parent component, it's because we are dealing with a scrolling window component, which has sub-components. So this should be a safe cast to make. for(MenuComponent*component:data.parentComponent->GetComponents()){ //HACK ALERT! If we are accessing a parent component, it's because we are dealing with a scrolling window component, which has sub-components. So this should be a safe cast to make.
if(component->GetName().starts_with("item")){ if(component->GetName().starts_with("item")){
MenuItemButton*button2=dynamic_cast<MenuItemButton*>(component);//HACK ALERT! This is probably an item since we populated item lists using this name for the components. So we assume these are MenuItemButton classes. MenuItemButton*button2=DYNAMIC_CAST<MenuItemButton*>(component);//HACK ALERT! This is probably an item since we populated item lists using this name for the components. So we assume these are MenuItemButton classes.
if(button2==nullptr)ERR("Could not cast item to a MenuItemButton*!"); if(button2==nullptr)ERR("Could not cast item to a MenuItemButton*!");
if(button2==button){ if(button2==button){
if(button2->selected!=-1){ if(button2->selected!=-1){
@ -77,7 +77,7 @@ void Menu::InitializeConsumableInventoryWindow(){
button->selected=data.menu.I(A::LOADOUT_SLOT); button->selected=data.menu.I(A::LOADOUT_SLOT);
data.game->SetLoadoutItem(button->selected,button->GetItem().lock()->ActualName()); data.game->SetLoadoutItem(button->selected,button->GetItem().lock()->ActualName());
return true; return true;
})END; },InventoryCreator::Player_InventoryUpdate)END;
Menu::AddInventoryListener(consumableWindow,"Consumables"); Menu::AddInventoryListener(consumableWindow,"Consumables");
//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. //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.

@ -0,0 +1,219 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 - 2022 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2023 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "InventoryCreator.h"
#include "RowInventoryScrollableWindowComponent.h"
#define DEFINE(SubName) InventoryCreator InventoryCreator::SubName##_InventoryUpdate(&InventoryCreator::SubName##_InventorySlotsUpdate,&InventoryCreator::SubName##_AddButtonOnSlotUpdate);
DEFINE(Player);
DEFINE(RowPlayer);
DEFINE(RowMerchant);
DEFINE(RowPlayerWeapons);
DEFINE(RowPlayerArmor);
#pragma region Player Inventory Updates
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::Player_InventorySlotsUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
size_t invSize=Inventory::get(cat).size();
//We only want to refresh the inventory slots if the component count no longer matches what's actually in our inventory.
if(component.components.size()<invSize){//We need more space to display our items.
component.AddButtonOnSlotUpdate(cat);
}else
if(component.components.size()>invSize){ //There are empty spots, so let's clean up.
component.RemoveAllComponents();
for(std::weak_ptr<Item> item:Inventory::get(cat)){
component.AddButtonOnSlotUpdate(cat);
}
}
};
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::Player_AddButtonOnSlotUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
size_t invSize=component.components.size()+1;
int invWidth=int(component.rect.size.x/(float(component.options.size.x)+component.options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
int itemIndex=y*invWidth+x;
vf2d buttonSize=component.options.size;
int totalSpacing=component.options.padding+buttonSize.x;
component.ADD("item_"+cat+"_"+std::to_string(itemIndex),MenuItemButton)({{float(totalSpacing*x),float(totalSpacing*y)},buttonSize},Inventory::get(cat),itemIndex,component.inventoryButtonClickAction,component.inventoryButtonHoverAction,component.inventoryButtonMouseOutAction,component.parentMenu,component.itemNameLabelName,component.itemDescriptionLabelName,component.inventoryButtonsActive?IconButtonAttr::SELECTABLE:IconButtonAttr::NOT_SELECTABLE)END
->SetCompactDescriptions(component.compact==COMPACT);
};
#pragma endregion
#pragma region Row Inventory Player Updates
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowPlayer_InventorySlotsUpdate=Player_InventorySlotsUpdate;
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowPlayer_AddButtonOnSlotUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
RowInventoryScrollableWindowComponent*c=DYNAMIC_CAST<RowInventoryScrollableWindowComponent*>(&component);
size_t invSize=c->components.size()+1;
int invWidth=int(c->rect.size.x/(float(c->options.size.x)+c->options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
int itemIndex=y*invWidth+x;
vf2d buttonSize=c->options.size;
vf2d totalSpacing={c->options.padding+buttonSize.x,c->options.padding+buttonSize.y};
auto newItem=c->ADD("item_"+cat+"_"+std::to_string(itemIndex),RowItemDisplay)({totalSpacing*vf2d{float(x),float(y)},buttonSize},Inventory::GetInventorySlot(cat,itemIndex),c->inventoryButtonClickAction,c->itemNameLabelName,c->itemDescriptionLabelName,c->inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
newItem->SetCompactDescriptions(c->compact==COMPACT);
newItem->SetPriceLabelType(c->priceLabel);
newItem->SetHoverFunc(c->inventoryButtonHoverAction);
newItem->SetMouseOutFunc(c->inventoryButtonMouseOutAction);
};
#pragma endregion
#pragma region Row Merchant Updates
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowMerchant_InventorySlotsUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
const std::vector<std::shared_ptr<Item>>&merchantInv=Merchant::GetCurrentTravelingMerchant().GetShopItems();
//We only want to refresh the inventory slots if the component count no longer matches what's actually in our inventory.
if(component.components.size()<merchantInv.size()){//We need more space to display our items.
component.AddButtonOnSlotUpdate(cat);
}else
if(component.components.size()>merchantInv.size()){ //There are empty spots, so let's clean up.
component.RemoveAllComponents();
for(std::shared_ptr<Item> item:merchantInv){
component.AddButtonOnSlotUpdate(cat);
}
}
};
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowMerchant_AddButtonOnSlotUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
const std::vector<std::shared_ptr<Item>>&merchantInv=Merchant::GetCurrentTravelingMerchant().GetShopItems();
RowInventoryScrollableWindowComponent*c=DYNAMIC_CAST<RowInventoryScrollableWindowComponent*>(&component);
size_t invSize=c->components.size()+1;
int invWidth=int(c->rect.size.x/(float(c->options.size.x)+c->options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
int itemIndex=y*invWidth+x;
vf2d buttonSize=c->options.size;
vf2d totalSpacing={c->options.padding+buttonSize.x,c->options.padding+buttonSize.y};
auto newItem=c->ADD("merchant_item_"+cat+"_"+std::to_string(itemIndex),RowItemDisplay)({totalSpacing*vf2d{float(x),float(y)},buttonSize},Merchant::GetCurrentTravelingMerchant().GetShopItems()[itemIndex],c->inventoryButtonClickAction,c->itemNameLabelName,c->itemDescriptionLabelName,c->inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
newItem->SetCompactDescriptions(c->compact==COMPACT);
newItem->SetPriceLabelType(c->priceLabel);
newItem->SetHoverFunc(c->inventoryButtonHoverAction);
newItem->SetMouseOutFunc(c->inventoryButtonMouseOutAction);
};
#pragma endregion
#pragma region Row Player Weapons Updates
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowPlayerWeapons_InventorySlotsUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
std::vector<std::shared_ptr<Item>>weapons;
std::copy_if(Inventory::get("Equipment").begin(),Inventory::get("Equipment").end(),std::back_inserter(weapons),[](std::shared_ptr<Item> item){return item.get()->IsWeapon();});
//We only want to refresh the inventory slots if the component count no longer matches what's actually in our inventory.
if(component.components.size()<weapons.size()){//We need more space to display our items.
component.AddButtonOnSlotUpdate(cat);
}else
if(component.components.size()>weapons.size()){ //There are empty spots, so let's clean up.
component.RemoveAllComponents();
for(std::weak_ptr<Item> item:Inventory::get(cat)){
component.AddButtonOnSlotUpdate(cat);
}
}
};
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowPlayerWeapons_AddButtonOnSlotUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
std::vector<std::shared_ptr<Item>>weapons;
std::copy_if(Inventory::get("Equipment").begin(),Inventory::get("Equipment").end(),std::back_inserter(weapons),[](std::shared_ptr<Item> item){return item.get()->IsWeapon();});
RowInventoryScrollableWindowComponent*c=DYNAMIC_CAST<RowInventoryScrollableWindowComponent*>(&component);
size_t invSize=c->components.size()+1;
int invWidth=int(c->rect.size.x/(float(c->options.size.x)+c->options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
int itemIndex=y*invWidth+x;
vf2d buttonSize=c->options.size;
vf2d totalSpacing={c->options.padding+buttonSize.x,c->options.padding+buttonSize.y};
for(std::shared_ptr<Item> weapon:weapons){
auto newItem=c->ADD("item_"+cat+"_"+std::to_string(itemIndex),RowItemDisplay)({totalSpacing*vf2d{float(x),float(y)},buttonSize},weapon,c->inventoryButtonClickAction,c->itemNameLabelName,c->itemDescriptionLabelName,c->inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
newItem->SetCompactDescriptions(c->compact==COMPACT);
newItem->SetPriceLabelType(c->priceLabel);
newItem->SetHoverFunc(c->inventoryButtonHoverAction);
newItem->SetMouseOutFunc(c->inventoryButtonMouseOutAction);
}
};
#pragma endregion
#pragma region Row Player Armor Updates
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowPlayerArmor_InventorySlotsUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
std::vector<std::shared_ptr<Item>>armor;
std::copy_if(Inventory::get("Equipment").begin(),Inventory::get("Equipment").end(),std::back_inserter(armor),[](std::shared_ptr<Item> item){return item.get()->IsArmor();});
//We only want to refresh the inventory slots if the component count no longer matches what's actually in our inventory.
if(component.components.size()<armor.size()){//We need more space to display our items.
component.AddButtonOnSlotUpdate(cat);
}else
if(component.components.size()>armor.size()){ //There are empty spots, so let's clean up.
component.RemoveAllComponents();
for(std::weak_ptr<Item> item:Inventory::get(cat)){
component.AddButtonOnSlotUpdate(cat);
}
}
};
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> InventoryCreator::RowPlayerArmor_AddButtonOnSlotUpdate=
[](InventoryScrollableWindowComponent&component,ITCategory cat){
std::vector<std::shared_ptr<Item>>armor;
std::copy_if(Inventory::get("Equipment").begin(),Inventory::get("Equipment").end(),std::back_inserter(armor),[](std::shared_ptr<Item> item){return item.get()->IsArmor();});
RowInventoryScrollableWindowComponent*c=DYNAMIC_CAST<RowInventoryScrollableWindowComponent*>(&component);
size_t invSize=c->components.size()+1;
int invWidth=int(c->rect.size.x/(float(c->options.size.x)+c->options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
int itemIndex=y*invWidth+x;
vf2d buttonSize=c->options.size;
vf2d totalSpacing={c->options.padding+buttonSize.x,c->options.padding+buttonSize.y};
for(std::shared_ptr<Item> armor:armor){
auto newItem=c->ADD("item_"+cat+"_"+std::to_string(itemIndex),RowItemDisplay)({totalSpacing*vf2d{float(x),float(y)},buttonSize},armor,c->inventoryButtonClickAction,c->itemNameLabelName,c->itemDescriptionLabelName,c->inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
newItem->SetCompactDescriptions(c->compact==COMPACT);
newItem->SetPriceLabelType(c->priceLabel);
newItem->SetHoverFunc(c->inventoryButtonHoverAction);
newItem->SetMouseOutFunc(c->inventoryButtonMouseOutAction);
}
};
#pragma endregion

@ -0,0 +1,65 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 - 2022 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2023 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "Item.h"
//WARNING! This macro will leave the member scoping at a PRIVATE level upon completion!
#define _SETUP(SubName) \
private: \
static std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> SubName##_InventorySlotsUpdate; \
static std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)> SubName##_AddButtonOnSlotUpdate; \
public: \
static InventoryCreator SubName##_InventoryUpdate; \
private:
class InventoryScrollableWindowComponent;
class InventoryCreator{
_SETUP(Player);
_SETUP(RowPlayer);
_SETUP(RowMerchant);
_SETUP(RowPlayerWeapons);
_SETUP(RowPlayerArmor);
public:
const std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)>*const InventorySlotsUpdate;
const std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)>*const AddButtonOnSlotUpdate;
inline InventoryCreator(
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)>*const InventorySlotsUpdate,
std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)>*const AddButtonOnSlotUpdate)
:InventorySlotsUpdate(InventorySlotsUpdate),AddButtonOnSlotUpdate(AddButtonOnSlotUpdate){};
};

@ -41,6 +41,7 @@ All rights reserved.
#include "MenuItemButton.h" #include "MenuItemButton.h"
#include "Crawler.h" #include "Crawler.h"
#include "ScrollableWindowComponent.h" #include "ScrollableWindowComponent.h"
#include "InventoryCreator.h"
using A=Attribute; using A=Attribute;
@ -56,22 +57,26 @@ struct InventoryWindowOptions{
// //
// Please ensure these are overwritten, otherwise you will get errors complaining about dynamic casts unable to convert classes successfully. // Please ensure these are overwritten, otherwise you will get errors complaining about dynamic casts unable to convert classes successfully.
class InventoryScrollableWindowComponent:public ScrollableWindowComponent{ class InventoryScrollableWindowComponent:public ScrollableWindowComponent{
friend class InventoryCreator;
friend class Menu;
protected: protected:
std::function<bool(MenuFuncData)>inventoryButtonClickAction; std::function<bool(MenuFuncData)>inventoryButtonClickAction;
std::function<bool(MenuFuncData)>inventoryButtonHoverAction; std::function<bool(MenuFuncData)>inventoryButtonHoverAction;
std::function<bool(MenuFuncData)>inventoryButtonMouseOutAction; std::function<bool(MenuFuncData)>inventoryButtonMouseOutAction;
const std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)>*const onInventorySlotsUpdate;
const std::function<void(InventoryScrollableWindowComponent&component,ITCategory cat)>*const addButtonOnSlotUpdate;
CompactText compact=COMPACT; CompactText compact=COMPACT;
InventoryWindowOptions options; InventoryWindowOptions options;
std::string itemNameLabelName; std::string itemNameLabelName;
std::string itemDescriptionLabelName; std::string itemDescriptionLabelName;
bool inventoryButtonsActive=true; bool inventoryButtonsActive=true;
public: public:
inline InventoryScrollableWindowComponent(geom2d::rect<float>rect,std::string itemNameLabelName,std::string itemDescriptionLabelName,std::function<bool(MenuFuncData)>inventoryButtonClickAction,InventoryWindowOptions options={.padding=8,.size={24,24}},bool inventoryButtonsActive=true,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE) inline InventoryScrollableWindowComponent(geom2d::rect<float>rect,std::string itemNameLabelName,std::string itemDescriptionLabelName,std::function<bool(MenuFuncData)>inventoryButtonClickAction,const InventoryCreator&creator,InventoryWindowOptions options={.padding=8,.size={24,24}},bool inventoryButtonsActive=true,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)
:InventoryScrollableWindowComponent(rect,itemNameLabelName,itemDescriptionLabelName,inventoryButtonClickAction,DO_NOTHING,DO_NOTHING,options,inventoryButtonsActive,attributes){ :InventoryScrollableWindowComponent(rect,itemNameLabelName,itemDescriptionLabelName,inventoryButtonClickAction,DO_NOTHING,DO_NOTHING,creator,options,inventoryButtonsActive,attributes){
} }
inline InventoryScrollableWindowComponent(geom2d::rect<float>rect,std::string itemNameLabelName,std::string itemDescriptionLabelName,std::function<bool(MenuFuncData)>inventoryButtonClickAction,std::function<bool(MenuFuncData)>inventoryButtonHoverAction,std::function<bool(MenuFuncData)>inventoryButtonMouseOutAction,InventoryWindowOptions options={.padding=8,.size={24,24}},bool inventoryButtonsActive=true,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE) inline InventoryScrollableWindowComponent(geom2d::rect<float>rect,std::string itemNameLabelName,std::string itemDescriptionLabelName,std::function<bool(MenuFuncData)>inventoryButtonClickAction,std::function<bool(MenuFuncData)>inventoryButtonHoverAction,std::function<bool(MenuFuncData)>inventoryButtonMouseOutAction,const InventoryCreator&creator,InventoryWindowOptions options={.padding=8,.size={24,24}},bool inventoryButtonsActive=true,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)
:ScrollableWindowComponent(rect,attributes),inventoryButtonHoverAction(inventoryButtonHoverAction),inventoryButtonMouseOutAction(inventoryButtonMouseOutAction),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName), :ScrollableWindowComponent(rect,attributes),inventoryButtonHoverAction(inventoryButtonHoverAction),inventoryButtonMouseOutAction(inventoryButtonMouseOutAction),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName),
options(options),inventoryButtonClickAction(inventoryButtonClickAction),inventoryButtonsActive(inventoryButtonsActive){} options(options),inventoryButtonClickAction(inventoryButtonClickAction),inventoryButtonsActive(inventoryButtonsActive),addButtonOnSlotUpdate(creator.AddButtonOnSlotUpdate),onInventorySlotsUpdate(creator.InventorySlotsUpdate){}
virtual inline void Update(Crawler*game)override{ virtual inline void Update(Crawler*game)override{
ScrollableWindowComponent::Update(game); ScrollableWindowComponent::Update(game);
bool noneHovered=true; bool noneHovered=true;
@ -90,65 +95,21 @@ public:
if(compact)this->compact=COMPACT; if(compact)this->compact=COMPACT;
else this->compact=NON_COMPACT; else this->compact=NON_COMPACT;
for(MenuComponent*component:components){ for(MenuComponent*component:components){
MenuItemButton*itemButton=dynamic_cast<MenuItemButton*>(component); MenuItemButton*itemButton=DYNAMIC_CAST<MenuItemButton*>(component);
if(itemButton){
itemButton->SetCompactDescriptions(compact); itemButton->SetCompactDescriptions(compact);
}else{
ERR("WARNING! Attempting to cast a button that isn't a MenuItemButton!");
}
} }
} }
protected: protected:
inline 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;i<components.size();i++){
MenuComponent*button=components[i];
button->Update(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;j<components.size()-1;j++){
//Take the item in the next slot and move it to this slot.
Menu::menus[components[j]->parentMenu]->components.at(components[j]->name)=components[size_t(j+1)];
std::swap(components[j]->inventoryIndex,components[size_t(j+1)]->inventoryIndex);
std::swap(components[j],components[size_t(j+1)]);
}
MenuComponent*lastButton=components[components.size()-1];
//Now we have to fix up the keyboard button list.
RemoveButton(lastButton);
i--; //Subtract one from the index so we don't accidently skip slots.
}
}
CalculateBounds(); //Recalculate the bounds as it's possible the width/height of the component has changed.
}
virtual inline void OnInventorySlotsUpdate(ITCategory cat)override{
size_t invSize=Inventory::get(cat).size();
//We only want to refresh the inventory slots if the component count no longer matches what's actually in our inventory.
if(components.size()<invSize){//We need more space to display our items.
AddButtonOnSlotUpdate(cat);
}else
if(components.size()>invSize){ //There are empty spots, so let's clean up.
RemoveAllComponents();
for(std::weak_ptr<Item> item:Inventory::get(cat)){
AddButtonOnSlotUpdate(cat);
}
}
}
virtual inline void Cleanup()override final{ virtual inline void Cleanup()override final{
} }
//Called whenever an inventory slot gets updated, whether it's adding or removing an item.
inline void OnInventorySlotsUpdate(ITCategory cat){
(*onInventorySlotsUpdate)(*this,cat);
};
virtual inline void AddButtonOnSlotUpdate(ITCategory cat){ inline void AddButtonOnSlotUpdate(ITCategory cat){
size_t invSize=components.size()+1; (*addButtonOnSlotUpdate)(*this,cat);
int invWidth=int(rect.size.x/(float(options.size.x)+options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
int itemIndex=y*invWidth+x;
vf2d buttonSize=options.size;
int totalSpacing=options.padding+buttonSize.x;
ADD("item_"+cat+"_"+std::to_string(itemIndex),MenuItemButton)({{float(totalSpacing*x),float(totalSpacing*y)},buttonSize},Inventory::get(cat),itemIndex,inventoryButtonClickAction,inventoryButtonHoverAction,inventoryButtonMouseOutAction,parentMenu,itemNameLabelName,itemDescriptionLabelName,inventoryButtonsActive?IconButtonAttr::SELECTABLE:IconButtonAttr::NOT_SELECTABLE)END
->SetCompactDescriptions(compact==COMPACT);
} }
}; };

@ -90,7 +90,7 @@ void Menu::InitializeInventoryWindow(){
auto inventoryDisplay=inventoryWindow->ADD("Inventory Display - "+category,RowInventoryScrollableWindowComponent)({{72,28},{150,inventoryWindow->size.y-44}},"Item Name Label","Item Description Label",DO_NOTHING, auto inventoryDisplay=inventoryWindow->ADD("Inventory Display - "+category,RowInventoryScrollableWindowComponent)({{72,28},{150,inventoryWindow->size.y-44}},"Item Name Label","Item Description Label",DO_NOTHING,
[](MenuFuncData data){ [](MenuFuncData data){
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(dynamic_cast<RowItemDisplay*>(data.component)->GetItem()); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(DYNAMIC_CAST<RowItemDisplay*>(data.component)->GetItem());
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon(); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon();
return true; return true;
}, },
@ -98,7 +98,9 @@ void Menu::InitializeInventoryWindow(){
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(Item::BLANK); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(Item::BLANK);
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon(); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon();
return true; return true;
},{.padding=1,.size={137,28}})END; },
InventoryCreator::RowPlayer_InventoryUpdate,
{.padding=1,.size={137,28}})END;
if(first){ if(first){
inventoryWindow->S(A::LAST_INVENTORY_TYPE_OPENED)=category; inventoryWindow->S(A::LAST_INVENTORY_TYPE_OPENED)=category;

@ -203,6 +203,21 @@ void ItemInfo::InitializeItems(){
props.customProps=&data[key]; props.customProps=&data[key];
} }
it.useFunc=scriptName; it.useFunc=scriptName;
#pragma region Equipment Category Verification Tests
int equipmentCategories=0;
equipmentCategories+=it.IsWeapon();
equipmentCategories+=it.IsArmor();
equipmentCategories+=it.IsAccessory();
if(it.IsEquippable()&&equipmentCategories==0)ERR(std::format("WARNING! {} is not considered a weapon, armor, or accessory but is considered equippable!",it.Name()))
if(it.IsEquippable()&&equipmentCategories!=1)ERR(std::format("WARNING! {} is considered in {} categories among the [weapon, armor, or accessory] set but is considered equippable!",it.Name(),equipmentCategories))
if(it.IsEquippable()&&it.Category()!="Equipment"&&it.Category()!="Accessories")ERR(std::format("WARNING! {} is considered equippable, but is not in either the Equipment category or the Accessories category!",it.Name()))
if(it.Category()=="Equipment"&&!(it.IsWeapon()||it.IsArmor()))ERR(std::format("WARNING! {} is in the Equipment Category, but is not considered a Weapon or Armor!",it.Name()))
if(it.Category()=="Accessories"&&it.IsWeapon())ERR(std::format("WARNING! {} is in the Accessories Category, but is considered a Weapon!",it.Name()))
if(it.Category()=="Accessories"&&it.IsArmor())ERR(std::format("WARNING! {} is in the Accessories Category, but is considered Armor!",it.Name()))
if(it.Category()=="Accessories"&&!it.IsAccessory())ERR(std::format("WARNING! {} is in the Accessories Category, but not considered an Accessory!",it.Name()))
#pragma endregion
} }
}; };
@ -471,8 +486,11 @@ const std::string Item::DisplayName()const{
} }
return name; return name;
} }
const bool ItemInfo::IsEquippable()const{
return slot!=EquipSlot::NONE&&(category=="Equipment"||category=="Accessories");
}
const bool Item::IsEquippable()const{ const bool Item::IsEquippable()const{
return Category()=="Equipment"||Category()=="Accessories"; return it->IsEquippable();
} }
const std::string Item::Description(CompactText compact)const{ const std::string Item::Description(CompactText compact)const{
std::string description=it->Description(); std::string description=it->Description();
@ -883,3 +901,24 @@ const EnhancementInfo&Item::GetEnhancementInfo()const{
const bool EnhancementInfo::CanBeEnhanced()const{ const bool EnhancementInfo::CanBeEnhanced()const{
return enhancementStats.size()=="Item.Item Max Enhancement Level"_I+1&&craftingRequirements.size()=="Item.Item Max Enhancement Level"_I+1; return enhancementStats.size()=="Item.Item Max Enhancement Level"_I+1&&craftingRequirements.size()=="Item.Item Max Enhancement Level"_I+1;
}; };
const bool ItemInfo::IsWeapon()const{
return slot==EquipSlot::WEAPON;
}
const bool ItemInfo::IsArmor()const{
return IsEquippable()&&!IsWeapon()&&!IsAccessory();
}
const bool ItemInfo::IsAccessory()const{
return slot==EquipSlot::RING1||slot==EquipSlot::RING2;
}
const bool Item::IsWeapon()const{
return it->IsWeapon();
}
const bool Item::IsArmor()const{
return it->IsArmor();
}
const bool Item::IsAccessory()const{
return it->IsAccessory();
}

@ -185,6 +185,9 @@ public:
static std::shared_ptr<Item>BLANK; static std::shared_ptr<Item>BLANK;
const std::optional<const::ItemSet*const>ItemSet()const; const std::optional<const::ItemSet*const>ItemSet()const;
const bool IsEquippable()const; const bool IsEquippable()const;
const bool IsWeapon()const;
const bool IsArmor()const;
const bool IsAccessory()const;
const uint32_t BuyValue()const; const uint32_t BuyValue()const;
const uint32_t SellValue()const; const uint32_t SellValue()const;
const bool CanBeSold()const; const bool CanBeSold()const;
@ -299,6 +302,10 @@ public:
const bool CanBePurchased()const; const bool CanBePurchased()const;
const bool UseDuringCast()const; const bool UseDuringCast()const;
const EnhancementInfo&GetEnhancementInfo()const; const EnhancementInfo&GetEnhancementInfo()const;
const bool IsEquippable()const;
const bool IsWeapon()const;
const bool IsArmor()const;
const bool IsAccessory()const;
}; };
class ItemOverlay{ class ItemOverlay{

@ -55,12 +55,12 @@ void Menu::InitializeLevelCompleteWindow(){
levelCompleteWindow->ADD("Monster Loot Outline",MenuComponent)({{0,32},{windowSize.size.x-80.f,72}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END; levelCompleteWindow->ADD("Monster Loot Outline",MenuComponent)({{0,32},{windowSize.size.x-80.f,72}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END;
levelCompleteWindow->ADD("Monster Loot Label",MenuLabel)({{0,32},{windowSize.size.x-80.f,12}},"Monster Loot",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE)END; levelCompleteWindow->ADD("Monster Loot Label",MenuLabel)({{0,32},{windowSize.size.x-80.f,12}},"Monster Loot",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE)END;
auto monsterLootWindow=levelCompleteWindow->ADD("Monster Loot Window",InventoryScrollableWindowComponent)({{0,44},{windowSize.size.x-80.f,60}},"Monster Loot Popup Item Name","Monster Loot Popup Item Description",DO_NOTHING)END; auto monsterLootWindow=levelCompleteWindow->ADD("Monster Loot Window",InventoryScrollableWindowComponent)({{0,44},{windowSize.size.x-80.f,60}},"Monster Loot Popup Item Name","Monster Loot Popup Item Description",DO_NOTHING,InventoryCreator::Player_InventoryUpdate)END;
Menu::AddInventoryListener(monsterLootWindow,"Monster Loot"); Menu::AddInventoryListener(monsterLootWindow,"Monster Loot");
levelCompleteWindow->ADD("Stage Loot Outline",MenuComponent)({{0,108},{windowSize.size.x-80.f,72}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END; levelCompleteWindow->ADD("Stage Loot Outline",MenuComponent)({{0,108},{windowSize.size.x-80.f,72}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END;
levelCompleteWindow->ADD("Stage Loot Label",MenuLabel)({{0,108},{windowSize.size.x-80.f,12}},"Stage Loot",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE)END; levelCompleteWindow->ADD("Stage Loot Label",MenuLabel)({{0,108},{windowSize.size.x-80.f,12}},"Stage Loot",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::OUTLINE)END;
auto stageLootWindow=levelCompleteWindow->ADD("Stage Loot Window",InventoryScrollableWindowComponent)({{0,120},{windowSize.size.x-80.f,60}},"Stage Loot Popup Item Name","Stage Loot Popup Item Description",DO_NOTHING)END; auto stageLootWindow=levelCompleteWindow->ADD("Stage Loot Window",InventoryScrollableWindowComponent)({{0,120},{windowSize.size.x-80.f,60}},"Stage Loot Popup Item Name","Stage Loot Popup Item Description",DO_NOTHING,InventoryCreator::Player_InventoryUpdate)END;
Menu::AddInventoryListener(stageLootWindow,"Stage Loot"); Menu::AddInventoryListener(stageLootWindow,"Stage Loot");
auto nextButtonAction=[](MenuFuncData data){ auto nextButtonAction=[](MenuFuncData data){

@ -318,7 +318,7 @@ void Menu::Draw(Crawler*game){
MenuComponent*selectedComponent=buttons[selection.y][selection.x]; MenuComponent*selectedComponent=buttons[selection.y][selection.x];
vf2d drawOffset{}; vf2d drawOffset{};
if(selectedComponent->parentComponent!=nullptr){ if(selectedComponent->parentComponent!=nullptr){
ScrollableWindowComponent*scrollableComponent=dynamic_cast<ScrollableWindowComponent*>(selectedComponent->parentComponent); ScrollableWindowComponent*scrollableComponent=DYNAMIC_CAST<ScrollableWindowComponent*>(selectedComponent->parentComponent);
if(scrollableComponent!=nullptr){ if(scrollableComponent!=nullptr){
drawOffset+=scrollableComponent->GetScrollAmount(); drawOffset+=scrollableComponent->GetScrollAmount();
} }
@ -582,14 +582,16 @@ void Menu::SetMouseNavigation(bool mouseNavigation){
void Menu::InventorySlotsUpdated(ITCategory cat){ void Menu::InventorySlotsUpdated(ITCategory cat){
//Update the inventory with a new inventory slot, since there's one additional item to interact with now. //Update the inventory with a new inventory slot, since there's one additional item to interact with now.
for(MenuComponent*component:inventoryListeners.at(cat)){ for(MenuComponent*component:inventoryListeners.at(cat)){
component->OnInventorySlotsUpdate(cat); InventoryScrollableWindowComponent*comp=DYNAMIC_CAST<InventoryScrollableWindowComponent*>(component); //HACK ALERT! We're assuming that these must be these classes otherwise they have no reason to even be using these listeners. Make sure that the lowest base class that requires these implements these functions!!!
comp->OnInventorySlotsUpdate(cat);
} }
} }
void Menu::MerchantInventorySlotsUpdated(ITCategory cat){ void Menu::MerchantInventorySlotsUpdated(ITCategory cat){
//Update the inventory with a new inventory slot, since there's one additional item to interact with now. //Update the inventory with a new inventory slot, since there's one additional item to interact with now.
for(MenuComponent*component:merchantInventoryListeners.at(cat)){ for(MenuComponent*component:merchantInventoryListeners.at(cat)){
component->OnInventorySlotsUpdate(cat); InventoryScrollableWindowComponent*comp=DYNAMIC_CAST<InventoryScrollableWindowComponent*>(component); //HACK ALERT! We're assuming that these must be these classes otherwise they have no reason to even be using these listeners. Make sure that the lowest base class that requires these implements these functions!!!
comp->OnInventorySlotsUpdate(cat);
} }
} }

@ -244,13 +244,8 @@ private:
template<typename T> template<typename T>
T*Component(MenuType menu,std::string componentName){ T*Component(MenuType menu,std::string componentName){
T*tmp=dynamic_cast<T*>(Menu::menus[menu]->components[componentName]); T*tmp=DYNAMIC_CAST<T*>(Menu::menus[menu]->components[componentName]);
if(tmp!=nullptr){
return tmp; return tmp;
}else{
ERR("WARNING! Attempting to cast a button that isn't a "<<typeid(T).name()<<"!");
}
return nullptr;
} }
struct MenuFuncData{ struct MenuFuncData{

@ -191,8 +191,6 @@ vf2d MenuComponent::GetPos(){
return rect.pos; return rect.pos;
} }
void MenuComponent::OnInventorySlotsUpdate(ITCategory cat){}
std::string MenuComponent::GetLabel(){ std::string MenuComponent::GetLabel(){
return label; return label;
} }

@ -66,6 +66,8 @@ using SelectionType::CROSSHAIR;
using SelectionType::HIGHLIGHT; using SelectionType::HIGHLIGHT;
using SelectionType::INNER_BOX; using SelectionType::INNER_BOX;
class InventoryScrollableWindowComponent;
class MenuComponent:public IAttributable{ class MenuComponent:public IAttributable{
friend class Menu; friend class Menu;
friend class MenuItemButton; friend class MenuItemButton;
@ -136,8 +138,6 @@ public:
virtual bool DropDraggableItem(MenuComponent*draggable); virtual bool DropDraggableItem(MenuComponent*draggable);
//A notification that a button outside the region has been selected. Return false if it's not allowed. //A notification that a button outside the region has been selected. Return false if it's not allowed.
virtual bool HandleOutsideDisabledButtonSelection(MenuComponent*disabledButton); virtual bool HandleOutsideDisabledButtonSelection(MenuComponent*disabledButton);
//Called whenever an inventory slot gets updated, whether it's adding or removing an item.
virtual void OnInventorySlotsUpdate(ITCategory cat);
//Called whenever equipment and base stats are updated, notifying a component that numbers that may be displayed have changed. //Called whenever equipment and base stats are updated, notifying a component that numbers that may be displayed have changed.
virtual void OnEquipStatsUpdate(); virtual void OnEquipStatsUpdate();
std::string GetLabel(); std::string GetLabel();

@ -38,7 +38,7 @@ All rights reserved.
#include "Menu.h" #include "Menu.h"
#include "Crawler.h" #include "Crawler.h"
#include "RowMerchantInventoryScrollableWindowComponent.h" #include "RowInventoryScrollableWindowComponent.h"
#include "MenuItemItemButton.h" #include "MenuItemItemButton.h"
#include "MenuComponent.h" #include "MenuComponent.h"
#include "PlayerMoneyLabel.h" #include "PlayerMoneyLabel.h"
@ -61,7 +61,7 @@ void Menu::InitializeMerchantWindow(){
std::sort(categories.begin(),categories.end(),[](std::pair<std::string,int>&cat1,std::pair<std::string,int>&cat2){return cat1.second<cat2.second;}); std::sort(categories.begin(),categories.end(),[](std::pair<std::string,int>&cat1,std::pair<std::string,int>&cat2){return cat1.second<cat2.second;});
auto buyTab=merchantWindow->ADD("Buy Tab",MenuComponent)({{2,0},{merchantWindow->size.x/2-4,24}},"Buy",[](MenuFuncData data){ auto buyTab=merchantWindow->ADD("Buy Tab",MenuComponent)({{2,0},{merchantWindow->size.x/2-4,24}},"Buy",[](MenuFuncData data){
Component<RowMerchantInventoryScrollableWindowComponent>(MERCHANT,"Merchant Inventory Display")->Enable(true); Component<RowInventoryScrollableWindowComponent>(MERCHANT,"Merchant Inventory Display")->Enable(true);
Component<MenuComponent>(MERCHANT,"Sell Tab")->selected=false; Component<MenuComponent>(MERCHANT,"Sell Tab")->selected=false;
Component<MenuComponent>(MERCHANT,"Inventory Tabs Outline")->Enable(false); Component<MenuComponent>(MERCHANT,"Inventory Tabs Outline")->Enable(false);
for(auto&[category,items]:ITEM_CATEGORIES){ for(auto&[category,items]:ITEM_CATEGORIES){
@ -91,9 +91,9 @@ void Menu::InitializeMerchantWindow(){
})END; })END;
sellTab->selectionType=SelectionType::HIGHLIGHT; sellTab->selectionType=SelectionType::HIGHLIGHT;
auto inventoryDisplay=merchantWindow->ADD("Merchant Inventory Display",RowMerchantInventoryScrollableWindowComponent)({{2,28},{220,merchantWindow->size.y-44}},"Item Name Label","Item Description Label", auto inventoryDisplay=merchantWindow->ADD("Merchant Inventory Display",RowInventoryScrollableWindowComponent)({{2,28},{220,merchantWindow->size.y-44}},"Item Name Label","Item Description Label",
[](MenuFuncData data){ [](MenuFuncData data){
RowItemDisplay*item=dynamic_cast<RowItemDisplay*>(data.component); RowItemDisplay*item=DYNAMIC_CAST<RowItemDisplay*>(data.component);
Component<MenuLabel>(BUY_ITEM,"Item Purchase Header")->S(A::ITEM_NAME)=item->GetItem().lock()->ActualName(); Component<MenuLabel>(BUY_ITEM,"Item Purchase Header")->S(A::ITEM_NAME)=item->GetItem().lock()->ActualName();
Component<MenuLabel>(BUY_ITEM,"Price per item Amount Label")->SetLabel(std::to_string(item->GetItem().lock()->BuyValue())); Component<MenuLabel>(BUY_ITEM,"Price per item Amount Label")->SetLabel(std::to_string(item->GetItem().lock()->BuyValue()));
Component<MenuLabel>(BUY_ITEM,"Amount to buy Amount Label")->SetLabel("1"); Component<MenuLabel>(BUY_ITEM,"Amount to buy Amount Label")->SetLabel("1");
@ -110,7 +110,7 @@ void Menu::InitializeMerchantWindow(){
return true; return true;
}, },
[](MenuFuncData data){ [](MenuFuncData data){
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(dynamic_cast<RowItemDisplay*>(data.component)->GetItem()); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(DYNAMIC_CAST<RowItemDisplay*>(data.component)->GetItem());
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon(); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon();
return true; return true;
}, },
@ -118,7 +118,9 @@ void Menu::InitializeMerchantWindow(){
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(Item::BLANK); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(Item::BLANK);
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon(); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon();
return true; return true;
},{.padding=1,.size={220-13,28}})END; },
InventoryCreator::RowMerchant_InventoryUpdate,
{.padding=1,.size={220-13,28}})END;
inventoryDisplay->SetPriceLabelType(PriceLabel::BUY_LABEL); inventoryDisplay->SetPriceLabelType(PriceLabel::BUY_LABEL);
for(auto&[category,items]:ITEM_CATEGORIES){ for(auto&[category,items]:ITEM_CATEGORIES){
@ -151,7 +153,7 @@ void Menu::InitializeMerchantWindow(){
auto inventoryDisplay=merchantWindow->ADD("Inventory Display - "+category,RowInventoryScrollableWindowComponent)({{72,28},{150,merchantWindow->size.y-44}},"Item Name Label","Item Description Label", auto inventoryDisplay=merchantWindow->ADD("Inventory Display - "+category,RowInventoryScrollableWindowComponent)({{72,28},{150,merchantWindow->size.y-44}},"Item Name Label","Item Description Label",
[](MenuFuncData data){ [](MenuFuncData data){
RowItemDisplay*item=dynamic_cast<RowItemDisplay*>(data.component); RowItemDisplay*item=DYNAMIC_CAST<RowItemDisplay*>(data.component);
if(item->GetItem().lock()->CanBeSold()){ if(item->GetItem().lock()->CanBeSold()){
Component<MenuLabel>(SELL_ITEM,"Item Sell Header")->S(A::ITEM_NAME)=item->GetItem().lock()->ActualName(); Component<MenuLabel>(SELL_ITEM,"Item Sell Header")->S(A::ITEM_NAME)=item->GetItem().lock()->ActualName();
Component<MenuLabel>(SELL_ITEM,"Price per item Amount Label")->SetLabel(std::to_string(item->GetItem().lock()->SellValue())); Component<MenuLabel>(SELL_ITEM,"Price per item Amount Label")->SetLabel(std::to_string(item->GetItem().lock()->SellValue()));
@ -170,7 +172,7 @@ void Menu::InitializeMerchantWindow(){
return true; return true;
}, },
[](MenuFuncData data){ [](MenuFuncData data){
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(dynamic_cast<RowItemDisplay*>(data.component)->GetItem()); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(DYNAMIC_CAST<RowItemDisplay*>(data.component)->GetItem());
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon(); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon();
return true; return true;
}, },
@ -178,7 +180,9 @@ void Menu::InitializeMerchantWindow(){
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(Item::BLANK); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(Item::BLANK);
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon(); Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon();
return true; return true;
},{.padding=1,.size={137,28}})END; },
InventoryCreator::RowPlayer_InventoryUpdate,
{.padding=1,.size={137,28}})END;
inventoryDisplay->SetPriceLabelType(PriceLabel::SELL_LABEL); inventoryDisplay->SetPriceLabelType(PriceLabel::SELL_LABEL);
if(first){ if(first){

@ -38,65 +38,30 @@ All rights reserved.
#include "InventoryScrollableWindowComponent.h" #include "InventoryScrollableWindowComponent.h"
#include "RowItemDisplay.h" #include "RowItemDisplay.h"
#include "InventoryCreator.h"
class RowInventoryScrollableWindowComponent:public InventoryScrollableWindowComponent{ class RowInventoryScrollableWindowComponent:public InventoryScrollableWindowComponent{
friend class InventoryCreator;
protected: protected:
PriceLabel::PriceLabel priceLabel=PriceLabel::NONE; PriceLabel::PriceLabel priceLabel=PriceLabel::NONE;
public: public:
inline RowInventoryScrollableWindowComponent(geom2d::rect<float>rect,std::string itemNameLabelName,std::string itemDescriptionLabelName,std::function<bool(MenuFuncData)>inventoryButtonClickAction,std::function<bool(MenuFuncData)>inventoryButtonHoverAction,std::function<bool(MenuFuncData)>inventoryButtonMouseOutAction,InventoryWindowOptions options={.padding=8,.size={24,24}},bool inventoryButtonsActive=true,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE) inline RowInventoryScrollableWindowComponent(geom2d::rect<float>rect,std::string itemNameLabelName,std::string itemDescriptionLabelName,std::function<bool(MenuFuncData)>inventoryButtonClickAction,std::function<bool(MenuFuncData)>inventoryButtonHoverAction,std::function<bool(MenuFuncData)>inventoryButtonMouseOutAction,const InventoryCreator&creator,InventoryWindowOptions options={.padding=8,.size={24,24}},bool inventoryButtonsActive=true,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)
:InventoryScrollableWindowComponent(rect,itemNameLabelName,itemDescriptionLabelName,inventoryButtonClickAction,inventoryButtonHoverAction,inventoryButtonMouseOutAction,options,inventoryButtonsActive,attributes){} :InventoryScrollableWindowComponent(rect,itemNameLabelName,itemDescriptionLabelName,inventoryButtonClickAction,inventoryButtonHoverAction,inventoryButtonMouseOutAction,creator,options,inventoryButtonsActive,attributes){}
virtual inline void SetCompactDescriptions(bool compact)override final{ virtual inline void SetCompactDescriptions(bool compact)override final{
if(compact)this->compact=COMPACT; if(compact)this->compact=COMPACT;
else this->compact=NON_COMPACT; else this->compact=NON_COMPACT;
for(MenuComponent*component:components){ for(MenuComponent*component:components){
RowItemDisplay*itemButton=dynamic_cast<RowItemDisplay*>(component); RowItemDisplay*itemButton=DYNAMIC_CAST<RowItemDisplay*>(component);
if(itemButton){
itemButton->SetCompactDescriptions(compact); itemButton->SetCompactDescriptions(compact);
}else{
ERR("WARNING! Attempting to cast a button that isn't a MenuItemButton!");
}
} }
} }
virtual inline void SetPriceLabelType(PriceLabel::PriceLabel labelType)final{ virtual inline void SetPriceLabelType(PriceLabel::PriceLabel labelType)final{
this->priceLabel=labelType; this->priceLabel=labelType;
for(MenuComponent*component:components){ for(MenuComponent*component:components){
RowItemDisplay*itemButton=dynamic_cast<RowItemDisplay*>(component); RowItemDisplay*itemButton=DYNAMIC_CAST<RowItemDisplay*>(component);
if(itemButton){
itemButton->SetPriceLabelType(labelType); itemButton->SetPriceLabelType(labelType);
}else{
ERR("WARNING! Attempting to cast a button that isn't a MenuItemButton!");
}
}
}
virtual inline void AddButtonOnSlotUpdate(ITCategory cat)override{
size_t invSize=components.size()+1;
int invWidth=int(rect.size.x/(float(options.size.x)+options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
int itemIndex=y*invWidth+x;
vf2d buttonSize=options.size;
vf2d totalSpacing={options.padding+buttonSize.x,options.padding+buttonSize.y};
auto newItem=ADD("item_"+cat+"_"+std::to_string(itemIndex),RowItemDisplay)({totalSpacing*vf2d{float(x),float(y)},buttonSize},Inventory::GetInventorySlot(cat,itemIndex),inventoryButtonClickAction,itemNameLabelName,itemDescriptionLabelName,inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
newItem->SetCompactDescriptions(compact==COMPACT);
newItem->SetPriceLabelType(priceLabel);
newItem->SetHoverFunc(inventoryButtonHoverAction);
newItem->SetMouseOutFunc(inventoryButtonMouseOutAction);
//Since we are indexing into a vector reference which is inevitably going to get erased as we add more items,
//we must update all previous menu component references in case that memory has been overwritten!
for(int counter=0;MenuComponent*component:components){
RowItemDisplay*item=dynamic_cast<RowItemDisplay*>(component);
if(item!=nullptr){
item->SetItem(Inventory::GetInventorySlot(cat,counter));
}else{
ERR("WARNING! Could not properly cast item to RowItemDisplay* type!");
}
counter++;
} }
} }
}; };

@ -1,78 +0,0 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 - 2022 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2023 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "RowInventoryScrollableWindowComponent.h"
class RowMerchantInventoryScrollableWindowComponent:public RowInventoryScrollableWindowComponent{
public:
inline RowMerchantInventoryScrollableWindowComponent(geom2d::rect<float>rect,std::string itemNameLabelName,std::string itemDescriptionLabelName,std::function<bool(MenuFuncData)>inventoryButtonClickAction,std::function<bool(MenuFuncData)>inventoryButtonHoverAction,std::function<bool(MenuFuncData)>inventoryButtonMouseOutAction,InventoryWindowOptions options={.padding=8,.size={24,24}},bool inventoryButtonsActive=true,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)
:RowInventoryScrollableWindowComponent(rect,itemNameLabelName,itemDescriptionLabelName,inventoryButtonClickAction,inventoryButtonHoverAction,inventoryButtonMouseOutAction,options,inventoryButtonsActive,attributes){}
virtual inline void OnInventorySlotsUpdate(ITCategory cat)override{
const std::vector<std::shared_ptr<Item>>&merchantInv=Merchant::GetCurrentTravelingMerchant().GetShopItems();
//We only want to refresh the inventory slots if the component count no longer matches what's actually in our inventory.
if(components.size()<merchantInv.size()){//We need more space to display our items.
AddButtonOnSlotUpdate(cat);
}else
if(components.size()>merchantInv.size()){ //There are empty spots, so let's clean up.
RemoveAllComponents();
for(std::shared_ptr<Item> item:merchantInv){
AddButtonOnSlotUpdate(cat);
}
}
}
virtual inline void AddButtonOnSlotUpdate(ITCategory cat)override{
const std::vector<std::shared_ptr<Item>>&merchantInv=Merchant::GetCurrentTravelingMerchant().GetShopItems();
size_t invSize=components.size()+1;
int invWidth=int(rect.size.x/(float(options.size.x)+options.padding));
int x=int((invSize-1)%invWidth);
int y=int((invSize-1)/invWidth);
int itemIndex=y*invWidth+x;
vf2d buttonSize=options.size;
vf2d totalSpacing={options.padding+buttonSize.x,options.padding+buttonSize.y};
auto newItem=ADD("merchant_item_"+cat+"_"+std::to_string(itemIndex),RowItemDisplay)({totalSpacing*vf2d{float(x),float(y)},buttonSize},Merchant::GetCurrentTravelingMerchant().GetShopItems()[itemIndex],inventoryButtonClickAction,itemNameLabelName,itemDescriptionLabelName,inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
newItem->SetCompactDescriptions(compact==COMPACT);
newItem->SetPriceLabelType(priceLabel);
newItem->SetHoverFunc(inventoryButtonHoverAction);
newItem->SetMouseOutFunc(inventoryButtonMouseOutAction);
}
};

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 2 #define VERSION_MINOR 2
#define VERSION_PATCH 1 #define VERSION_PATCH 1
#define VERSION_BUILD 4900 #define VERSION_BUILD 4919
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

Loading…
Cancel
Save