// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
};
</script>
<scriptasynctype="text/javascript"src="Adventures in Lestoria.js"></script>
<scripttype="text/javascript">
Module.canvas.addEventListener("resize", (e) => {
var viewWidth = e.detail.width;
var viewHeight = e.detail.width / Module._olc_WindowAspectRatio;
ERR("Error TOGGLE!!! Please turn on debug_toggleable_items in configuration.txt and re-run the program to see which toggleable item did not properly get a toggleable group set.");
}
if(Unlock::unlocks.size()==0){
ERR("WARNING! There are no unlocks set! This was probably not intentional! It means no areasa on the overworld are accessible!");
}
@ -2582,8 +2575,8 @@ int AiL::GetCurrentChapter(){
blacksmithWindow->ADD("Item Name Label",MenuLabel)({{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
blacksmithWindow->ADD("Item Name Label",MenuLabel)(geom2d::rect<float>{{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
buyItemWindow->ADD("Price Per Item Label",MenuLabel)(geom2d::rect<float>{{4,18},{188,12}},"Price Per Item",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
buyItemWindow->ADD("Amount to Buy Label",MenuLabel)(geom2d::rect<float>{{4,34},{188,12}},"Amount to Buy",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
buyItemWindow->ADD("Price per item Amount Label",MenuLabel)({{buyItemWindow->size.x/2+28,18},{72,12}},"0",1.0f,ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
buyItemWindow->ADD("Amount to buy Amount Label",MenuLabel)({{buyItemWindow->size.x/2+48,34},{32,12}},"0",1.0f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::FIT_TO_LABEL)END;
buyItemWindow->ADD("Price per item Amount Label",MenuLabel)(geom2d::rect<float>{{buyItemWindow->size.x/2+28,18},{72,12}},"0",1.0f,ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
buyItemWindow->ADD("Amount to buy Amount Label",MenuLabel)(geom2d::rect<float>{{buyItemWindow->size.x/2+48,34},{32,12}},"0",1.0f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::FIT_TO_LABEL)END;
classInfoWindow->ADD("Rotating Character Display",CharacterRotatingDisplay)({{15,48},{72,120}},GFX[data.classFullImgName].Decal())END;
classInfoWindow->ADD("Rotating Character Display",CharacterRotatingDisplay)(geom2d::rect<float>{{15,48},{72,120}},GFX[data.classFullImgName].Decal())END;
if(OppositeRingSlotDoesNotMatchCurrentEquip(comp)){//If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
if(OppositeRingSlotDoesNotMatchCurrentEquip(comp.lock())){//If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
if(OppositeRingSlotDoesNotMatchCurrentEquip(button)){//If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
if(OppositeRingSlotDoesNotMatchCurrentEquip(button.lock())){//If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
classSelectionWindow->ADD("Class Selection Title Label",MenuLabel)({{4,20},{outlineSize.x,32}},"Choose a Character Class",2,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
classSelectionWindow->ADD("Class Selection Title Label",MenuLabel)(geom2d::rect<float>{{4,20},{outlineSize.x,32}},"Choose a Character Class",2,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
consumableCraftItemWindow->ADD("Item Name Header",MenuLabel)({{2,-4},{consumableCraftItemWindow->size.x-4,12}},"Item Name",1.0f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
consumableCraftItemWindow->ADD("Item Name Header",MenuLabel)(geom2d::rect<float>{{2,-4},{consumableCraftItemWindow->size.x-4,12}},"Item Name",1.0f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
staticautoGetQuantity=[&](){
returnstd::stoi(Component<MenuLabel>(CONSUMABLE_CRAFT_ITEM,"Amount to Craft Amount Label")->GetLabel());
consumableCraftItemWindow->ADD("Amount to Craft Label",MenuLabel)({{4,34},{188,12}},"Craft Amount",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
consumableCraftItemWindow->ADD("Amount to Craft Amount Label",MenuLabel)({{consumableCraftItemWindow->size.x/2+48,34},{32,12}},"1"
consumableCraftItemWindow->ADD("Amount to Craft Label",MenuLabel)(geom2d::rect<float>{{4,34},{188,12}},"Craft Amount",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
consumableCraftItemWindow->ADD("Amount to Craft Amount Label",MenuLabel)(geom2d::rect<float>{{consumableCraftItemWindow->size.x/2+48,34},{32,12}},"1"
consumableCraftingWindow->ADD("Item Name Label",MenuLabel)({{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
consumableCraftingWindow->ADD("Item Name Label",MenuLabel)(geom2d::rect<float>{{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
craftItemWindow->ADD("Item Name Header",MenuLabel)({{2,-16},{craftItemWindow->size.x-4,12}},"Item Name",1.0f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
craftItemWindow->ADD("Item Name Header",MenuLabel)(geom2d::rect<float>{{2,-16},{craftItemWindow->size.x-4,12}},"Item Name",1.0f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
craftItemWindow->ADD("Enhancement Level Header",MenuLabel)({{2,0},{craftItemWindow->size.x-4,12}},"Level X ->#00AA00 Y",1.f,ComponentAttr::SHADOW|ComponentAttr::FIXED_WIDTH_FONT)END;
craftItemWindow->ADD("Enhancement Level Header",MenuLabel)(geom2d::rect<float>{{2,0},{craftItemWindow->size.x-4,12}},"Level X ->#00AA00 Y",1.f,ComponentAttr::SHADOW|ComponentAttr::FIXED_WIDTH_FONT)END;
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")){
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==button){
if(button2->selected!=-1){
data.game->ClearLoadoutItem(button2->selected);
for(std::weak_ptr<MenuComponent>component:data.parentComponent.lock()->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.
std::weak_ptr<MenuItemButton>button2=DYNAMIC_POINTER_CAST<MenuItemButton>(component.lock());//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.expired())ERR("Could not cast item to a MenuItemButton*!");
//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,-5},{windowSize.x-1,18}},"Consumables",2,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END;
inventoryWindow->ADD("Inventory Type Label",MenuLabel)(geom2d::rect<float>{{0,-5},{windowSize.x-1,18}},"Consumables",2,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END;
levelCompleteWindow->ADD("Level EXP Gain Outline",MenuLabel)({{windowSize.size.x-72.f,104},{71,36}},"+ Exp",1,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
levelCompleteWindow->ADD("Level EXP Gain Outline",MenuLabel)(geom2d::rect<float>{{windowSize.size.x-72.f,104},{71,36}},"+ Exp",1,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
loadGameWindow->ADD("Go Back Button",MenuComponent)(geom2d::rect<float>{{24,124},{48,12}},"Back",[](MenuFuncDatamenu){Menu::CloseMenu();returntrue;})END;
ERR("WARNING! Menu Type "<<type<<" does not exist!")
}
//Lock up everything once it's done.
menus[type]->buttons.SetInitialized();
menus[type]->keyboardButtons.SetInitialized();
for(auto&[key,value]:menus[type]->components){
MenuComponent*component=value;
component->AfterCreate();
value->AfterCreate();
}
if(menus.size()>MAX_MENUS)ERR("WARNING! Exceeded maximum expected menu count of "<<MAX_MENUS<<"!");
}
if(Menu::unhandledComponents.size()>0){
std::cout<<"WARNING! There are "<<Menu::unhandledComponents.size()<<" components that were not added to any Menu! These have been leaked and should not be happening!"<<std::endl;
std::cout<<"See below for a report of unhandled components:"<<std::endl;
//Let's transfer some information about our selection being off the screen. Our intention with keyboard controls is that the screen will scroll to the correct location instead.
//If we return false, then we handled it ourselves, no need to go back to the previous selection.
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!!!
std::weak_ptr<InventoryScrollableWindowComponent>comp=DYNAMIC_POINTER_CAST<InventoryScrollableWindowComponent>(component.lock());//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!!!
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!!!
std::weak_ptr<InventoryScrollableWindowComponent>comp=DYNAMIC_POINTER_CAST<InventoryScrollableWindowComponent>(component.lock());//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!!!
ERR("WARNING! Component "<<component.lock()->name<<" has already been added to the "<<category<<" listener list! There should not be any duplicates!!")
ERR("WARNING! Component "<<component->name<<" has already been added to the "<<category<<" merchant listener list! There should not be any duplicates!!")
ERR("WARNING! Component "<<component.lock()->name<<" has already been added to the "<<category<<" merchant listener list! There should not be any duplicates!!")
@ -112,62 +112,28 @@ class Menu:public IAttributable{
friendclassEntityStats;
floatbuttonHoldTime=0;
vi2dselection={-1,-1};
std::weak_ptr<MenuComponent>selection;
vi2dlastActiveMousePos={};
intcomponentCount=0;
MenuComponent*draggingComponent=nullptr;
std::unique_ptr<MenuComponent>draggingComponent;
ViewPortwindow;
staticsafemap<ITCategory,std::vector<MenuComponent*>>inventoryListeners;//All menu components that care about inventory updates subscribe to this list indirectly (See Menu::AddInventoryListener()).
staticsafemap<ITCategory,std::vector<MenuComponent*>>merchantInventoryListeners;//All menu components that care about merchant inventory updates subscribe to this list indirectly (See Menu::AddMerchantInventoryListener()).
staticstd::vector<MenuComponent*>equipStatListeners;//All menu components that care about stat/equip updates subscribe to this list indirectly (See Menu::AddStatListener()).
staticstd::vector<MenuComponent*>chapterListeners;//All menu components that care about story chapter updates subscribe to this list indirectly (See Menu::AddChapterListener()).
staticsafemap<ITCategory,std::vector<std::weak_ptr<MenuComponent>>>inventoryListeners;//All menu components that care about inventory updates subscribe to this list indirectly (See Menu::AddInventoryListener()).
staticsafemap<ITCategory,std::vector<std::weak_ptr<MenuComponent>>>merchantInventoryListeners;//All menu components that care about merchant inventory updates subscribe to this list indirectly (See Menu::AddMerchantInventoryListener()).
staticstd::vector<std::weak_ptr<MenuComponent>>equipStatListeners;//All menu components that care about stat/equip updates subscribe to this list indirectly (See Menu::AddStatListener()).
staticstd::vector<std::weak_ptr<MenuComponent>>chapterListeners;//All menu components that care about story chapter updates subscribe to this list indirectly (See Menu::AddChapterListener()).
public:
//The constructor is private. Use CreateMenu() instead!
Menu()=default;
~Menu();
//DO NOT USE DIRECTLY! You should be utilizing the ADD macro for adding components.
//We must lock the values before calling sort. Sort seems to try and create new accesses.
buttons.SetInitialized();
keyboardButtons.SetInitialized();
//We make an assumption that menu components are supposed to be in left-to-right order. Sometimes we may add things out-of-order, so this fixes the problem by sorting the items afterwards.
staticstd::vector<MenuComponent*>unhandledComponents;//This list contains MenuComponents that are created and haven't been assigned via _AddComponent. If we get to the end of menu initialization and there are any components in this vector, we have leaked memory and will report this.
staticconstvf2dCENTERED;
staticboolIsMenuOpen();
constMenuTypeGetType()const;
safemap<std::string,MenuComponent*>components;//A friendly way to interrogate any component we are interested in.
std::vector<MenuComponent*>displayComponents;//Components that are only for displaying purposes.
safemap<std::string,std::shared_ptr<MenuComponent>>components;//A friendly way to interrogate any component we are interested in.
staticstd::map<MenuType,Menu*>menus;
vf2dpos;//Specify the upper-left corner of the window. Using CENTERED will always put this where the upper-left corner would center the window.
vf2dsize;//Size in tiles (24x24), every menu will be tile-based
safemap<int/*Y*/,std::vector<MenuComponent*>>buttons;//Buttons are stored in rows followed by their column order.
safemap<int/*Y*/,std::vector<MenuComponent*>>keyboardButtons;//Button ordered storage for keyboard/menu
staticTheme&GetCurrentTheme();
staticboolUsingMouseNavigation();
voidSetMouseNavigation(boolmouseNavigation);
staticvoidInventorySlotsUpdated(ITCategorycat);//Called whenever the player's inventory gets modified.
staticvoidMerchantInventorySlotsUpdated(ITCategorycat);//Called whenever a traveling merchant's inventory item gets updated.
staticvoidAddInventoryListener(MenuComponent*component,ITCategorycategory);//Adds a component to be in a given listener category.
staticvoidAddMerchantInventoryListener(MenuComponent*component,ITCategorycategory);//Adds a component to be in a given listener category.
staticvoidAddEquipStatListener(MenuComponent*component);//Adds a component to be in an equip stat listener. Will receive updates whenever stats are updated via equips.
staticvoidAddChapterListener(MenuComponent*component);//Adds a component to be in a chapter listener. Will receive updates anytime the chapter in-game changes.
staticvoidAddInventoryListener(std::weak_ptr<MenuComponent>component,ITCategorycategory);//Adds a component to be in a given listener category.
staticvoidAddMerchantInventoryListener(std::weak_ptr<MenuComponent>component,ITCategorycategory);//Adds a component to be in a given listener category.
staticvoidAddEquipStatListener(std::weak_ptr<MenuComponent>component);//Adds a component to be in an equip stat listener. Will receive updates whenever stats are updated via equips.
staticvoidAddChapterListener(std::weak_ptr<MenuComponent>component);//Adds a component to be in a chapter listener. Will receive updates anytime the chapter in-game changes.
vf2dcenter();
//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.
//pMenu->components.erase(this->name); //We're not going to do this here because we are in the middle of a loop for another menu component when cleaning up.
merchantWindow->ADD("Item Name Label",MenuLabel)({{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
merchantWindow->ADD("Item Name Label",MenuLabel)(geom2d::rect<float>{{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
saveFileWindow->ADD("Save File Name Entry Label",MenuLabel)({{-8,0},{112,36}},"Save File Name:",1.0f,ComponentAttr::SHADOW)END;
saveFileWindow->ADD("Save File Name Text Entry",TextEntryLabel)({{-8,36},{112,24}},TEXTCHANGE_DONOTHING,false,16U,2.f,ComponentAttr::FIT_TO_LABEL|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::BACKGROUND)END;
saveFileWindow->ADD("Save File Name Entry Label",MenuLabel)(geom2d::rect<float>{{-8,0},{112,36}},"Save File Name:",1.0f,ComponentAttr::SHADOW)END;
saveFileWindow->ADD("Save File Name Text Entry",TextEntryLabel)(geom2d::rect<float>{{-8,36},{112,24}},TEXTCHANGE_DONOTHING,false,16U,2.f,ComponentAttr::FIT_TO_LABEL|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::BACKGROUND)END;
sellItemWindow->ADD("Price Per Item Label",MenuLabel)(geom2d::rect<float>{{4,18},{188,12}},"Price Per Item",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
sellItemWindow->ADD("Amount to Sell Label",MenuLabel)(geom2d::rect<float>{{4,34},{188,12}},"Amount to Sell",1.0f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
sellItemWindow->ADD("Price per item Amount Label",MenuLabel)({{sellItemWindow->size.x/2+28,18},{72,12}},"0",1.0f,ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
sellItemWindow->ADD("Amount to sell Amount Label",MenuLabel)({{sellItemWindow->size.x/2+48,34},{32,12}},"0",1.0f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::FIT_TO_LABEL)END;
sellItemWindow->ADD("Price per item Amount Label",MenuLabel)(geom2d::rect<float>{{sellItemWindow->size.x/2+28,18},{72,12}},"0",1.0f,ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
sellItemWindow->ADD("Amount to sell Amount Label",MenuLabel)(geom2d::rect<float>{{sellItemWindow->size.x/2+48,34},{32,12}},"0",1.0f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::FIT_TO_LABEL)END;
userIDWindow->ADD("User ID Creation Explanation",MenuLabel)({{0,-4},{168,92}},"user_id_message"_FS+"\n\n"+"user_id_message2"_FS,1.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END;
userIDWindow->ADD("User ID Input",TextEntryLabel)({{36,94},{96,12}},[](std::string_viewnewLabel){
userIDWindow->ADD("User ID Creation Explanation",MenuLabel)(geom2d::rect<float>{{0,-4},{168,92}},"user_id_message"_FS+"\n\n"+"user_id_message2"_FS,1.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END;
userIDWindow->ADD("User ID Input",TextEntryLabel)(geom2d::rect<float>{{36,94},{96,12}},[](std::string_viewnewLabel){