# pragma region License
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . 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 © 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include "Menu.h"
# include "MenuComponent.h"
# include "PopupMenuLabel.h"
# include "StatLabel.h"
# include "CharacterRotatingDisplay.h"
# include "AdventuresInLestoria.h"
# include "ClassInfo.h"
# include "MenuItemItemButton.h"
# include "EquipSlotButton.h"
# include "Item.h"
# include "ScrollableWindowComponent.h"
# include "RowItemDisplay.h"
# include "SoundEffect.h"
INCLUDE_game
INCLUDE_GFX
void Menu : : InitializeCharacterMenuWindow ( ) {
static bool equipmentWindowOpened = false ;
vf2d windowSize = game - > GetScreenSize ( ) - vf2d { 52 , 52 } ;
Menu * characterMenuWindow = CreateMenu ( CHARACTER_MENU , CENTERED , windowSize ) ;
characterMenuWindow - > ADD ( " Character Label " , MenuLabel ) ( geom2d : : rect < float > { { 0 , - 4 } , { float ( windowSize . x ) - 1 , 24 } } , " Character " , 2 , ComponentAttr : : SHADOW | ComponentAttr : : OUTLINE | ComponentAttr : : BACKGROUND ) END ;
characterMenuWindow - > ADD ( " Equip Slot Outline " , MenuComponent ) ( geom2d : : rect < float > { { 0 , 28 } , { 120 , windowSize . y - 37 } } , " " , DO_NOTHING , ButtonAttr : : UNSELECTABLE ) END ;
characterMenuWindow - > ADD ( " Character Rotating Display " , CharacterRotatingDisplay ) ( geom2d : : rect < float > { { 135 , 28 } , { 90 , windowSize . y - 48 } } , GFX [ classutils : : GetClassInfo ( game - > GetPlayer ( ) - > GetClassName ( ) ) . classFullImgName ] . Decal ( ) ) END ;
const static std : : array < std : : string , 7 > displayAttrs {
" Health " ,
" Attack " ,
" Defense " ,
" Move Spd % " ,
" CDR " ,
" Crit Rate " ,
" Crit Dmg " ,
} ;
characterMenuWindow - > ADD ( " Equip Selection Outline " , MenuComponent ) ( geom2d : : rect < float > { { 123 , 28 } , { 120 , windowSize . y - 37 } } , " " , DO_NOTHING , ButtonAttr : : UNSELECTABLE ) END
- > Enable ( false ) ;
characterMenuWindow - > ADD ( " Equip List " , ScrollableWindowComponent ) ( geom2d : : rect < float > { { 123 , 28 } , { 120 , windowSize . y - 37 - 24 } } ) DEPTH - 1 END
- > Enable ( false ) ;
characterMenuWindow - > ADD ( " Equip Selection Bottom Outline " , MenuComponent ) ( geom2d : : rect < float > { { 123 , 28 + ( windowSize . y - 37 - 24 ) } , { 120 , 24 } } , " " , DO_NOTHING , ButtonAttr : : UNSELECTABLE ) END
- > Enable ( false ) ;
auto equipSelectionSelectButton = characterMenuWindow - > ADD ( " Equip Selection Select Button " , MenuComponent ) ( geom2d : : rect < float > { { 123 + 12 , 28 + ( windowSize . y - 37 - 24 ) + 6 } , { 96 , 12 } } , " Select " ,
[ ] ( MenuFuncData data ) {
Component < MenuComponent > ( data . component . lock ( ) - > parentMenu , " Equip Selection Outline " ) - > Enable ( false ) ;
Component < ScrollableWindowComponent > ( data . component . lock ( ) - > parentMenu , " Equip List " ) - > Enable ( false ) ;
Component < MenuComponent > ( data . component . lock ( ) - > parentMenu , " Equip Selection Bottom Outline " ) - > Enable ( false ) ;
Component < MenuComponent > ( data . component . lock ( ) - > parentMenu , " Equip Selection Select Button " ) - > Enable ( false ) ;
Component < CharacterRotatingDisplay > ( data . component . lock ( ) - > parentMenu , " Character Rotating Display " ) - > Enable ( true ) ;
for ( int counter = 0 ; const std : : string & attribute : displayAttrs ) {
std : : weak_ptr < StatLabel > statDisplayLabel = Component < StatLabel > ( CHARACTER_MENU , " Attribute " + std : : string ( ItemAttribute : : Get ( attribute ) . Name ( ) ) + " Label " ) ;
statDisplayLabel . lock ( ) - > SetStatChangeAmt ( 0 ) ;
}
equipmentWindowOpened = false ;
return true ;
} ) DEPTH 0 END ;
equipSelectionSelectButton - > Enable ( false ) ;
const static auto GetLabelText = [ ] ( ItemAttribute attribute ) {
std : : string attrStr = std : : string ( attribute . Name ( ) ) + " : \n " ;
attrStr + = std : : to_string ( game - > GetPlayer ( ) - > GetStat ( attribute ) ) ;
if ( attribute . DisplayAsPercent ( ) ) {
attrStr + = " % " ;
}
return attrStr ;
} ;
int equipSlot = 1 ;
for ( int i = 0 ; i < 8 ; i + + ) {
float x = 31 + ( i % 2 ) * 33 ;
float y = 24 + ( i / 2 ) * 28 ;
float labelX = 0 + ( i % 2 ) * 88 ;
float labelY = 24 + ( i / 2 ) * 28 + 36 ;
if ( i < 6 ) {
y - = 8 ;
labelY - = 8 ;
}
const static std : : array < std : : string , 8 > slotNames { " Helmet " , " Weapon " , " Armor " , " Gloves " , " Pants " , " Shoes " , " Ring 1 " , " Ring 2 " } ;
EquipSlot slot = EquipSlot ( equipSlot ) ;
auto equipmentSlot = characterMenuWindow - > ADD ( " Equip Slot " + slotNames [ i ] , EquipSlotButton ) ( geom2d : : rect < float > { { x , y + 28 } , { 24 , 24 } } , slot , MenuType : : ENUM_END ,
[ & ] ( MenuFuncData data ) {
EquipSlot slot = EquipSlot ( data . component . lock ( ) - > I ( Attribute : : EQUIP_TYPE ) ) ;
const std : : vector < std : : shared_ptr < Item > > & equips = Inventory : : get ( " Equipment " ) ;
const std : : vector < std : : shared_ptr < Item > > & accessories = Inventory : : get ( " Accessories " ) ;
std : : vector < std : : weak_ptr < Item > > availableEquipment ;
std : : copy_if ( equips . begin ( ) , equips . end ( ) , std : : back_inserter ( availableEquipment ) , [ & ] ( const std : : shared_ptr < Item > it ) {
return it - > GetEquipSlot ( ) & slot ;
} ) ;
std : : copy_if ( accessories . begin ( ) , accessories . end ( ) , std : : back_inserter ( availableEquipment ) , [ & ] ( const std : : shared_ptr < Item > it ) {
return it - > GetEquipSlot ( ) & slot ;
} ) ;
std : : shared_ptr < ScrollableWindowComponent > equipList = Component < ScrollableWindowComponent > ( data . component . lock ( ) - > parentMenu , " Equip List " ) ;
equipList - > RemoveAllComponents ( ) ;
for ( int counter = 0 ; const std : : weak_ptr < Item > it : availableEquipment ) {
const static auto OppositeRingSlotDoesNotMatchCurrentEquip = [ ] ( std : : weak_ptr < RowItemDisplay > comp ) {
EquipSlot slot = EquipSlot ( comp . lock ( ) - > I ( Attribute : : EQUIP_TYPE ) ) ;
std : : weak_ptr < Item > otherItem ;
if ( slot & EquipSlot : : RING1 ) otherItem = Inventory : : GetEquip ( EquipSlot : : RING2 ) ;
else
if ( slot & EquipSlot : : RING2 ) otherItem = Inventory : : GetEquip ( EquipSlot : : RING1 ) ;
return ISBLANK ( otherItem ) | | ( & * comp . lock ( ) - > GetItem ( ) . lock ( ) ! = & * otherItem . lock ( ) ) ;
} ;
auto equip = equipList - > ADD ( " Equip Item " + std : : to_string ( counter ) , RowItemDisplay ) ( geom2d : : rect < float > { { 2 , 2 + counter * 29.f } , { 120 - 15 , 28 } } , it ,
[ ] ( MenuFuncData data ) {
std : : weak_ptr < RowItemDisplay > comp = DYNAMIC_POINTER_CAST < RowItemDisplay > ( data . component . lock ( ) ) ;
if ( ! comp . expired ( ) ) {
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.
Inventory : : EquipItem ( comp . lock ( ) - > GetItem ( ) , EquipSlot ( comp . lock ( ) - > I ( Attribute : : EQUIP_TYPE ) ) ) ;
SoundEffect : : PlaySFX ( comp . lock ( ) - > GetItem ( ) . lock ( ) - > UseSound ( ) , SoundEffect : : CENTERED ) ;
for ( std : : weak_ptr < MenuComponent > button : DYNAMIC_POINTER_CAST < ScrollableWindowComponent > ( data . parentComponent . lock ( ) ) - > GetComponents ( ) ) {
std : : weak_ptr < RowItemDisplay > comp = DYNAMIC_POINTER_CAST < RowItemDisplay > ( button . lock ( ) ) ;
comp . lock ( ) - > SetSelected ( false ) ;
}
comp . lock ( ) - > SetSelected ( true ) ;
for ( int counter = 0 ; const std : : string & attribute : displayAttrs ) {
std : : shared_ptr < StatLabel > statDisplayLabel = Component < StatLabel > ( CHARACTER_MENU , " Attribute " + std : : string ( ItemAttribute : : Get ( attribute ) . Name ( ) ) + " Label " ) ;
statDisplayLabel - > SetStatChangeAmt ( 0 ) ;
}
std : : shared_ptr < MenuItemItemButton > equipButton = Component < MenuItemItemButton > ( CHARACTER_MENU , " Equip Slot " + slotNames [ data . parentComponent . lock ( ) - > I ( A : : INDEXED_THEME ) ] ) ;
equipButton - > SetItem ( comp . lock ( ) - > GetItem ( ) , false ) ;
}
} else {
ERR ( " WARNING! Attempting to cast a button that isn't a RowItemDisplay! " ) ;
}
return true ;
} , " Item Name " , " Item Description " ) END ;
equip - > SetHoverFunc (
[ & ] ( MenuFuncData data ) {
std : : weak_ptr < RowItemDisplay > button = DYNAMIC_POINTER_CAST < RowItemDisplay > ( data . component . lock ( ) ) ;
if ( ! button . expired ( ) ) {
const std : : weak_ptr < Item > buttonItem = button . lock ( ) - > GetItem ( ) ;
std : : vector < float > statsBeforeEquip ;
EquipSlot slot = EquipSlot ( button . lock ( ) - > I ( Attribute : : EQUIP_TYPE ) ) ;
for ( const std : : string & attribute : displayAttrs ) {
statsBeforeEquip . push_back ( game - > GetPlayer ( ) - > GetStat ( attribute ) ) ;
}
std : : weak_ptr < Item > equippedItem = Inventory : : GetEquip ( slot ) ;
std : : weak_ptr < Item > otherItem ;
if ( slot = = EquipSlot : : RING1 ) otherItem = Inventory : : GetEquip ( EquipSlot : : RING2 ) ;
else
if ( slot = = EquipSlot : : RING2 ) otherItem = Inventory : : GetEquip ( EquipSlot : : RING1 ) ;
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.
Inventory : : EquipItem ( buttonItem , slot ) ;
for ( int counter = 0 ; const std : : string & attribute : displayAttrs ) {
std : : weak_ptr < StatLabel > statDisplayLabel = Component < StatLabel > ( CHARACTER_MENU , " Attribute " + std : : string ( ItemAttribute : : Get ( attribute ) . Name ( ) ) + " Label " ) ;
int statChangeAmt = game - > GetPlayer ( ) - > GetStat ( attribute ) - statsBeforeEquip [ counter ] ;
statDisplayLabel . lock ( ) - > SetStatChangeAmt ( statChangeAmt ) ;
counter + + ;
}
Inventory : : UnequipItem ( slot ) ;
if ( ! ISBLANK ( equippedItem ) ) {
Inventory : : EquipItem ( equippedItem , slot ) ;
}
if ( ! ISBLANK ( otherItem ) ) {
if ( slot = = EquipSlot : : RING1 ) Inventory : : EquipItem ( otherItem , EquipSlot : : RING2 ) ;
else
if ( slot = = EquipSlot : : RING2 ) Inventory : : EquipItem ( otherItem , EquipSlot : : RING1 ) ;
}
}
} else {
ERR ( " WARNING! Attempting to cast a button that isn't a RowItemDisplay! " ) ;
}
return true ;
} ) ;
equip - > SetMouseOutFunc (
[ ] ( MenuFuncData data ) {
for ( int counter = 0 ; const std : : string & attribute : displayAttrs ) {
std : : weak_ptr < StatLabel > statDisplayLabel = Component < StatLabel > ( CHARACTER_MENU , " Attribute " + std : : string ( ItemAttribute : : Get ( attribute ) . Name ( ) ) + " Label " ) ;
statDisplayLabel . lock ( ) - > SetStatChangeAmt ( 0 ) ;
counter + + ;
}
return true ;
} ) ;
equip - > SetShowQuantity ( false ) ;
equip - > SetSelectionType ( SelectionType : : NONE ) ;
equip - > I ( Attribute : : EQUIP_TYPE ) = int ( slot ) ;
if ( Inventory : : GetEquip ( slot ) = = it ) {
equip - > SetSelected ( true ) ;
}
equip - > SetCompactDescriptions ( NON_COMPACT ) ;
counter + + ;
}
equipList - > I ( Attribute : : INDEXED_THEME ) = data . component . lock ( ) - > I ( Attribute : : INDEXED_THEME ) ;
Component < MenuComponent > ( data . component . lock ( ) - > parentMenu , " Equip Selection Outline " ) - > Enable ( true ) ;
equipList - > Enable ( true ) ;
Component < MenuComponent > ( data . component . lock ( ) - > parentMenu , " Equip Selection Bottom Outline " ) - > Enable ( true ) ;
Component < MenuComponent > ( data . component . lock ( ) - > parentMenu , " Equip Selection Select Button " ) - > Enable ( true ) ;
Component < CharacterRotatingDisplay > ( data . component . lock ( ) - > parentMenu , " Character Rotating Display " ) - > Enable ( false ) ;
equipmentWindowOpened = true ;
return true ;
} , [ ] ( MenuFuncData data ) { //On Mouse Hover
EquipSlot slot = DYNAMIC_POINTER_CAST < EquipSlotButton > ( data . component . lock ( ) ) - > GetSlot ( ) ;
const std : : weak_ptr < Item > equip = Inventory : : GetEquip ( slot ) ;
if ( ! ISBLANK ( equip ) ) {
Component < CharacterRotatingDisplay > ( data . component . lock ( ) - > parentMenu , " Character Rotating Display " ) - > Enable ( false ) ;
}
return true ;
} , [ ] ( MenuFuncData data ) { //On Mouse Out
if ( ! equipmentWindowOpened ) {
Component < MenuLabel > ( data . component . lock ( ) - > parentMenu , " Item Equip Description " ) - > SetLabel ( " " ) ;
Component < MenuLabel > ( data . component . lock ( ) - > parentMenu , " Item Equip Name " ) - > Enable ( false ) ;
Component < MenuLabel > ( data . component . lock ( ) - > parentMenu , " Item Equip Description " ) - > Enable ( false ) ;
Component < CharacterRotatingDisplay > ( data . component . lock ( ) - > parentMenu , " Character Rotating Display " ) - > Enable ( true ) ;
}
return true ;
} , " Item Equip Name " , " Item Equip Description " ) END ;
equipmentSlot - > I ( Attribute : : EQUIP_TYPE ) = int ( slot ) ;
equipmentSlot - > I ( Attribute : : INDEXED_THEME ) = i ;
equipmentSlot - > SetShowQuantity ( false ) ;
equipmentSlot - > SetCompactDescriptions ( false ) ;
equipSlot < < = 1 ;
characterMenuWindow - > ADD ( " Equip Label " + slotNames [ i ] , PopupMenuLabel ) ( geom2d : : rect < float > { { labelX , labelY } , { 24 , 16 } } , slotNames [ i ] , vf2d { 0.5 , 1.f } , ComponentAttr : : SHADOW ) END ;
Menu : : AddEquipStatListener ( equipmentSlot ) ;
}
characterMenuWindow - > ADD ( " Stat Display Outline " , MenuComponent ) ( geom2d : : rect < float > { { 245 , 28 } , { 62 , windowSize . y - 37 } } , " " , DO_NOTHING , ButtonAttr : : UNSELECTABLE ) END ;
int yOffset = 0 ;
for ( const std : : string & attribute : displayAttrs ) {
std : : string attrStr = GetLabelText ( ItemAttribute : : Get ( attribute ) ) ;
auto attrLabel = characterMenuWindow - > ADD ( " Attribute " + std : : string ( ItemAttribute : : Get ( attribute ) . Name ( ) ) + " Label " , StatLabel ) ( geom2d : : rect < float > { { 245 , 28 + 2 + float ( yOffset ) } , { 62 , 18 } } , ItemAttribute : : Get ( attribute ) , 1 , ComponentAttr : : SHADOW | ComponentAttr : : LEFT_ALIGN ) END ;
Menu : : AddEquipStatListener ( attrLabel ) ;
yOffset + = 20 ;
}
characterMenuWindow - > ADD ( " Back button " , MenuComponent ) ( geom2d : : rect < float > { { windowSize . x / 2 - 64 , windowSize . y } , { 128 , 12 } } , " Back " , [ ] ( MenuFuncData data ) { Menu : : stack . pop_back ( ) ; return true ; } ) END ;
auto itemNameDisplay = characterMenuWindow - > ADD ( " Item Name " , MenuLabel ) ( geom2d : : rect < float > { { 0 , 28 } , { 120 , 12 } } , " " , 1 , ComponentAttr : : BACKGROUND | ComponentAttr : : LEFT_ALIGN | ComponentAttr : : OUTLINE | ComponentAttr : : SHADOW | ComponentAttr : : FIT_TO_LABEL ) END ;
auto itemDescriptionDisplay = characterMenuWindow - > ADD ( " Item Description " , MenuLabel ) ( geom2d : : rect < float > { { 0 , 40 } , { 120 , windowSize . y - 49 } } , " " , 1 , ComponentAttr : : BACKGROUND | ComponentAttr : : LEFT_ALIGN | ComponentAttr : : OUTLINE | ComponentAttr : : SHADOW ) END ;
auto itemEquipNameDisplay = characterMenuWindow - > ADD ( " Item Equip Name " , MenuLabel ) ( geom2d : : rect < float > { { 123 , 28 } , { 120 , 12 } } , " " , 1 , ComponentAttr : : BACKGROUND | ComponentAttr : : LEFT_ALIGN | ComponentAttr : : OUTLINE | ComponentAttr : : SHADOW | ComponentAttr : : FIT_TO_LABEL ) END ;
auto itemEquipDescriptionDisplay = characterMenuWindow - > ADD ( " Item Equip Description " , MenuLabel ) ( geom2d : : rect < float > { { 123 , 40 } , { 120 , windowSize . y - 49 } } , " " , 1 , ComponentAttr : : BACKGROUND | ComponentAttr : : LEFT_ALIGN | ComponentAttr : : OUTLINE | ComponentAttr : : SHADOW ) END ;
itemNameDisplay - > Enable ( false ) ;
itemDescriptionDisplay - > Enable ( false ) ;
itemEquipNameDisplay - > Enable ( false ) ;
itemEquipDescriptionDisplay - > Enable ( false ) ;
}