Added Merchant/Buy/Sell Menu windows. Added sorted item callbacks for traveling merchant inventory modifications.

pull/28/head
sigonasr2 1 year ago
parent d9e1a1ebaf
commit 71fe49162d
  1. 48
      Crawler/BuyItemWindow.cpp
  2. 16
      Crawler/Crawler.vcxproj
  3. 12
      Crawler/Crawler.vcxproj.filters
  4. 3
      Crawler/InventoryConsumableWindow.cpp
  5. 6
      Crawler/InventoryScrollableWindowComponent.h
  6. 3
      Crawler/InventoryWindow.cpp
  7. 4
      Crawler/Item.cpp
  8. 6
      Crawler/LevelCompleteWindow.cpp
  9. 33
      Crawler/Menu.cpp
  10. 13
      Crawler/Menu.h
  11. 11
      Crawler/MenuComponent.cpp
  12. 19
      Crawler/MenuComponent.h
  13. 23
      Crawler/MenuLabel.h
  14. 30
      Crawler/Merchant.cpp
  15. 4
      Crawler/Merchant.h
  16. 92
      Crawler/MerchantWindow.cpp
  17. 1
      Crawler/MonsterAttribute.h
  18. 2
      Crawler/RowInventoryScrollableWindowComponent.h
  19. 86
      Crawler/RowMerchantInventoryScrollableWindowComponent.h
  20. 45
      Crawler/SellItemWindow.cpp
  21. 4
      Crawler/State_OverworldMap.cpp
  22. 2
      Crawler/Version.h
  23. BIN
      x64/Release/Crawler.exe

@ -0,0 +1,48 @@
#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 "Menu.h"
#include "MenuLabel.h"
void Menu::InitializeBuyItemWindow(){
Menu*buyItemWindow=CreateMenu(BUY_ITEM,CENTERED,{192,72});
buyItemWindow->ADD("Item Purchase Header",MenuLabel)({{2,2},{188,12}},"Buying ",1,ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND|ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
}

@ -403,6 +403,10 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="RowMerchantInventoryScrollableWindowComponent.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="safemap.h" />
<ClInclude Include="ScrollableWindowComponent.h" />
<ClInclude Include="InventoryScrollableWindowComponent.h" />
@ -439,6 +443,10 @@
</SubType>
</ClCompile>
<ClCompile Include="Bullet.cpp" />
<ClCompile Include="BuyItemWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="CharacterInfoWindow.cpp" />
<ClCompile Include="CharacterMenuWindow.cpp">
<SubType>
@ -491,6 +499,10 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="MerchantWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Meteor.cpp" />
<ClCompile Include="OverworldMapLevelWindow.cpp" />
<ClCompile Include="OverworldMenuWindow.cpp">
@ -507,6 +519,10 @@
<ClCompile Include="PulsatingFire.cpp" />
<ClCompile Include="Ranger.cpp" />
<ClCompile Include="RUN_STRATEGY.cpp" />
<ClCompile Include="SellItemWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ShootAfar.cpp" />
<ClCompile Include="SlimeKing.cpp" />
<ClCompile Include="State_GameRun.cpp" />

@ -369,6 +369,9 @@
<ClInclude Include="discord-files\types.h">
<Filter>Header Files\discord-files</Filter>
</ClInclude>
<ClInclude Include="RowMerchantInventoryScrollableWindowComponent.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">
@ -605,6 +608,15 @@
<ClCompile Include="discord-files\user_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="MerchantWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="BuyItemWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="SellItemWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />

@ -55,7 +55,7 @@ void Menu::InitializeConsumableInventoryWindow(){
inventoryWindow->I(A::LOADOUT_SLOT)=0;
inventoryWindow->ADD("inventory",InventoryScrollableWindowComponent)({{1,20},{windowSize.x,96.f}},"Consumables","itemName","itemDescription",
auto consumableWindow=inventoryWindow->ADD("inventory",InventoryScrollableWindowComponent)({{1,20},{windowSize.x,96.f}},"Consumables","itemName","itemDescription",
[&](MenuFuncData data){
MenuItemButton*button=(MenuItemButton*)data.component;
data.game->ClearLoadoutItem(data.menu.I(A::LOADOUT_SLOT));
@ -78,6 +78,7 @@ void Menu::InitializeConsumableInventoryWindow(){
data.game->SetLoadoutItem(button->selected,button->GetItem().ActualName());
return true;
})END;
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.
inventoryWindow->ADD("Inventory Type Label",MenuLabel)({{0,0},{windowSize.x-1,18}},"Consumables",2,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END;

@ -72,9 +72,7 @@ public:
}
inline InventoryScrollableWindowComponent(geom2d::rect<float>rect,ITCategory invType,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)
:ScrollableWindowComponent(rect,attributes),inventoryButtonHoverAction(inventoryButtonHoverAction),inventoryButtonMouseOutAction(inventoryButtonMouseOutAction),inventoryType(invType),itemNameLabelName(itemNameLabelName),itemDescriptionLabelName(itemDescriptionLabelName),
options(options),inventoryButtonClickAction(inventoryButtonClickAction),inventoryButtonsActive(inventoryButtonsActive){
Menu::AddInventoryListener(this,invType);
}
options(options),inventoryButtonClickAction(inventoryButtonClickAction),inventoryButtonsActive(inventoryButtonsActive){}
virtual inline void Update(Crawler*game)override{
ScrollableWindowComponent::Update(game);
bool noneHovered=true;
@ -122,7 +120,7 @@ protected:
}
CalculateBounds(); //Recalculate the bounds as it's possible the width/height of the component has changed.
}
virtual inline void OnInventorySlotsUpdate(ITCategory cat)override final{
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.

@ -81,7 +81,7 @@ void Menu::InitializeInventoryWindow(){
//Close the old inventory window and show the proper one.
Component<RowInventoryScrollableWindowComponent>(data.menu.GetType(),"Inventory Display - "+lastInventoryTypeOpened)->Enable(false);
Component<MenuComponent>(data.menu.GetType(),lastInventoryTypeOpened+" Inventory Tab")->SetSelected(false);
Component<InventoryScrollableWindowComponent>(data.menu.GetType(),"Inventory Display - "+data.component->S(A::CATEGORY_NAME))->Enable(true);
Component<RowInventoryScrollableWindowComponent>(data.menu.GetType(),"Inventory Display - "+data.component->S(A::CATEGORY_NAME))->Enable(true);
Component<MenuComponent>(data.menu.GetType(),data.component->S(A::CATEGORY_NAME)+" Inventory Tab")->SetSelected(true);
lastInventoryTypeOpened=data.component->S(A::CATEGORY_NAME);
return true;
@ -106,6 +106,7 @@ void Menu::InitializeInventoryWindow(){
button->onClick(MenuFuncData{*inventoryWindow,game,button}); //Simulate a click of this button if it's the top one for an initial inventory display.
}
Menu::AddInventoryListener(inventoryDisplay,category);
inventoryDisplay->Enable(first);
inventoryDisplay->SetCompactDescriptions(false);

@ -86,7 +86,7 @@ void ItemInfo::InitializeItems(){
for(auto&[key,value]:DATA["ItemCategory"].GetKeys()){
ITEM_CATEGORIES[key];
Inventory::sortedInv[key];
Menu::inventoryListeners[key];
Menu::InitializeMenuListenerCategory(key);
}
auto ReadItems=[&](datafile&data){
@ -197,7 +197,7 @@ void ItemInfo::InitializeItems(){
ITEM_DATA.SetInitialized();
ITEM_CATEGORIES.SetInitialized();
Menu::inventoryListeners.SetInitialized();
Menu::LockInListeners();
for(auto&[name,info]:ITEM_DATA){
Item tempItem{1,name};

@ -55,11 +55,13 @@ 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 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 Window",InventoryScrollableWindowComponent)({{0,44},{windowSize.size.x-80.f,60}},"Monster Loot","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","Monster Loot Popup Item Name","Monster Loot Popup Item Description",DO_NOTHING)END;
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 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 Window",InventoryScrollableWindowComponent)({{0,120},{windowSize.size.x-80.f,60}},"Stage Loot","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","Stage Loot Popup Item Name","Stage Loot Popup Item Description",DO_NOTHING)END;
Menu::AddInventoryListener(stageLootWindow,"Stage Loot");
auto nextButtonAction=[](MenuFuncData data){
Unlock::UnlockArea(State_OverworldMap::GetCurrentConnectionPoint().map);

@ -49,6 +49,7 @@ std::map<MenuType,Menu*>Menu::menus;
std::string Menu::themeSelection="BlueDefault";
safeunorderedmap<std::string,Theme>Menu::themes;
safemap<ITCategory,std::vector<MenuComponent*>>Menu::inventoryListeners;
safemap<ITCategory,std::vector<MenuComponent*>>Menu::merchantInventoryListeners;
std::vector<MenuComponent*>Menu::equipStatListeners;
const vf2d Menu::CENTERED = {-456,-456};
std::vector<MenuComponent*>Menu::unhandledComponents;
@ -97,6 +98,9 @@ void Menu::InitializeMenus(){
InitializeOverworldMenuWindow();
InitializeCharacterMenuWindow();
InitializeInventoryWindow();
InitializeMerchantWindow();
InitializeBuyItemWindow();
InitializeSellItemWindow();
for(MenuType type=MenuType(int(MenuType::ENUM_START)+1);type<MenuType::ENUM_END;type=MenuType(int(type+1))){
if(menus.count(type)==0){
@ -573,6 +577,13 @@ void Menu::InventorySlotsUpdated(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.
for(MenuComponent*component:merchantInventoryListeners.at(cat)){
component->OnInventorySlotsUpdate(cat);
}
}
void Menu::AddInventoryListener(MenuComponent*component,ITCategory category){
if(inventoryListeners.count(category)){
std::vector<MenuComponent*>&listenerList=inventoryListeners.at(category);
@ -585,6 +596,23 @@ void Menu::AddInventoryListener(MenuComponent*component,ITCategory category){
}
}
void Menu::InitializeMenuListenerCategory(const std::string&category){
inventoryListeners[category];
merchantInventoryListeners[category];
}
void Menu::AddMerchantInventoryListener(MenuComponent*component,ITCategory category){
if(merchantInventoryListeners.count(category)){
std::vector<MenuComponent*>&listenerList=merchantInventoryListeners.at(category);
if(std::find(listenerList.begin(),listenerList.end(),component)!=listenerList.end()){
ERR("WARNING! Component "<<component->name<<" has already been added to the "<<category<<" merchant listener list! There should not be any duplicates!!")
}
listenerList.push_back(component);
}else{
ERR("WARNING! Inventory category "<<category<<" does not exist!")
}
}
void Menu::AddEquipStatListener(MenuComponent*component){
if(std::find(equipStatListeners.begin(),equipStatListeners.end(),component)!=equipStatListeners.end()){
ERR("WARNING! Component "<<component->name<<" has already been added to the Equip Stat listener list! There should not be any duplicates!!")
@ -654,4 +682,9 @@ void Menu::RecalculateComponentCount(){
MenuType Menu::GetType(){
return type;
}
void Menu::LockInListeners(){
inventoryListeners.SetInitialized();
merchantInventoryListeners.SetInitialized();
}

@ -72,6 +72,9 @@ enum MenuType{
OVERWORLD_MENU,
CHARACTER_MENU,
INVENTORY,
MERCHANT,
BUY_ITEM,
SELL_ITEM,
#pragma region Enum End //DO NOT REMOVE
///////////////////////////////////////////////////////////
/*DO NOT REMOVE!!*/ENUM_END////////////////////////////////
@ -90,6 +93,9 @@ class Menu:public IAttributable{
static void InitializeOverworldMenuWindow();
static void InitializeCharacterMenuWindow();
static void InitializeInventoryWindow();
static void InitializeMerchantWindow();
static void InitializeBuyItemWindow();
static void InitializeSellItemWindow();
friend class Crawler;
friend struct Player;
@ -104,6 +110,7 @@ class Menu:public IAttributable{
MenuComponent*draggingComponent=nullptr;
ViewPort window;
static safemap<ITCategory,std::vector<MenuComponent*>>inventoryListeners; //All menu components that care about inventory updates subscribe to this list indirectly (See Menu::AddInventoryListener()).
static safemap<ITCategory,std::vector<MenuComponent*>>merchantInventoryListeners; //All menu components that care about merchant inventory updates subscribe to this list indirectly (See Menu::AddMerchantInventoryListener()).
static std::vector<MenuComponent*>equipStatListeners; //All menu components that care about stat/equip updates subscribe to this list indirectly (See Menu::AddStatListener()).
public:
//The constructor is private. Use CreateMenu() instead!
@ -167,7 +174,9 @@ public:
}
void Update(Crawler*game);
void Draw(Crawler*game);
static void InitializeMenuListenerCategory(const std::string&category);
static void InitializeMenus();
static void LockInListeners();
static void OpenMenu(MenuType menu,bool cover=true);
static void CloseMenu();
static void CloseAllMenus();
@ -190,8 +199,10 @@ public:
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.
static void InventorySlotsUpdated(ITCategory cat); //Called whenever the player's inventory gets modified.
static void MerchantInventorySlotsUpdated(ITCategory cat); //Called whenever a traveling merchant's inventory item gets updated.
static void AddInventoryListener(MenuComponent*component,ITCategory category); //Adds a component to be in a given listener category.
static void AddMerchantInventoryListener(MenuComponent*component,ITCategory category); //Adds a component to be in a given listener category.
static void AddEquipStatListener(MenuComponent*component); //Adds a component to be in an equip stat listener. Will receive updates whenever stats are updated via equips.
vf2d center();
//Returns the last menu type created and last registered component, in case a component is detected as memory leaking, provides this information to each component for safety.

@ -45,7 +45,7 @@ INCLUDE_game
using A=Attribute;
MenuComponent::MenuComponent(geom2d::rect<float>rect,std::string label,MenuFunc onClick,ButtonAttr attributes)
:rect(rect),originalPos(rect.pos),label(label),menuDest(MenuType::ENUM_END),onClick(onClick),hoverEffect(0),selectable(!(attributes&ButtonAttr::UNSELECTABLE)),selectableViaKeyboard(!(attributes&ButtonAttr::UNSELECTABLE_VIA_KEYBOARD)),memoryLeakInfo(Menu::GetMemoryLeakReportInfo()){
:rect(rect),originalPos(rect.pos),label(label),menuDest(MenuType::ENUM_END),onClick(onClick),hoverEffect(0),selectable(!(attributes&ButtonAttr::UNSELECTABLE)),selectableViaKeyboard(!(attributes&ButtonAttr::UNSELECTABLE_VIA_KEYBOARD)),memoryLeakInfo(Menu::GetMemoryLeakReportInfo()),fitToLabel(attributes&ButtonAttr::FIT_TO_LABEL){
Menu::unhandledComponents.push_back(this);
}
@ -114,7 +114,14 @@ void MenuComponent::DrawDecal(ViewPort&window,bool focused){
window.FillRectDecal(rect.pos+V(A::DRAW_OFFSET)+vf2d{0,rect.size.y-1+1},{rect.size.x+1,1});
}
if(showDefaultLabel){
window.DrawStringPropDecal(rect.pos+V(A::DRAW_OFFSET)+rect.size/2-vf2d(game->GetTextSizeProp(label))/2.f*labelScaling,label,WHITE,labelScaling);
vf2d adjustedScale=labelScaling;
if(fitToLabel){
float sizeRatio=((game->GetTextSizeProp(label)*adjustedScale).x)/(rect.size.x-2);
if(sizeRatio>1){
adjustedScale.x/=sizeRatio;
}
}
window.DrawStringPropDecal(rect.pos+V(A::DRAW_OFFSET)+rect.size/2-vf2d(game->GetTextSizeProp(label))/2.f*adjustedScale,label,WHITE,adjustedScale);
}
if(selected){
switch(selectionType){

@ -40,17 +40,19 @@ All rights reserved.
#include "BitwiseEnum.h"
enum class ButtonAttr{
NONE=0b00,
UNSELECTABLE=0b01, //Makes the component unselectable.
UNSELECTABLE_VIA_KEYBOARD=0b10, //Makes the component unselectable via keyboard.
NONE= 0b000,
UNSELECTABLE= 0b001, //Makes the component unselectable.
UNSELECTABLE_VIA_KEYBOARD= 0b010, //Makes the component unselectable via keyboard.
FIT_TO_LABEL= 0b100, //Scales the text horizontally to fit the label if the text takes up more space rather than wrapping.
};
enum class ComponentAttr{
NONE=0b0000,
LEFT_ALIGN=0b0001, //Labels are centered by default.
SHADOW=0b0010, //Adds shadows to the label text.
OUTLINE=0b0100, //Adds an outline around the component.
BACKGROUND=0b1000, //Renders the background of the menu theme for this component.
NONE= 0b00000,
LEFT_ALIGN= 0b00001, //Labels are centered by default.
SHADOW= 0b00010, //Adds shadows to the label text.
OUTLINE= 0b00100, //Adds an outline around the component.
BACKGROUND= 0b01000, //Renders the background of the menu theme for this component.
FIT_TO_LABEL= 0b10000, //Scales the text horizontally to fit the label if the text takes up more space rather than wrapping.
};
enum class SelectionType{
@ -105,6 +107,7 @@ protected:
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.
bool fitToLabel=false; //Will shrink text horizontally to fit the size of the label if the display text is too large.
vf2d labelScaling={1,1};
virtual void Update(Crawler*game);
virtual void DrawDecal(ViewPort&window,bool focused);

@ -56,6 +56,7 @@ public:
border=attributes&ComponentAttr::OUTLINE;
this->background=attributes&ComponentAttr::BACKGROUND;
showDefaultLabel=false;
fitToLabel=attributes&ComponentAttr::FIT_TO_LABEL;
}
inline virtual void SetLabel(std::string text){
label=text;
@ -66,15 +67,27 @@ protected:
}
virtual void inline DrawDecal(ViewPort&window,bool focused)override{
MenuComponent::DrawDecal(window,focused);
std::string wrappedText=util::WrapText(game,label,int(rect.size.x),true,{float(scale),float(scale)});
vf2d drawPos=rect.middle()-vf2d{game->GetTextSizeProp(wrappedText)}*float(scale)/2; //Assume centered.
std::string adjustedText=label;
if(!fitToLabel){
adjustedText=util::WrapText(game,label,int(rect.size.x),true,{float(scale),float(scale)});
}
vf2d adjustedScale={scale,scale};
if(fitToLabel){
float sizeRatio=((game->GetTextSizeProp(label)*adjustedScale).x)/(rect.size.x-2);
if(sizeRatio>1){
adjustedScale.x/=sizeRatio;
}
}
vf2d drawPos=rect.middle()-vf2d{game->GetTextSizeProp(adjustedText)}*float(adjustedScale.x)/2; //Assume centered.
if(!centered){
drawPos=vf2d{rect.pos.x+2,rect.middle().y-game->GetTextSizeProp(wrappedText).y/2}; //We should at least vertically align here.
drawPos=vf2d{rect.pos.x+2,rect.middle().y-game->GetTextSizeProp(adjustedText).y/2}; //We should at least vertically align here.
}
if(shadow){
window.DrawShadowStringPropDecal(drawPos,wrappedText,WHITE,BLACK,{float(scale),float(scale)});
window.DrawShadowStringPropDecal(drawPos,adjustedText,WHITE,BLACK,adjustedScale);
}else{
window.DrawStringPropDecal(drawPos,wrappedText,WHITE,{float(scale),float(scale)});
window.DrawStringPropDecal(drawPos,adjustedText,WHITE,adjustedScale);
}
}
};

@ -36,18 +36,22 @@ All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "Merchant.h"
#include "Crawler.h"
INCLUDE_game
INCLUDE_ITEM_CATEGORIES
std::map<Chapter,std::vector<Merchant>>Merchant::merchants;
std::map<ITCategory,std::vector<Item*>>Merchant::sortedItems;
MerchantFunctionPrimingData Merchant::purchaseFunctionPrimed("CanPurchaseItem()");
MerchantFunctionPrimingData Merchant::sellFunctionPrimed("CanSellItem()");
Merchant Merchant::travelingMerchant;
const Merchant&Merchant::GetRandomMerchant(Chapter chapter){
return merchants[chapter][rand()%(merchants[chapter].size()-1)];
const Merchant&newMerchant=merchants[chapter][rand()%(merchants[chapter].size()-1)];
return newMerchant;
}
const std::string&Merchant::GetDisplayName()const{
@ -58,6 +62,10 @@ const std::vector<Item>&Merchant::GetShopItems()const{
return shopItems;
}
const std::vector<Item*>&Merchant::GetShopItems(ITCategory category){
return sortedItems[category];
}
Merchant&Merchant::AddMerchant(Chapter chapter){
merchants[chapter].push_back({});
return merchants[chapter].back();
@ -65,6 +73,20 @@ Merchant&Merchant::AddMerchant(Chapter chapter){
void Merchant::AddItem(IT item,uint32_t amt,uint8_t enhancementLevel){
shopItems.push_back(Item{amt,item,enhancementLevel});
if(&GetCurrentTravelingMerchant()==this){
UpdateSortedItemsList();
Menu::MerchantInventorySlotsUpdated(ITEM_DATA[item].Category());
}
}
void Merchant::UpdateSortedItemsList(){
const Merchant&merchant=GetCurrentTravelingMerchant();
for(auto&[key,items]:ITEM_CATEGORIES){
sortedItems[key].clear();
}
std::for_each(merchant.shopItems.begin(),merchant.shopItems.end(),[](const Item&item){
sortedItems[item.Category()].push_back(const_cast<Item*>(&item));
});
}
INCLUDE_DATA
@ -137,7 +159,7 @@ bool Merchant::CanSellItem(IT item,uint32_t amt)const{
};
void Merchant::PurchaseItem(IT item,uint32_t amt){
purchaseFunctionPrimed.Validate(item,amt);
uint32_t totalCost=0U;
for(Item&it:shopItems){
if(it==item){
@ -181,6 +203,10 @@ void Merchant::SellItem(IT item,uint32_t amt){
void Merchant::RandomizeTravelingMerchant(){
travelingMerchant=GetRandomMerchant(game->GetCurrentChapter());
for(auto&[key,items]:ITEM_CATEGORIES){
Menu::MerchantInventorySlotsUpdated(key);
}
UpdateSortedItemsList();
};
Merchant&Merchant::GetCurrentTravelingMerchant(){
return travelingMerchant;

@ -42,6 +42,7 @@ class Item;
using Chapter=int;
using IT=std::string;
using ITCategory=std::string;
class Merchant{
public:
@ -49,6 +50,7 @@ public:
static Merchant&GetCurrentTravelingMerchant();
const std::string&GetDisplayName()const;
const std::vector<Item>&GetShopItems()const;
const static std::vector<Item*>&GetShopItems(ITCategory category);
void AddItem(IT item,uint32_t amt=1,uint8_t enhancementLevel=0U);
bool CanPurchaseItem(IT item,uint32_t amt=1U)const;
bool CanSellItem(IT item,uint32_t amt=1U)const;
@ -65,4 +67,6 @@ private:
static std::map<Chapter,std::vector<Merchant>>merchants;
std::string displayName;
std::vector<Item>shopItems;
static std::map<ITCategory,std::vector<Item*>>sortedItems;
static void UpdateSortedItemsList();
};

@ -0,0 +1,92 @@
#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 "Menu.h"
#include "Crawler.h"
#include "RowMerchantInventoryScrollableWindowComponent.h"
#include "MenuItemItemButton.h"
#include "MenuComponent.h"
INCLUDE_game
INCLUDE_ITEM_CATEGORIES
INCLUDE_DATA
void Menu::InitializeMerchantWindow(){
Menu*merchantWindow=CreateMenu(MERCHANT,CENTERED,game->GetScreenSize()-vi2d{24,24});
static std::string lastInventoryTypeOpened="";
std::vector<std::pair<std::string,int>>categories;
for(auto&[category,items]:ITEM_CATEGORIES){
if(DATA["ItemCategory"][category].GetString(0)=="!HIDE")continue; //This category is meant to be hidden!
categories.push_back({category,DATA["ItemCategory"][category].GetInt(0)}); //We assume the first value becomes the sort order we wish to use.
}
std::sort(categories.begin(),categories.end(),[](std::pair<std::string,int>&cat1,std::pair<std::string,int>&cat2){return cat1.second<cat2.second;});
auto inventoryDisplay=merchantWindow->ADD("Merchant Inventory Display",RowMerchantInventoryScrollableWindowComponent)({{2,28},{220,merchantWindow->size.y-44}},"Item Name Label","Item Description Label",
[](MenuFuncData data){
RowItemDisplay*item=dynamic_cast<RowItemDisplay*>(data.component);
Component<MenuLabel>(BUY_ITEM,"Item Purchase Header")->S(A::ITEM_NAME)=item->GetItem().ActualName();
Component<MenuLabel>(BUY_ITEM,"Item Purchase Header")->SetLabel("Buying "+item->GetItem().DisplayName());
Menu::OpenMenu(BUY_ITEM);
return true;
},
[](MenuFuncData data){
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(dynamic_cast<RowItemDisplay*>(data.component)->GetItem());
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon();
return true;
},
[](MenuFuncData data){
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->SetItem(Item::BLANK);
Component<MenuItemItemButton>(data.menu.GetType(),"Item Icon")->UpdateIcon();
return true;
},{.padding=1,.size={220-13,28}})END;
for(auto&[category,items]:ITEM_CATEGORIES){
Menu::AddMerchantInventoryListener(inventoryDisplay,category);
}
#pragma region Inventory Description
float inventoryDescriptionWidth=merchantWindow->pos.x+merchantWindow->size.x-26-224;
merchantWindow->ADD("Item Description Outline",MenuLabel)({{224,28},{inventoryDescriptionWidth,merchantWindow->size.y-44}},"",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
merchantWindow->ADD("Item Icon",MenuItemItemButton)({{226+inventoryDescriptionWidth/2-24,30},{48,48}},Item::BLANK,MenuType::ENUM_END,DO_NOTHING,"","",IconButtonAttr::NOT_SELECTABLE)END;
merchantWindow->ADD("Item Name Label",MenuLabel)({{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
merchantWindow->ADD("Item Description Label",MenuLabel)({{226,94},{inventoryDescriptionWidth-6,merchantWindow->size.y-44-66}},"",0.5f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
#pragma endregion
}

@ -72,4 +72,5 @@ enum class Attribute{
BASE_TEXT,
CATEGORY_NAME,
DRAW_OFFSET,
ITEM_NAME,
};

@ -44,7 +44,7 @@ public:
inline RowInventoryScrollableWindowComponent(geom2d::rect<float>rect,ITCategory invType,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)
:InventoryScrollableWindowComponent(rect,invType,itemNameLabelName,itemDescriptionLabelName,inventoryButtonClickAction,inventoryButtonHoverAction,inventoryButtonMouseOutAction,options,inventoryButtonsActive,attributes){}
virtual inline void SetCompactDescriptions(bool compact)override{
virtual inline void SetCompactDescriptions(bool compact)override final{
if(compact)this->compact=COMPACT;
else this->compact=NON_COMPACT;
for(MenuComponent*component:components){

@ -0,0 +1,86 @@
#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<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.
RemoveEmptySlots();
}
}
virtual inline void AddButtonOnSlotUpdate(ITCategory cat)override{
const std::vector<Item>&merchantInv=Merchant::GetCurrentTravelingMerchant().GetShopItems();
size_t invSize=merchantInv.size();
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},Merchant::GetCurrentTravelingMerchant().GetShopItems()[itemIndex],inventoryButtonClickAction,itemNameLabelName,itemDescriptionLabelName,inventoryButtonsActive?ButtonAttr::NONE:ButtonAttr::UNSELECTABLE)END;
newItem->SetCompactDescriptions(compact==COMPACT);
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(merchantInv[itemIndex]);
}else{
ERR("WARNING! Could not properly cast item to RowItemDisplay* type!");
}
counter++;
}
}
};

@ -0,0 +1,45 @@
#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 "Menu.h"
#include "MenuLabel.h"
void Menu::InitializeSellItemWindow(){
Menu*sellItemWindow=CreateMenu(SELL_ITEM,CENTERED,{120,72});
}

@ -92,6 +92,10 @@ void State_OverworldMap::OnUserUpdate(Crawler*game){
Menu::OpenMenu(OVERWORLD_MENU);
}
if(game->GetKey(S).bPressed){
Menu::OpenMenu(MERCHANT);
}
#pragma region Handle Connection Point Clicking and Movement
for(ConnectionPoint&cp:connections){
if(game->GetMouse(Mouse::LEFT).bPressed&&geom2d::overlaps(game->GetWorldMousePos(),cp.rect)){

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

Binary file not shown.
Loading…
Cancel
Save