Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mods can add categories to the help menu #78126

Merged
merged 9 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions data/help/texts.json → data/core/help.json

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions doc/HELP_MENU.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Help Menu

The help menu consists of scrollable categorised help pages that would ideally explain everything a new survivor needs to know, as well as any information the game can't convey clearly in an immersive way.

## Categories

Each category is made up of a json object which for mods can be placed anywhere but for vanilla should be placed in `/data/core`.

| Field | Description |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"type"` | (required) must be `"help"` |
| `"order"` | (required) integer, where this category should appear in the list relative to others from the same source, with 0 being first. Must be unique per source (Every mod can start from 0). Each mods categories are appended to the end of the list per their load order. |
| `"name"` | (required) string, name of the category, can use [color tags](COLOR.md#Color tags) |
| `"messages"` | (required) array of strings, each string respresents a new line seperated paragraph containing information for this category, can use [color tags](COLOR.md#Color tags) and [keybind tags](#Keybind tags). Seperated mainly for ease of editing the json as `\n` lets you add newlines within strings |

### Keybind tags

The keybind tags here use a different syntax than elsewhere for now.
Keybind tags take the form <press_keybind> where keybind corresponds to a keybind id, most of which are found in [keybindings.json](./data/raw/keybindings.json) and are automatically colored light blue.

### Special tags

`<DRAW_NOTE_COLORS>` and `<HELP_DRAW_DIRECTIONS>` are special hardcoded tags that can be found in [help.cpp](./src/help.cpp)
`<DRAW_NOTE_COLORS>` displays a list of all the color abbreviations to be used with overmap notes.
`<HELP_DRAW_DIRECTIONS>` displays the hjkl and numpad movement directions in a nicely rendered way. The movement directions have 3 binds by default with the above keybind tags not letting you specify which to use so unhardcoding it would result in a messier drawing.

### Example

```json
{
"type": "help",
"order": 0,
"name": "First Category Name",
"messages": [ "A useful tip.", "<color_red>Some scary warning.</color>", "A list of three keybinds.\n<press_pause> lets you pass one second.\n<press_wait> lets you wait for longer.\n<press_sleep> lets you sleep." ]
},
```
179 changes: 101 additions & 78 deletions src/help.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@
#include <numeric>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

#include "action.h"
#include "cata_utility.h"
#include "catacharset.h"
#include "color.h"
#include "cursesdef.h"
#include "debug.h"
#include "input_context.h"
#include "json_error.h"
#include "output.h"
#include "path_info.h"
#include "point.h"
Expand All @@ -25,34 +24,49 @@
#include "translations.h"
#include "ui_manager.h"

class JsonObject;

help &get_help()
{
static help single_instance;
return single_instance;
}

void help::load()
void help::load( const JsonObject &jo, const std::string &src )
{
read_from_file_optional_json( PATH_INFO::help(), [&]( const JsonValue & jsin ) {
deserialize( jsin );
} );
get_help().load_object( jo, src );
}

void help::deserialize( const JsonArray &ja )
void help::reset()
{
for( JsonObject jo : ja ) {
if( jo.get_string( "type" ) != "help" ) {
debugmsg( "object with type other than \"type\" found in help text file" );
continue;
}

std::vector<translation> messages;
jo.read( "messages", messages );
get_help().reset_instance();
}

translation name;
jo.read( "name", name );
void help::reset_instance()
{
current_order_start = 0;
current_src = "";
help_texts.clear();
}

help_texts[ jo.get_int( "order" ) ] = std::make_pair( name, messages );
void help::load_object( const JsonObject &jo, const std::string &src )
{
if( src == "dda" ) {
jo.throw_error( string_format( "Vanilla help must be located in %s",
PATH_INFO::jsondir().generic_u8string() ) );
}
if( src != current_src ) {
current_order_start = help_texts.empty() ? 0 : help_texts.crbegin()->first + 1;
current_src = src;
}
std::vector<translation> messages;
jo.read( "messages", messages );

translation name;
jo.read( "name", name );
const int modified_order = jo.get_int( "order" ) + current_order_start;
if( !help_texts.try_emplace( modified_order, std::make_pair( name, messages ) ).second ) {
jo.throw_error_at( "order", "\"order\" must be unique (per src)" );
}
}

Expand Down Expand Up @@ -88,7 +102,7 @@ std::string help::get_dir_grid()
}

std::map<int, inclusive_rectangle<point>> help::draw_menu( const catacurses::window &win,
int selected ) const
int selected, std::map<int, input_event> &hotkeys ) const
{
std::map<int, inclusive_rectangle<point>> opt_map;

Expand All @@ -102,15 +116,22 @@ std::map<int, inclusive_rectangle<point>> help::draw_menu( const catacurses::win
int second_column = divide_round_up( getmaxx( win ), 2 );
size_t i = 0;
for( const auto &text : help_texts ) {
const std::string cat_name = text.second.first.translated();
const int cat_width = utf8_width( remove_color_tags( shortcut_text( c_white, cat_name ) ) );
std::string cat_name;
auto hotkey_it = hotkeys.find( text.first );
if( hotkey_it != hotkeys.end() ) {
cat_name = colorize( hotkey_it->second.short_description(),
selected == text.first ? hilite( c_light_blue ) : c_light_blue );
cat_name += ": ";
}
cat_name += text.second.first.translated();
const int cat_width = utf8_width( remove_color_tags( cat_name ) );
if( i < half_size ) {
second_column = std::max( second_column, cat_width + 4 );
}

const point sc_start( i < half_size ? 1 : second_column, y + i % half_size );
shortcut_print( win, sc_start, selected == text.first ? hilite( c_white ) : c_white,
selected == text.first ? hilite( c_light_blue ) : c_light_blue, cat_name );
fold_and_print( win, sc_start, getmaxx( win ) - 2,
selected == text.first ? hilite( c_white ) : c_white, cat_name );
++i;

opt_map.emplace( text.first,
Expand Down Expand Up @@ -168,23 +189,26 @@ void help::display_help() const
std::map<int, inclusive_rectangle<point>> opt_map;
int sel = -1;

const hotkey_queue &hkq = hotkey_queue::alphabets();
input_event next_hotkey = ctxt.first_unassigned_hotkey( hkq );
std::map<int, input_event> hotkeys;
for( const auto &text : help_texts ) {
hotkeys.emplace( text.first, next_hotkey );
next_hotkey = ctxt.next_unassigned_hotkey( hkq, next_hotkey );
}

ui.on_redraw( [&]( const ui_adaptor & ) {
draw_border( w_help_border, BORDER_COLOR, _( "Help" ) );
wnoutrefresh( w_help_border );
opt_map = draw_menu( w_help, sel );
opt_map = draw_menu( w_help, sel, hotkeys );
} );

std::map<int, std::vector<std::string>> hotkeys;
for( const auto &text : help_texts ) {
hotkeys.emplace( text.first, get_hotkeys( text.second.first.translated() ) );
}

do {
ui_manager::redraw();

sel = -1;
action = ctxt.handle_input();
std::string sInput = ctxt.get_raw_input().text;
input_event input = ctxt.get_raw_input();

// Mouse selection
if( action == "MOUSE_MOVE" || action == "SELECT" ) {
Expand All @@ -196,8 +220,8 @@ void help::display_help() const
} );
if( cnt > 0 && action == "SELECT" ) {
auto iter = hotkeys.find( sel );
if( iter != hotkeys.end() && !iter->second.empty() ) {
sInput = iter->second.front();
if( iter != hotkeys.end() ) {
input = iter->second;
action = "CONFIRM";
}
}
Expand All @@ -209,58 +233,57 @@ void help::display_help() const
if( help_text_it == help_texts.end() ) {
continue;
}
for( const std::string &hotkey : hotkey_entry.second ) {
if( sInput == hotkey ) {
std::vector<std::string> i18n_help_texts;
i18n_help_texts.reserve( help_text_it->second.second.size() );
std::transform( help_text_it->second.second.begin(), help_text_it->second.second.end(),
std::back_inserter( i18n_help_texts ), [&]( const translation & line ) {
std::string line_proc = line.translated();
if( line_proc == "<DRAW_NOTE_COLORS>" ) {
line_proc = get_note_colors();
} else if( line_proc == "<HELP_DRAW_DIRECTIONS>" ) {
line_proc = get_dir_grid();
}
size_t pos = line_proc.find( "<press_", 0, 7 );
while( pos != std::string::npos ) {
size_t pos2 = line_proc.find( ">", pos, 1 );

std::string action = line_proc.substr( pos + 7, pos2 - pos - 7 );
std::string replace = "<color_light_blue>" +
press_x( look_up_action( action ), "", "" ) + "</color>";

if( replace.empty() ) {
debugmsg( "Help json: Unknown action: %s", action );
} else {
line_proc = string_replace(
line_proc, "<press_" + std::move( action ) + ">", replace );
}

pos = line_proc.find( "<press_", pos2, 7 );
if( input == hotkey_entry.second ) {
std::vector<std::string> i18n_help_texts;
i18n_help_texts.reserve( help_text_it->second.second.size() );
std::transform( help_text_it->second.second.begin(), help_text_it->second.second.end(),
std::back_inserter( i18n_help_texts ), [&]( const translation & line ) {
std::string line_proc = line.translated();
if( line_proc == "<DRAW_NOTE_COLORS>" ) {
line_proc = get_note_colors();
} else if( line_proc == "<HELP_DRAW_DIRECTIONS>" ) {
line_proc = get_dir_grid();
}
size_t pos = line_proc.find( "<press_", 0, 7 );
while( pos != std::string::npos ) {
size_t pos2 = line_proc.find( ">", pos, 1 );

std::string action = line_proc.substr( pos + 7, pos2 - pos - 7 );
std::string replace = "<color_light_blue>" +
press_x( look_up_action( action ), "", "" ) + "</color>";

if( replace.empty() ) {
debugmsg( "Help json: Unknown action: %s", action );
} else {
line_proc = string_replace(
line_proc, "<press_" + std::move( action ) + ">", replace );
}
return line_proc;
} );

if( !i18n_help_texts.empty() ) {
ui.on_screen_resize( nullptr );
pos = line_proc.find( "<press_", pos2, 7 );
}
return line_proc;
} );

if( !i18n_help_texts.empty() ) {
ui.on_screen_resize( nullptr );

const auto get_w_help_border = [&]() {
init_windows( ui );
return w_help_border;
};
const auto get_w_help_border = [&]() {
init_windows( ui );
return w_help_border;
};

scrollable_text( get_w_help_border, _( "Help" ),
std::accumulate( i18n_help_texts.begin() + 1, i18n_help_texts.end(),
i18n_help_texts.front(),
[]( std::string lhs, const std::string & rhs ) {
return std::move( lhs ) + "\n\n" + rhs;
} ) );
scrollable_text( get_w_help_border, _( "Help" ),
std::accumulate( i18n_help_texts.begin() + 1, i18n_help_texts.end(),
i18n_help_texts.front(),
[]( std::string lhs, const std::string & rhs ) {
return std::move( lhs ) + "\n\n" + rhs;
} ) );

ui.on_screen_resize( init_windows );
}
action = "CONFIRM";
break;
ui.on_screen_resize( init_windows );
}
action = "CONFIRM";
break;

}
}
} while( action != "QUIT" );
Expand Down
15 changes: 10 additions & 5 deletions src/help.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
#include <vector>

#include "cuboid_rectangle.h"
#include "input.h"

class JsonArray;
class JsonObject;
class translation;
namespace catacurses
{
Expand All @@ -20,16 +22,19 @@ class window;
class help
{
public:
void load();
static void load( const JsonObject &jo, const std::string &src );
static void reset();
void display_help() const;

private:
void deserialize( const JsonArray &ja );
void load_object( const JsonObject &jo, const std::string &src );
void reset_instance();
std::map<int, inclusive_rectangle<point>> draw_menu( const catacurses::window &win,
int selected ) const;
int selected, std::map<int, input_event> &hotkeys ) const;
static std::string get_note_colors();
static std::string get_dir_grid();

// Modifier for each mods order
int current_order_start = 0;
std::string current_src;
std::map<int, std::pair<translation, std::vector<translation>>> help_texts;
};

Expand Down
3 changes: 3 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "game.h"
#include "gates.h"
#include "harvest.h"
#include "help.h"
#include "input.h"
#include "item_action.h"
#include "item_category.h"
Expand Down Expand Up @@ -274,6 +275,7 @@ void DynamicDataLoader::initialize()
add( "weather_type", &weather_types::load );
add( "ammo_effect", &ammo_effects::load );
add( "emit", &emit::load_emit );
add( "help", &help::load );
add( "activity_type", &activity_type::load );
add( "addiction_type", &add_type::load_add_types );
add( "movement_mode", &move_mode::load_move_mode );
Expand Down Expand Up @@ -665,6 +667,7 @@ void DynamicDataLoader::unload_data()
disease_type::reset();
dreams.clear();
emit::reset();
help::reset();
enchantment::reset();
event_statistic::reset();
effect_on_conditions::reset();
Expand Down
1 change: 0 additions & 1 deletion src/input_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,6 @@ action_id input_context::display_menu( bool permit_execute_action )
if( changed && query_yn( _( "Save changes?" ) ) ) {
try {
inp_mngr.save();
get_help().load();
} catch( std::exception &err ) {
popup( _( "saving keybindings failed: %s" ), err.what() );
}
Expand Down
2 changes: 0 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -851,8 +851,6 @@ int main( int argc, const char *argv[] )

main_menu::queued_world_to_load = std::move( cli.world );

get_help().load();

while( true ) {
main_menu menu;
try {
Expand Down
4 changes: 0 additions & 4 deletions src/path_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,6 @@ std::string PATH_INFO::graveyarddir()
{
return user_dir_value + "graveyard/";
}
cata_path PATH_INFO::help()
{
return datadir_path_value / "help" / "texts.json";
}
cata_path PATH_INFO::keybindings()
{
return datadir_path_value / "raw" / "keybindings.json";
Expand Down
Loading
Loading