Skip to content

Commit

Permalink
Merge pull request #75624 from maciekk/spell_list_sort
Browse files Browse the repository at this point in the history
Improve invlet management for Magiclysm spell list.
  • Loading branch information
Maleclypse authored Aug 29, 2024
2 parents 95637ed + b794dc5 commit cbcdfa4
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 36 deletions.
7 changes: 7 additions & 0 deletions src/inventory.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ class invlet_wrapper : private std::string
explicit invlet_wrapper( const char *chars ) : std::string( chars ) { }

bool valid( int invlet ) const;

// Get ordinal number (first, second, third, ...) of invlet.
// Informs sorting order.
int ordinal( int invlet ) const {
return this->find( invlet );
}

std::string get_allowed_chars() const {
return *this;
}
Expand Down
99 changes: 64 additions & 35 deletions src/magic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <set>
#include <utility>
Expand Down Expand Up @@ -2399,7 +2400,7 @@ class spellcasting_callback : public uilist_callback
void display_spell_info( size_t index );
public:
// invlets reserved for special functions
const std::set<int> reserved_invlets{ 'I', '=', '*' };
static const std::set<int> reserved_invlets;
bool casting_ignore;

spellcasting_callback( std::vector<spell *> &spells,
Expand All @@ -2415,8 +2416,11 @@ class spellcasting_callback : public uilist_callback
int invlet = 0;
invlet = popup_getkey( _( "Choose a new hotkey for this spell." ) );
if( inv_chars.valid( invlet ) ) {
const bool invlet_set =
get_player_character().magic->set_invlet( known_spells[entnum]->id(), invlet, reserved_invlets );
std::set<int> used_invlets{ spellcasting_callback::reserved_invlets };
get_player_character().magic->update_used_invlets( used_invlets );
const bool invlet_set = get_player_character().magic->set_invlet(
known_spells[entnum]->id(), invlet, used_invlets );
// TODO: if key already in use, have spells swap invlets?
if( !invlet_set ) {
popup( _( "Hotkey already used." ) );
} else {
Expand Down Expand Up @@ -2462,6 +2466,8 @@ class spellcasting_callback : public uilist_callback
}
};

const std::set<int> spellcasting_callback::reserved_invlets { 'I', '=', '*' };

bool spell::casting_time_encumbered( const Character &guy ) const
{
int encumb = 0;
Expand Down Expand Up @@ -2749,14 +2755,23 @@ bool known_magic::set_invlet( const spell_id &sp, int invlet, const std::set<int
return false;
}
invlets[sp] = invlet;
// TODO: we should really update used_invlets too, to avoid inconsistency
return true;
}

void known_magic::rem_invlet( const spell_id &sp )
{
// TODO: ... except that rem_invlet cannot update used_invlets (not passed in)
invlets.erase( sp );
}

void known_magic::update_used_invlets( std::set<int> &used_invlets )
{
for( const std::pair<const spell_id, int> &invlet_pair : invlets ) {
used_invlets.emplace( invlet_pair.second );
}
}

void known_magic::toggle_favorite( const spell_id &sp )
{
if( favorites.count( sp ) > 0 ) {
Expand All @@ -2777,41 +2792,57 @@ int known_magic::get_invlet( const spell_id &sp, std::set<int> &used_invlets )
if( found != invlets.end() ) {
return found->second;
}
for( const std::pair<const spell_id, int> &invlet_pair : invlets ) {
used_invlets.emplace( invlet_pair.second );
}
for( int i = 'a'; i <= 'z'; i++ ) {
if( used_invlets.count( i ) == 0 ) {
used_invlets.emplace( i );
return i;
}
}
for( int i = 'A'; i <= 'Z'; i++ ) {
if( used_invlets.count( i ) == 0 ) {
used_invlets.emplace( i );
return i;
}
}
for( int i = '!'; i <= '-'; i++ ) {
if( used_invlets.count( i ) == 0 ) {
used_invlets.emplace( i );
return i;
update_used_invlets( used_invlets );
// For spells without an invlet, assign first available one.
// Assignment is "sticky" (permanent), to avoid invlets getting scrambled
// when spells are added or subtracted.
// TODO: respect "Auto inventory letters" option?
for( char &ch : inv_chars.get_allowed_chars() ) {
int invlet = static_cast<int>( static_cast<unsigned char>( ch ) );
if( set_invlet( sp, invlet, used_invlets ) ) {
used_invlets.emplace( invlet );
return invlet;
}
}
return 0;
}

int known_magic::select_spell( Character &guy )
{
std::vector<spell *> known_spells = get_spells();
std::vector<spell *> known_spells_sorted = get_spells();

std::set<int> used_invlets{ spellcasting_callback::reserved_invlets };

// Sort the spell lists by 3 dimensions.
sort( known_spells_sorted.begin(), known_spells_sorted.end(),
[&guy, &used_invlets, this]( spell * left, spell * right ) -> int {
const bool l_fav = guy.magic->is_favorite( left->id() );
const bool r_fav = guy.magic->is_favorite( right->id() );
// 1. Favorite spells before non-favorite
if( l_fav != r_fav )
{
return l_fav > r_fav;
}
const int l_invlet = get_invlet( left->id(), used_invlets );
const int r_invlet = get_invlet( right->id(), used_invlets );
// 2. By invlet, if present (but in allowed_chars order; e.g.,
// lower-case first)
if( l_invlet != r_invlet )
{
return inv_chars.ordinal( l_invlet ) < inv_chars.ordinal( r_invlet );
}
// 3. By spell name
return strcmp( left->name().c_str(), right->name().c_str() );
} );

uilist spell_menu;
spell_menu.desired_bounds = {
-1.0,
-1.0,
std::max( 80, TERMX * 3 / 8 ) *ImGui::CalcTextSize( "X" ).x,
clamp( static_cast<int>( known_spells.size() ), 24, TERMY * 9 / 10 ) *ImGui::GetTextLineHeightWithSpacing(),
clamp( static_cast<int>( known_spells_sorted.size() ), 24, TERMY * 9 / 10 ) *ImGui::GetTextLineHeightWithSpacing(),
};

spell_menu.title = _( "Choose a Spell" );
spell_menu.input_category = "SPELL_MENU";
spell_menu.additional_actions.emplace_back( "CHOOSE_INVLET", translation() );
Expand All @@ -2820,13 +2851,13 @@ int known_magic::select_spell( Character &guy )
spell_menu.additional_actions.emplace_back( "SCROLL_DOWN_SPELL_MENU", translation() );
spell_menu.additional_actions.emplace_back( "SCROLL_FAVORITE", translation() );
spell_menu.hilight_disabled = true;
spellcasting_callback cb( known_spells, casting_ignore );
spellcasting_callback cb( known_spells_sorted, casting_ignore );
spell_menu.callback = &cb;
spell_menu.add_category( "all", _( "All" ) );
spell_menu.add_category( "favorites", _( "Favorites" ) );

std::vector<std::pair<std::string, std::string>> categories;
for( const spell *s : known_spells ) {
for( const spell *s : known_spells_sorted ) {
if( s->can_cast( guy ) && ( s->spell_class().is_valid() || s->spell_class() == trait_NONE ) ) {
const std::string spell_class_name = s->spell_class() == trait_NONE ? _( "Classless" ) :
s->spell_class().obj().name();
Expand All @@ -2843,30 +2874,28 @@ int known_magic::select_spell( Character &guy )
spell_menu.add_category( cat.first, cat.second );
}

spell_menu.set_category_filter( [&guy, known_spells]( const uilist_entry & entry,
spell_menu.set_category_filter( [&guy, known_spells_sorted]( const uilist_entry & entry,
const std::string & key )->bool {
if( key == "all" )
{
return true;
} else if( key == "favorites" )
{
return guy.magic->is_favorite( known_spells[entry.retval]->id() );
return guy.magic->is_favorite( known_spells_sorted[entry.retval]->id() );
}
return ( known_spells[entry.retval]->spell_class().is_valid() || known_spells[entry.retval]->spell_class() == trait_NONE ) && known_spells[entry.retval]->spell_class().str() == key;
return ( known_spells_sorted[entry.retval]->spell_class().is_valid() || known_spells_sorted[entry.retval]->spell_class() == trait_NONE ) && known_spells_sorted[entry.retval]->spell_class().str() == key;
} );
if( !favorites.empty() ) {
spell_menu.set_category( "favorites" );
} else {
spell_menu.set_category( "all" );
}

std::set<int> used_invlets{ cb.reserved_invlets };

for( size_t i = 0; i < known_spells.size(); i++ ) {
spell_menu.addentry( static_cast<int>( i ), known_spells[i]->can_cast( guy ),
get_invlet( known_spells[i]->id(), used_invlets ), known_spells[i]->name() );
for( size_t i = 0; i < known_spells_sorted.size(); i++ ) {
spell_menu.addentry( static_cast<int>( i ), known_spells_sorted[i]->can_cast( guy ),
get_invlet( known_spells_sorted[i]->id(), used_invlets ), known_spells_sorted[i]->name() );
}
reflesh_favorite( &spell_menu, known_spells );
reflesh_favorite( &spell_menu, known_spells_sorted );

spell_menu.query( true, -1, true );

Expand Down
4 changes: 3 additions & 1 deletion src/magic.h
Original file line number Diff line number Diff line change
Expand Up @@ -725,13 +725,15 @@ class known_magic
// returns false if invlet is already used
bool set_invlet( const spell_id &sp, int invlet, const std::set<int> &used_invlets );
void rem_invlet( const spell_id &sp );
// returns which invlets are already in use
void update_used_invlets( std::set<int> &used_invlets );

void toggle_favorite( const spell_id &sp );
bool is_favorite( const spell_id &sp );
private:
// gets length of longest spell name
int get_spellname_max_width();
// gets invlet if assigned, or -1 if not
// gets invlet if assigned, or 0 if not
int get_invlet( const spell_id &sp, std::set<int> &used_invlets );
};

Expand Down

0 comments on commit cbcdfa4

Please sign in to comment.