2023-11-20 23:25:36 -06:00
# pragma region License
2023-11-14 18:11:32 -06:00
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
2024-01-02 00:46:32 -06:00
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . com >
2023-11-14 18:11:32 -06:00
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 .
2023-11-29 00:50:00 -06:00
Portions of this software are copyright © 2023 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
2023-11-14 18:11:32 -06:00
*/
2023-11-20 23:25:36 -06:00
# pragma endregion
2023-09-28 21:13:04 +00:00
# pragma once
2023-10-17 05:35:19 -05:00
# include "Item.h"
2023-09-28 21:13:04 +00:00
# include <stack>
2023-10-03 02:34:26 -05:00
# include "safemap.h"
# include "Theme.h"
# include "Attributable.h"
2023-10-07 15:47:26 -05:00
# include "olcUTIL_Geometry2D.h"
2023-12-14 04:18:05 -06:00
# include "olcPGEX_ViewPort.h"
2023-10-01 01:48:27 -05:00
2024-01-04 05:21:56 -06:00
class AiL ;
2023-10-07 15:47:26 -05:00
class MenuComponent ;
2023-11-21 21:23:48 -06:00
class ScrollableWindowComponent ;
2023-10-07 15:47:26 -05:00
2023-12-10 19:14:37 -06:00
//Add a component to a menu using this macro. Follow-up with END at the end of it.
2023-12-10 20:14:32 -06:00
# define ADD(key,componentType) _AddComponent<componentType>(key,NEW componentType
2023-12-10 19:14:37 -06:00
# define END )
# define DEPTH ,
2023-12-09 01:58:46 -06:00
# define DEFAULT_DEPTH -999999
# define STARTING_DEPTH 999999
2023-10-07 15:47:26 -05:00
enum MenuType {
2023-12-10 19:14:37 -06:00
///////////////////////////////////////////////////////////
/*DO NOT REMOVE!!*/ ENUM_START , ///////////////////////////////
///////////////////////////////////////////////////////////
INVENTORY_CONSUMABLES ,
2023-10-20 22:49:12 -05:00
CLASS_INFO ,
CLASS_SELECTION ,
MAIN_MENU ,
2023-11-11 17:31:53 -06:00
OVERWORLD_LEVEL_SELECT ,
2023-11-14 23:20:13 -06:00
ITEM_LOADOUT ,
2023-11-19 15:57:18 -06:00
LEVEL_COMPLETE ,
2023-11-29 23:52:43 -06:00
OVERWORLD_MENU ,
CHARACTER_MENU ,
2023-12-10 19:14:37 -06:00
INVENTORY ,
2023-12-19 17:10:04 -06:00
MERCHANT ,
BUY_ITEM ,
SELL_ITEM ,
2023-12-27 17:00:52 -06:00
BLACKSMITH ,
2023-12-28 06:10:46 -06:00
CRAFT_ITEM ,
2023-12-28 21:48:12 -06:00
CRAFT_CONSUMABLE ,
2023-12-29 02:43:59 -06:00
CONSUMABLE_CRAFT_ITEM ,
2024-01-01 01:59:41 -06:00
SAVE_FILE_NAME ,
2024-01-01 07:48:57 -06:00
LOAD_GAME ,
2024-01-02 05:54:43 -06:00
USER_ID ,
2023-10-07 15:47:26 -05:00
///////////////////////////////////////////////////////////
/*DO NOT REMOVE!!*/ ENUM_END ////////////////////////////////
///////////////////////////////////////////////////////////
} ;
2023-09-28 21:13:04 +00:00
2023-11-13 21:26:34 -06:00
class Menu : public IAttributable {
2023-12-10 19:14:37 -06:00
static void InitializeConsumableInventoryWindow ( ) ;
static void InitializeClassInfoWindow ( ) ;
static void InitializeClassSelectionWindow ( ) ;
static void InitializeMainMenuWindow ( ) ;
static void InitializeOverworldMapLevelWindow ( ) ;
static void InitializeItemLoadoutWindow ( ) ;
static void InitializeLevelCompleteWindow ( ) ;
static void InitializeOverworldMenuWindow ( ) ;
static void InitializeCharacterMenuWindow ( ) ;
static void InitializeInventoryWindow ( ) ;
2023-12-19 17:10:04 -06:00
static void InitializeMerchantWindow ( ) ;
static void InitializeBuyItemWindow ( ) ;
static void InitializeSellItemWindow ( ) ;
2023-12-27 17:00:52 -06:00
static void InitializeBlacksmithCraftingWindow ( ) ;
2023-12-28 06:10:46 -06:00
static void InitializeCraftItemWindow ( ) ;
2023-12-28 21:48:12 -06:00
static void InitializeConsumableCraftingWindow ( ) ;
2023-12-29 02:43:59 -06:00
static void InitializeConsumableCraftItemWindow ( ) ;
2024-01-01 01:59:41 -06:00
static void InitializeSaveFileWindow ( ) ;
2024-01-01 07:48:57 -06:00
static void InitializeLoadGameWindow ( ) ;
2024-01-02 05:54:43 -06:00
static void InitializeUserIDWindow ( ) ;
2023-12-10 19:14:37 -06:00
2024-01-04 05:21:56 -06:00
friend class AiL ;
2023-10-18 17:51:08 +00:00
friend class ItemInfo ;
2023-12-23 21:15:08 -06:00
friend class EntityStats ;
2023-10-01 01:11:53 -05:00
2023-10-06 17:19:02 -05:00
float buttonHoldTime = 0 ;
2023-10-01 01:11:53 -05:00
vi2d selection = { - 1 , - 1 } ;
2023-10-17 01:21:00 -05:00
vi2d lastActiveMousePos = { } ;
2024-01-04 05:21:56 -06:00
int componentCount = 0 ;
2023-10-06 17:19:02 -05:00
MenuComponent * draggingComponent = nullptr ;
2023-12-14 04:18:05 -06:00
ViewPort window ;
2023-10-18 16:06:08 +00:00
static safemap < ITCategory , std : : vector < MenuComponent * > > inventoryListeners ; //All menu components that care about inventory updates subscribe to this list indirectly (See Menu::AddInventoryListener()).
2023-12-19 17:10:04 -06:00
static safemap < ITCategory , std : : vector < MenuComponent * > > merchantInventoryListeners ; //All menu components that care about merchant inventory updates subscribe to this list indirectly (See Menu::AddMerchantInventoryListener()).
2023-12-06 22:47:09 -06:00
static std : : vector < MenuComponent * > equipStatListeners ; //All menu components that care about stat/equip updates subscribe to this list indirectly (See Menu::AddStatListener()).
2023-12-29 01:00:42 -06:00
static std : : vector < MenuComponent * > chapterListeners ; //All menu components that care about story chapter updates subscribe to this list indirectly (See Menu::AddChapterListener()).
2023-09-29 00:03:20 -05:00
public :
2023-10-15 12:58:39 -05:00
//The constructor is private. Use CreateMenu() instead!
Menu ( ) = default ;
2023-10-22 23:19:47 -05:00
~ Menu ( ) ;
2023-12-10 19:14:37 -06:00
//DO NOT USE DIRECTLY! You should be utilizing the ADD macro for adding components.
template < class T >
2023-12-10 20:14:32 -06:00
T * _AddComponent ( std : : string componentKey , T * component , int depth = DEFAULT_DEPTH ) {
2023-12-10 19:14:37 -06:00
component - > parentMenu = type ;
if ( depth = = DEFAULT_DEPTH ) {
component - > depth = STARTING_DEPTH - componentCount ;
} else {
component - > depth = depth ;
}
if ( component - > selectable ) {
buttons . Unlock ( ) ;
if ( buttons . count ( int ( component - > rect . pos . y ) ) ) {
buttons . at ( int ( component - > rect . pos . y ) ) . push_back ( component ) ;
} else {
buttons [ int ( component - > rect . pos . y ) ] . push_back ( component ) ;
}
if ( component - > selectableViaKeyboard ) {
keyboardButtons . Unlock ( ) ;
if ( keyboardButtons . count ( int ( component - > rect . pos . y ) ) ) {
keyboardButtons . at ( int ( component - > rect . pos . y ) ) . push_back ( component ) ;
} else {
keyboardButtons [ int ( component - > rect . pos . y ) ] . push_back ( component ) ;
}
}
//We must lock the values before calling sort. Sort seems to try and create new accesses.
buttons . SetInitialized ( ) ;
keyboardButtons . SetInitialized ( ) ;
//We make an assumption that menu components are supposed to be in left-to-right order. Sometimes we may add things out-of-order, so this fixes the problem by sorting the items afterwards.
std : : sort ( buttons [ int ( component - > rect . pos . y ) ] . begin ( ) , buttons [ int ( component - > rect . pos . y ) ] . end ( ) , [ ] ( auto c1 , auto c2 ) {
return c1 - > GetPos ( ) . x < c2 - > GetPos ( ) . x ;
} ) ;
if ( keyboardButtons . count ( int ( component - > rect . pos . y ) ) ) { //Keyboard buttons may not necessarily contain this key...Let's be sure.
std : : sort ( keyboardButtons [ int ( component - > rect . pos . y ) ] . begin ( ) , keyboardButtons [ int ( component - > rect . pos . y ) ] . end ( ) , [ ] ( auto c1 , auto c2 ) {
return c1 - > GetPos ( ) . x < c2 - > GetPos ( ) . x ;
} ) ;
}
} else {
displayComponents . push_back ( component ) ;
}
RecalculateComponentCount ( ) ;
if ( components . count ( componentKey ) ) {
ERR ( " WARNING! Key " < < componentKey < < " for this sub-menu already exists! Key names must be unique! " )
}
component - > name = componentKey ;
components . Unlock ( ) ; //It's possible we can add a component later on, so we will make sure we remove the lock first.
components [ componentKey ] = component ;
components . SetInitialized ( ) ;
lastRegisteredComponent = componentKey ;
std : : erase_if ( Menu : : unhandledComponents , [ & ] ( auto b1 ) { return b1 = = component ; } ) ;
return component ;
}
2024-01-04 05:21:56 -06:00
void Update ( AiL * game ) ;
void Draw ( AiL * game ) ;
2023-12-19 17:10:04 -06:00
static void InitializeMenuListenerCategory ( const std : : string & category ) ;
2023-09-29 00:03:20 -05:00
static void InitializeMenus ( ) ;
2023-12-19 17:10:04 -06:00
static void LockInListeners ( ) ;
2023-11-11 17:31:53 -06:00
static void OpenMenu ( MenuType menu , bool cover = true ) ;
2023-10-22 21:56:09 -05:00
static void CloseMenu ( ) ;
2023-10-24 03:22:25 -05:00
static void CloseAllMenus ( ) ;
2023-11-11 00:54:50 -06:00
static void CleanupAllMenus ( ) ;
2023-09-29 00:03:20 -05:00
static std : : vector < Menu * > stack ;
2023-10-03 02:34:26 -05:00
static std : : string themeSelection ;
2023-10-03 04:09:42 -05:00
static safeunorderedmap < std : : string , Theme > themes ;
2023-12-10 20:14:32 -06:00
static std : : 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.
2023-10-06 15:00:27 -05:00
static const vf2d CENTERED ;
2023-11-11 00:54:50 -06:00
static bool IsMenuOpen ( ) ;
2024-01-03 04:59:47 -06:00
const MenuType GetType ( ) const ;
2023-10-07 19:06:56 -05:00
safemap < std : : string , MenuComponent * > components ; //A friendly way to interrogate any component we are interested in.
2023-10-22 23:19:47 -05:00
std : : vector < MenuComponent * > displayComponents ; //Components that are only for displaying purposes.
2023-10-11 19:50:12 -05:00
static std : : map < MenuType , Menu * > menus ;
vf2d pos ; //Specify the upper-left corner of the window. Using CENTERED will always put this where the upper-left corner would center the window.
vf2d size ; //Size in tiles (24x24), every menu will be tile-based
2023-10-17 05:35:19 -05:00
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
2023-10-07 15:47:26 -05:00
2023-10-15 14:59:35 -05:00
static Theme & GetCurrentTheme ( ) ;
2023-10-17 01:21:00 -05:00
bool UsingMouseNavigation ( ) ;
void SetMouseNavigation ( bool mouseNavigation ) ;
2023-12-19 17:10:04 -06:00
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.
2023-10-18 16:06:08 +00:00
static void AddInventoryListener ( MenuComponent * component , ITCategory category ) ; //Adds a component to be in a given listener category.
2023-12-19 17:10:04 -06:00
static void AddMerchantInventoryListener ( MenuComponent * component , ITCategory category ) ; //Adds a component to be in a given listener category.
2023-12-06 22:47:09 -06:00
static void AddEquipStatListener ( MenuComponent * component ) ; //Adds a component to be in an equip stat listener. Will receive updates whenever stats are updated via equips.
2023-12-29 01:00:42 -06:00
static void AddChapterListener ( MenuComponent * component ) ; //Adds a component to be in a chapter listener. Will receive updates anytime the chapter in-game changes.
2023-10-20 22:49:12 -05:00
vf2d center ( ) ;
2023-10-23 00:05:30 -05:00
//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.
static std : : pair < MenuType , std : : string > GetMemoryLeakReportInfo ( ) ;
2023-11-11 04:03:48 -06:00
virtual void Cleanup ( ) ;
2023-11-28 18:29:06 -06:00
static void DrawThemedWindow ( vf2d menuPos , vf2d size , Pixel renderColor = WHITE ) ;
2023-11-28 20:34:01 -06:00
//X (0-2), Y (0-2) for specific 9-patch tile (tiled version).
static Renderable & GetPatchPart ( int x , int y ) ;
2023-12-09 01:58:46 -06:00
void RecalculateComponentCount ( ) ;
2023-09-29 00:03:20 -05:00
private :
2023-10-15 12:58:39 -05:00
Menu ( vf2d pos , vf2d size ) ;
2023-10-23 00:05:30 -05:00
static MenuType lastMenuTypeCreated ;
static std : : string lastRegisteredComponent ;
2024-01-04 05:21:56 -06:00
void HoverMenuSelect ( AiL * game ) ;
void MenuSelect ( AiL * game ) ;
void CheckClickAndPerformMenuSelect ( AiL * game ) ;
2023-10-15 12:58:39 -05:00
//Mandatory before any menu operations! This creates and sets up the menu in memory.
static Menu * CreateMenu ( MenuType type , vf2d pos , vf2d size ) ;
2023-10-01 01:11:53 -05:00
2024-01-04 05:21:56 -06:00
void KeyboardButtonNavigation ( AiL * game , vf2d menuPos ) ;
static void DrawScaledWindowBackground ( AiL * game , vf2d menuPos , vf2d size , Pixel renderColor ) ;
static void DrawTiledWindowBackground ( AiL * game , vf2d menuPos , vf2d size , Pixel renderColor ) ;
static void DrawScaledWindowBorder ( AiL * game , vf2d menuPos , vf2d size , Pixel renderColor ) ;
static void DrawTiledWindowBorder ( AiL * game , vf2d menuPos , vf2d size , Pixel renderColor ) ;
2023-10-05 00:42:28 -05:00
2023-10-17 00:50:58 -05:00
//This triggers if we use a keyboard/controller input to try and select some off-screen menu item. We should ideally follow the menu cursor.
bool HandleOutsideDisabledButtonSelection ( MenuComponent * disabledButton ) ;
2023-10-05 00:42:28 -05:00
Pixel GetRenderColor ( ) ;
2023-12-10 19:14:37 -06:00
MenuType type ;
2023-12-09 01:58:46 -06:00
2023-10-17 01:21:00 -05:00
static bool MOUSE_NAVIGATION ;
2023-11-29 23:52:43 -06:00
bool cover ; //A black cover for when a menu pops up to fade out the stuff behind it.
2023-09-28 21:13:04 +00:00
} ;
2023-11-27 00:01:19 -06:00
template < typename T >
T * Component ( MenuType menu , std : : string componentName ) {
2023-12-27 20:40:01 -06:00
T * tmp = DYNAMIC_CAST < T * > ( Menu : : menus [ menu ] - > components [ componentName ] ) ;
return tmp ;
2023-11-27 00:01:19 -06:00
}
2023-10-07 15:47:26 -05:00
struct MenuFuncData {
Menu & menu ;
2024-01-04 05:21:56 -06:00
AiL * const game ;
2024-01-03 04:59:47 -06:00
MenuComponent * const component ;
ScrollableWindowComponent * const parentComponent = nullptr ;
2024-01-04 05:21:56 -06:00
MenuFuncData ( Menu & menu , AiL * const game , MenuComponent * const component , ScrollableWindowComponent * const parentComponent = nullptr ) ;
2023-10-07 15:47:26 -05:00
} ;
2023-12-05 23:07:49 -06:00
using MenuFunc = std : : function < bool ( MenuFuncData ) > ;