Added some menu scrolling helper functions. Implemented Artificer Enchant window. Added controller compatibility to Artificer Refining Window. Release Build 11652.

master
sigonasr2 3 months ago
parent a5c10f35e6
commit 86e2976549
  1. 10
      Adventures in Lestoria Tests/ItemTests.cpp
  2. 6
      Adventures in Lestoria/ArtificerDisassembleWindow.cpp
  3. 21
      Adventures in Lestoria/ArtificerEnchantConfirmWindow.cpp
  4. 58
      Adventures in Lestoria/ArtificerRefineWindow.cpp
  5. 12
      Adventures in Lestoria/ArtificerWindow.cpp
  6. 22
      Adventures in Lestoria/CharacterMenuWindow.cpp
  7. 4
      Adventures in Lestoria/Item.cpp
  8. 8
      Adventures in Lestoria/ItemEnchant.cpp
  9. 4
      Adventures in Lestoria/ItemEnchant.h
  10. 32
      Adventures in Lestoria/Menu.cpp
  11. 4
      Adventures in Lestoria/Menu.h
  12. 3
      Adventures in Lestoria/MenuItemLabel.h
  13. 32
      Adventures in Lestoria/ScrollableWindowComponent.h
  14. 2
      Adventures in Lestoria/Version.h
  15. 5
      Adventures in Lestoria/assets/config/audio/events.txt
  16. BIN
      Adventures in Lestoria/assets/sounds/take_enchant.ogg
  17. BIN
      x64/Release/Adventures in Lestoria.exe

@ -251,15 +251,21 @@ namespace ItemTests
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)}; std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Assert::IsFalse(slimeKingRing.lock()->HasEnchant()); Assert::IsFalse(slimeKingRing.lock()->HasEnchant());
for(int i:std::ranges::iota_view(0,1000)){ for(int i:std::ranges::iota_view(0,1000)){
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant()); std::string previousEnchantName{};
if(slimeKingRing.lock()->HasEnchant())previousEnchantName=slimeKingRing.lock()->GetEnchant().value().Name();
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchantName));
Assert::IsTrue(slimeKingRing.lock()->HasEnchant()); Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
Assert::AreNotEqual(previousEnchantName,slimeKingRing.lock()->GetEnchant().value().Name(),L"The previous enchant should never be the same as the new enchant attempt.");
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability. if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
} }
testGame->ChangePlayerClass(WIZARD); testGame->ChangePlayerClass(WIZARD);
player=testGame->GetPlayer(); //The player pointer has been reassigned... player=testGame->GetPlayer(); //The player pointer has been reassigned...
for(int i:std::ranges::iota_view(0,1000)){ for(int i:std::ranges::iota_view(0,1000)){
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant()); std::string previousEnchantName{};
if(slimeKingRing.lock()->HasEnchant())previousEnchantName=slimeKingRing.lock()->GetEnchant().value().Name();
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchantName));
Assert::IsTrue(slimeKingRing.lock()->HasEnchant()); Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
Assert::AreNotEqual(previousEnchantName,slimeKingRing.lock()->GetEnchant().value().Name(),L"The previous enchant should never be the same as the new enchant attempt.");
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability. if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
} }
} }

@ -109,7 +109,7 @@ void Menu::InitializeArtificerDisassembleWindow(){
EnableDisassemblyDisplay(); EnableDisassemblyDisplay();
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)}; RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem().lock()); Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem().lock());
Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(GFX.at(item.GetItem().lock()->FragmentName()).Decal()); Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(item.GetItem().lock()->Icon().Decal());
Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel(item.GetItem().lock()->FragmentName()); Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel(item.GetItem().lock()->FragmentName());
Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel(std::format("Currently Owned: {}",Inventory::GetItemCount(item.GetItem().lock()->FragmentName()))); Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel(std::format("Currently Owned: {}",Inventory::GetItemCount(item.GetItem().lock()->FragmentName())));
return true; return true;
@ -119,7 +119,7 @@ void Menu::InitializeArtificerDisassembleWindow(){
if(childComponent){ if(childComponent){
RowItemDisplay&item{childComponent.value().get()}; RowItemDisplay&item{childComponent.value().get()};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem().lock()); Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem().lock());
Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(GFX.at(item.GetItem().lock()->FragmentName()).Decal()); Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(item.GetItem().lock()->Icon().Decal());
Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel(item.GetItem().lock()->FragmentName()); Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel(item.GetItem().lock()->FragmentName());
Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel(std::format("Currently Owned: {}",Inventory::GetItemCount(item.GetItem().lock()->FragmentName()))); Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel(std::format("Currently Owned: {}",Inventory::GetItemCount(item.GetItem().lock()->FragmentName())));
EnableDisassemblyDisplay(); EnableDisassemblyDisplay();
@ -136,6 +136,8 @@ void Menu::InitializeArtificerDisassembleWindow(){
Menu::AddInventoryListener(inventoryDisplay,"Accessories"); Menu::AddInventoryListener(inventoryDisplay,"Accessories");
ResetDisassemblyDisplay();
artificerDisassembleWindow->SetupKeyboardNavigation( artificerDisassembleWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open [](MenuType type,Data&returnData){ //On Open
returnData=""; returnData="";

@ -40,6 +40,7 @@ All rights reserved.
#include "AdventuresInLestoria.h" #include "AdventuresInLestoria.h"
#include "MenuIconButton.h" #include "MenuIconButton.h"
#include "MenuItemLabel.h" #include "MenuItemLabel.h"
#include "SoundEffect.h"
INCLUDE_game INCLUDE_game
@ -68,12 +69,32 @@ void Menu::InitializeArtificerEnchantConfirmWindow(){
const float takeOldTextWidth{float(game->GetTextSize("Take Old").x)*2.f}; const float takeOldTextWidth{float(game->GetTextSize("Take Old").x)*2.f};
auto takeOldButton{artificerEnchantConfirmWindow->ADD("Take Old Button",MenuComponent)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-takeOldTextWidth/2.f,138.f},{takeOldTextWidth-8.f,20.f}},"Take Old",[](MenuFuncData data){ auto takeOldButton{artificerEnchantConfirmWindow->ADD("Take Old Button",MenuComponent)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-takeOldTextWidth/2.f,138.f},{takeOldTextWidth-8.f,20.f}},"Take Old",[](MenuFuncData data){
onClick: onClick:
const std::string&oldEnchantName{Menu::menus[ARTIFICER_ENCHANT]->S(A::ENCHANT_TYPE)};
std::weak_ptr<Item>newItem{std::get<std::weak_ptr<Item>>(Component<MenuItemLabel>(data.menu.GetType(),"New Item Description")->GetItem())}; //NOTE: We're making an assumption here that the new item description holds a weak pointer. This should be true because the only way to get here was to set it up through clicking the Enchant button in Artificer Enchant Window.
newItem.lock()->_EnchantItem(oldEnchantName);
Component<MenuComponent>(ARTIFICER_ENCHANT,"Fragment Enchant Button")->SetGrayedOut(!newItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{newItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Enchant Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Enchant Cost"_i[1]));
SoundEffect::PlaySFX("Take Enchant",SoundEffect::CENTERED);
Menu::CloseMenu();
return true; return true;
},vf2d{2.f,2.f})END}; },vf2d{2.f,2.f})END};
const float takeNewTextWidth{float(game->GetTextSize("Take New").x)*2.f}; const float takeNewTextWidth{float(game->GetTextSize("Take New").x)*2.f};
auto takeNewButton{artificerEnchantConfirmWindow->ADD("Take New Button",MenuComponent)(geom2d::rect<float>{vf2d{artificerEnchantConfirmWindow->size.x/2.f+8.f+artificerEnchantConfirmWindow->size.x/4.f-takeNewTextWidth/2.f,138.f},{takeNewTextWidth-8.f,20.f}},"Take New",[](MenuFuncData data){ auto takeNewButton{artificerEnchantConfirmWindow->ADD("Take New Button",MenuComponent)(geom2d::rect<float>{vf2d{artificerEnchantConfirmWindow->size.x/2.f+8.f+artificerEnchantConfirmWindow->size.x/4.f-takeNewTextWidth/2.f,138.f},{takeNewTextWidth-8.f,20.f}},"Take New",[](MenuFuncData data){
onClick: onClick:
std::weak_ptr<Item>newItem{std::get<std::weak_ptr<Item>>(Component<MenuItemLabel>(data.menu.GetType(),"New Item Description")->GetItem())};
Component<MenuComponent>(ARTIFICER_ENCHANT,"Fragment Enchant Button")->SetGrayedOut(!newItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{newItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Enchant Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Enchant Cost"_i[1]));
SoundEffect::PlaySFX("Take Enchant",SoundEffect::CENTERED);
Menu::CloseMenu();
return true; return true;
},vf2d{2.f,2.f})END}; },vf2d{2.f,2.f})END};

@ -157,7 +157,12 @@ void Menu::InitializeArtificerRefineWindow(){
artificerRefineWindow->SetupKeyboardNavigation( artificerRefineWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open [](MenuType type,Data&returnData){ //On Open
returnData=""; auto&items{Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents()};
if(items.size()>0){
returnData=items[0];
}else{
returnData="Back";
}
}, },
{ //Button Key { //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}}, {game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -165,12 +170,51 @@ void Menu::InitializeArtificerRefineWindow(){
Menu::CloseMenu(); Menu::CloseMenu();
}}}, }}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}}, {game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLUP,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->DecreaseSelectionIndex(3.f);
}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->IncreaseSelectionIndex(3.f);
}}},
} }
,{ //Button Navigation Rules ,{ //Button Navigation Rules
{"Sample Button",{ {"Accessory List",{
.up="", .up=[&](MenuType type,Data&returnData){
.down="", Menu::ScrollUp(type,"Accessory List",returnData,"Back");
.left="", },
.right="",}}, .down=[&](MenuType type,Data&returnData){
}); Menu::ScrollDown(type,"Accessory List",returnData,"Back");
},
.left="Back",
.right=[&](MenuType type,Data&returnData){
Menu::menus[type]->GetSelection().lock()->Click();
returnData="Fragment Refine Button";
},}},
{"Back",{
.up=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left="Fragment Refine Button",
.right="Fragment Refine Button",}},
{"Fragment Refine Button",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData="Back";
},
.right="Back",}},
}
);
} }

@ -76,7 +76,7 @@ void Menu::InitializeArtificerWindow(){
artificerWindow->SetupKeyboardNavigation( artificerWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open [](MenuType type,Data&returnData){ //On Open
returnData="Refine Button"; returnData="Disassemble Button";
}, },
{ //Button Key { //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}}, {game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -86,14 +86,14 @@ void Menu::InitializeArtificerWindow(){
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}}, {game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
} }
,{ //Button Navigation Rules ,{ //Button Navigation Rules
{"Refine Button",{
.up="Leave Button",
.down="Disassemble Button",}},
{"Disassemble Button",{ {"Disassemble Button",{
.up="Refine Button", .up="Leave Button",
.down="Refine Button",}},
{"Refine Button",{
.up="Disassemble Button",
.down="Enchant Button",}}, .down="Enchant Button",}},
{"Enchant Button",{ {"Enchant Button",{
.up="Disassemble Button", .up="Refine Button",
.down="Help Button",}}, .down="Help Button",}},
{"Help Button",{ {"Help Button",{
.up="Enchant Button", .up="Enchant Button",

@ -476,28 +476,10 @@ void Menu::InitializeCharacterMenuWindow(){
,{ //Button Navigation Rules ,{ //Button Navigation Rules
{"Equip List",{ {"Equip List",{
.up=[](MenuType type,Data&returnData){ .up=[](MenuType type,Data&returnData){
if(!Menu::menus[type]->GetSelection().expired()){ Menu::ScrollUp(type,"Equip List",returnData,"Equip Selection Select Button");
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponentIndex(selection);
index--;
if(index>=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents().size()){
returnData="Equip Selection Select Button";
}else{
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[index];
}
}
}, },
.down=[](MenuType type,Data&returnData){ .down=[](MenuType type,Data&returnData){
if(!Menu::menus[type]->GetSelection().expired()){ Menu::ScrollDown(type,"Equip List",returnData,"Equip Selection Select Button");
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponentIndex(selection);
index++;
if(index>=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents().size()){
returnData="Equip Selection Select Button";
}else{
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[index];
}
}
}, },
.left=[](MenuType type,Data&returnData){ .left=[](MenuType type,Data&returnData){
auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents(); auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents();

@ -1530,7 +1530,9 @@ const std::optional<std::string>&Item::FragmentIcon()const{
const ItemEnchantInfo Item::ApplyRandomEnchant(){ const ItemEnchantInfo Item::ApplyRandomEnchant(){
if(!CanBeEnchanted())ERR("WARNING! Trying to enchant an item that cannot be enchanted! Make sure you are checking with CanBeEnchanted() before calling this function!"); if(!CanBeEnchanted())ERR("WARNING! Trying to enchant an item that cannot be enchanted! Make sure you are checking with CanBeEnchanted() before calling this function!");
EnchantName randomEnchantName{ItemEnchant::RollRandomEnchant()}; std::string currentEnchantName{""};
if(HasEnchant())currentEnchantName=GetEnchant().value().Name();
EnchantName randomEnchantName{ItemEnchant::RollRandomEnchant(currentEnchantName)};
_EnchantItem(randomEnchantName); _EnchantItem(randomEnchantName);
Inventory::RemoveItem(FragmentName(),"Fragment Enchant Cost"_i[0]); Inventory::RemoveItem(FragmentName(),"Fragment Enchant Cost"_i[0]);
game->GetPlayer()->RemoveMoney("Fragment Enchant Cost"_i[1]); game->GetPlayer()->RemoveMoney("Fragment Enchant Cost"_i[1]);

@ -246,7 +246,7 @@ const std::vector<ItemEnchantInfo>ItemEnchant::GetAvailableEnchants(){
return filteredEnchants; return filteredEnchants;
} }
const std::string ItemEnchant::RollRandomEnchant(){ const std::string ItemEnchant::RollRandomEnchant(const std::string_view previousEnchantName){
const std::vector<ItemEnchantInfo>filteredEnchants{GetAvailableEnchants()}; const std::vector<ItemEnchantInfo>filteredEnchants{GetAvailableEnchants()};
int randomRoll{int(util::random_range(0,100))}; int randomRoll{int(util::random_range(0,100))};
@ -256,11 +256,11 @@ const std::string ItemEnchant::RollRandomEnchant(){
std::vector<ItemEnchantInfo>remainingAvailableEnchants; std::vector<ItemEnchantInfo>remainingAvailableEnchants;
if(randomRoll>=generalEnchantRange.first&&randomRoll<generalEnchantRange.second){ if(randomRoll>=generalEnchantRange.first&&randomRoll<generalEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::GENERAL;}); std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[&previousEnchantName](const ItemEnchantInfo&enchant){return enchant.Name()!=previousEnchantName&&enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::GENERAL;});
}else if(randomRoll>=classEnchantRange.first&&randomRoll<classEnchantRange.second){ }else if(randomRoll>=classEnchantRange.first&&randomRoll<classEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::CLASS;}); std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[&previousEnchantName](const ItemEnchantInfo&enchant){return enchant.Name()!=previousEnchantName&&enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::CLASS;});
}else if(randomRoll>=uniqueEnchantRange.first&&randomRoll<uniqueEnchantRange.second){ }else if(randomRoll>=uniqueEnchantRange.first&&randomRoll<uniqueEnchantRange.second){
std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[](const ItemEnchantInfo&enchant){return enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::UNIQUE;}); std::copy_if(filteredEnchants.begin(),filteredEnchants.end(),std::back_inserter(remainingAvailableEnchants),[&previousEnchantName](const ItemEnchantInfo&enchant){return enchant.Name()!=previousEnchantName&&enchant.Category()==ItemEnchantInfo::ItemEnchantCategory::UNIQUE;});
}else ERR(std::format("WARNING! Somehow did not pick a value in any of the given ranges. Rolled value was: {}. Ranges were {}-{}, {}-{}, {}-{}",randomRoll,generalEnchantRange.first,generalEnchantRange.second,classEnchantRange.first,classEnchantRange.second,uniqueEnchantRange.first,uniqueEnchantRange.second)); }else ERR(std::format("WARNING! Somehow did not pick a value in any of the given ranges. Rolled value was: {}. Ranges were {}-{}, {}-{}, {}-{}",randomRoll,generalEnchantRange.first,generalEnchantRange.second,classEnchantRange.first,classEnchantRange.second,uniqueEnchantRange.first,uniqueEnchantRange.second));
return remainingAvailableEnchants[util::random()%remainingAvailableEnchants.size()].Name(); return remainingAvailableEnchants[util::random()%remainingAvailableEnchants.size()].Name();

@ -100,6 +100,8 @@ private:
static std::unordered_map<ItemEnchantCategory,ItemEnchantCategoryData>ENCHANT_CATEGORIES; static std::unordered_map<ItemEnchantCategory,ItemEnchantCategoryData>ENCHANT_CATEGORIES;
}; };
class Item;
class ItemEnchant{ class ItemEnchant{
public: public:
ItemEnchant(const std::string_view enchantName); ItemEnchant(const std::string_view enchantName);
@ -109,7 +111,7 @@ public:
const std::optional<ItemEnchantInfo::AbilitySlot>&AbilitySlot()const; const std::optional<ItemEnchantInfo::AbilitySlot>&AbilitySlot()const;
const static std::vector<ItemEnchantInfo>GetAvailableEnchants(); const static std::vector<ItemEnchantInfo>GetAvailableEnchants();
//Rolls a class-appropriate random enchant. //Rolls a class-appropriate random enchant.
const static std::string RollRandomEnchant(); const static std::string RollRandomEnchant(const std::string_view previousEnchantName);
void SetAttribute(const std::string_view attr,const float val); void SetAttribute(const std::string_view attr,const float val);
const float&GetAttribute(const std::string_view attr)const; const float&GetAttribute(const std::string_view attr)const;
std::map<ItemAttribute,float>::const_iterator begin()const; std::map<ItemAttribute,float>::const_iterator begin()const;

@ -418,6 +418,7 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
if(std::holds_alternative<MenuDataFunc>(nav.up)){ if(std::holds_alternative<MenuDataFunc>(nav.up)){
Data returnData; Data returnData;
std::get<MenuDataFunc>(nav.up)(type,returnData); std::get<MenuDataFunc>(nav.up)(type,returnData);
if(!std::holds_alternative<ButtonName>(returnData)&&!std::holds_alternative<std::weak_ptr<MenuComponent>>(returnData))ERR(std::format("WARNING! No valid return type set for up button navigation in menu {}",int(type)));
SetSelection(returnData); SetSelection(returnData);
} }
} }
@ -429,6 +430,7 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
if(std::holds_alternative<MenuDataFunc>(nav.down)){ if(std::holds_alternative<MenuDataFunc>(nav.down)){
Data returnData; Data returnData;
std::get<MenuDataFunc>(nav.down)(type,returnData); std::get<MenuDataFunc>(nav.down)(type,returnData);
if(!std::holds_alternative<ButtonName>(returnData)&&!std::holds_alternative<std::weak_ptr<MenuComponent>>(returnData))ERR(std::format("WARNING! No valid return type set for down button navigation in menu {}",int(type)));
SetSelection(returnData); SetSelection(returnData);
} }
} }
@ -440,6 +442,7 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
if(std::holds_alternative<MenuDataFunc>(nav.left)){ if(std::holds_alternative<MenuDataFunc>(nav.left)){
Data returnData; Data returnData;
std::get<MenuDataFunc>(nav.left)(type,returnData); std::get<MenuDataFunc>(nav.left)(type,returnData);
if(!std::holds_alternative<ButtonName>(returnData)&&!std::holds_alternative<std::weak_ptr<MenuComponent>>(returnData))ERR(std::format("WARNING! No valid return type set for left button navigation in menu {}",int(type)));
SetSelection(returnData); SetSelection(returnData);
} }
} }
@ -451,6 +454,7 @@ void Menu::KeyboardButtonNavigation(AiL*game,vf2d menuPos){
if(std::holds_alternative<MenuDataFunc>(nav.right)){ if(std::holds_alternative<MenuDataFunc>(nav.right)){
Data returnData; Data returnData;
std::get<MenuDataFunc>(nav.right)(type,returnData); std::get<MenuDataFunc>(nav.right)(type,returnData);
if(!std::holds_alternative<ButtonName>(returnData)&&!std::holds_alternative<std::weak_ptr<MenuComponent>>(returnData))ERR(std::format("WARNING! No valid return type set for right button navigation in menu {}",int(type)));
SetSelection(returnData); SetSelection(returnData);
} }
} }
@ -814,4 +818,32 @@ const bool Menu::GameInitialized()const{
const bool Menu::IsMouseOverMenu(){ const bool Menu::IsMouseOverMenu(){
if(Menu::stack.size()==0)return false; if(Menu::stack.size()==0)return false;
return geom2d::overlaps(game->GetMousePos(),geom2d::rect<float>{Menu::stack.back()->pos-"Interface.9PatchSize"_V.x,Menu::stack.back()->size+"Interface.9PatchSize"_V.y*2}); return geom2d::overlaps(game->GetMousePos(),geom2d::rect<float>{Menu::stack.back()->pos-"Interface.9PatchSize"_V.x,Menu::stack.back()->size+"Interface.9PatchSize"_V.y*2});
}
void Menu::ScrollUp(const MenuType type,const std::string&componentName,Data&returnData,const std::optional<std::string>wrapDestination){
if(!Menu::menus[type]->GetSelection().expired()){
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,componentName)->GetComponentIndex(selection);
index--;
if(index>=Component<ScrollableWindowComponent>(type,componentName)->GetComponents().size()){
if(wrapDestination)returnData=wrapDestination.value();
else returnData=Component<ScrollableWindowComponent>(type,componentName)->GetComponents().back();
}else{
returnData=Component<ScrollableWindowComponent>(type,componentName)->GetComponents()[index];
}
}
}
void Menu::ScrollDown(const MenuType type,const std::string&componentName,Data&returnData,const std::optional<std::string>wrapDestination){
if(!Menu::menus[type]->GetSelection().expired()){
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,componentName)->GetComponentIndex(selection);
index++;
if(index>=Component<ScrollableWindowComponent>(type,componentName)->GetComponents().size()){
if(wrapDestination)returnData=wrapDestination.value();
else returnData=Component<ScrollableWindowComponent>(type,componentName)->GetComponents().front();
}else{
returnData=Component<ScrollableWindowComponent>(type,componentName)->GetComponents()[index];
}
}
} }

@ -208,6 +208,10 @@ public:
//Returns whether or not this menu type is currently in the foreground of the game, and thus being interacted with by the user. //Returns whether or not this menu type is currently in the foreground of the game, and thus being interacted with by the user.
static bool IsCurrentlyActive(MenuType type); static bool IsCurrentlyActive(MenuType type);
static const bool IsMouseOverMenu(); //Returns whether the mouse is hovering over any portion of the menu, thus can be used if a menu is not supposed to cover up everything to determine mouse interactions. static const bool IsMouseOverMenu(); //Returns whether the mouse is hovering over any portion of the menu, thus can be used if a menu is not supposed to cover up everything to determine mouse interactions.
//If a wrap destination is specified, will resolve the selection to that button when the top is reached, otherwise will default to wrapping around.
static void ScrollUp(const MenuType type,const std::string&componentName,Data&returnData,const std::optional<std::string>wrapDestination="");
//If a wrap destination is specified, will resolve the selection to that button when the bottom is reached, otherwise will default to wrapping around.
static void ScrollDown(const MenuType type,const std::string&componentName,Data&returnData,const std::optional<std::string>wrapDestination="");
private: private:
Menu(vf2d pos,vf2d size); Menu(vf2d pos,vf2d size);
static MenuType lastMenuTypeCreated; static MenuType lastMenuTypeCreated;

@ -90,6 +90,9 @@ public:
inline virtual void SetItem(std::variant<Item,std::weak_ptr<Item>>itemRef){ inline virtual void SetItem(std::variant<Item,std::weak_ptr<Item>>itemRef){
this->itemRef=itemRef; this->itemRef=itemRef;
} }
inline const std::variant<Item,std::weak_ptr<Item>>&GetItem()const{
return itemRef;
}
protected: protected:
//DO NOT USE! Use MenuLabel::SetLabel() instead! //DO NOT USE! Use MenuLabel::SetLabel() instead!
inline virtual void SetLabel(std::string text){ inline virtual void SetLabel(std::string text){

@ -68,6 +68,11 @@ protected:
return geom2d::overlaps(geom2d::rect<float>{{},rect.size},geom2d::rect<float>{component.lock()->rect.pos+vf2d{2,2},component.lock()->rect.size-vf2d{2,2}}); return geom2d::overlaps(geom2d::rect<float>{{},rect.size},geom2d::rect<float>{component.lock()->rect.pos+vf2d{2,2},component.lock()->rect.size-vf2d{2,2}});
} }
public: public:
enum ScrollResult{
NOT_SCROLLED,
SCROLLED,
};
inline ScrollableWindowComponent(geom2d::rect<float>rect,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE) inline ScrollableWindowComponent(geom2d::rect<float>rect,ComponentAttr attributes=ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)
:MenuComponent(rect,"",[](MenuFuncData data){return true;},ButtonAttr::UNSELECTABLE|ButtonAttr::UNSELECTABLE_VIA_KEYBOARD){ :MenuComponent(rect,"",[](MenuFuncData data){return true;},ButtonAttr::UNSELECTABLE|ButtonAttr::UNSELECTABLE_VIA_KEYBOARD){
background=attributes&ComponentAttr::BACKGROUND; background=attributes&ComponentAttr::BACKGROUND;
@ -139,10 +144,32 @@ public:
if(size_t(prevIndex)!=size_t(selectionIndex)){Menu::menus[parentMenu]->SetSelection(components[size_t(selectionIndex)],false);} if(size_t(prevIndex)!=size_t(selectionIndex)){Menu::menus[parentMenu]->SetSelection(components[size_t(selectionIndex)],false);}
} }
inline void IncreaseSelectionIndex(const float val){ inline const size_t GetSelectionIndex()const{
return selectionIndex;
}
inline std::weak_ptr<MenuComponent>GetSelectedComponent(){
return GetComponents().at(selectionIndex);
}
inline ScrollResult IncreaseSelectionIndex(const float val){
float prevIndex=selectionIndex; float prevIndex=selectionIndex;
selectionIndex=std::clamp(selectionIndex+val,0.f,float(components.size()-1)); selectionIndex=std::clamp(selectionIndex+val,0.f,float(components.size()-1));
if(size_t(prevIndex)!=size_t(selectionIndex)){Menu::menus[parentMenu]->SetSelection(components[size_t(selectionIndex)],false);} if(size_t(prevIndex)!=size_t(selectionIndex)){
Menu::menus[parentMenu]->SetSelection(components[size_t(selectionIndex)],false);
return SCROLLED;
}
return NOT_SCROLLED;
}
inline ScrollResult DecreaseSelectionIndex(const float val){
float prevIndex=selectionIndex;
selectionIndex=std::clamp(selectionIndex-val,0.f,float(components.size()-1));
if(size_t(prevIndex)!=size_t(selectionIndex)){
Menu::menus[parentMenu]->SetSelection(components[size_t(selectionIndex)],false);
return SCROLLED;
}
return NOT_SCROLLED;
} }
protected: protected:
virtual inline vf2d GetScrollAmount()const{ virtual inline vf2d GetScrollAmount()const{
@ -402,6 +429,7 @@ public:
button->renderInMain=false; //Now we are in control! button->renderInMain=false; //Now we are in control!
button->parentComponent=DYNAMIC_POINTER_CAST<ScrollableWindowComponent>(Menu::menus[parentMenu]->components[this->GetName()]); button->parentComponent=DYNAMIC_POINTER_CAST<ScrollableWindowComponent>(Menu::menus[parentMenu]->components[this->GetName()]);
button->disable=disable; button->disable=disable;
button->name=key;
CalculateBounds(); CalculateBounds();

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1 #define VERSION_MAJOR 1
#define VERSION_MINOR 3 #define VERSION_MINOR 3
#define VERSION_PATCH 0 #define VERSION_PATCH 0
#define VERSION_BUILD 11620 #define VERSION_BUILD 11652
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -417,6 +417,11 @@ Events
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%) # Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = rockland.ogg, 40% File[0] = rockland.ogg, 40%
} }
Take Enchant
{
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = take_enchant.ogg, 80%
}
Toggle On Toggle On
{ {
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%) # Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)

Loading…
Cancel
Save