From 4286fd97bcc9e1de63b784bfc0f33e7adae81cf6 Mon Sep 17 00:00:00 2001 From: KheirFerrum <102964889+KheirFerrum@users.noreply.github.com> Date: Wed, 5 Jun 2024 07:59:35 +0100 Subject: [PATCH 01/10] feat(port): UI, accessibility updates, tweaks (#4713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(port): UI, accessibility updates, tweaks (#4636) * Port UI, accessibility updates, tweaks * style(autofix.ci): automated formatting * Misc tweaks update * Misc tweaks update * style(autofix.ci): automated formatting * Misc tweaks update * Misc tweaks update * Misc tweaks update * Misc tweaks update * style(autofix.ci): automated formatting * Misc tweaks update * style(autofix.ci): automated formatting * Misc tweaks update * style(autofix.ci): automated formatting * Misc tweaks update * Misc tweaks update --------- Co-Authored-By: Zhilkin Serg Co-Authored-By: Jianxiang Wang (王健翔) Co-Authored-By: Coolthulhu [ui] Properly render control characters in keybindings list (#54350) fix Merge pull request #57361 from Qrox/ui-fixes Some UI fixes Merge pull request #59035 from Qrox/cursor Allow setting terminal cursor for screen readers and IME preview using ui_adaptor Display vehicle interaction UI keybindings according to settings (#47229) Info panel fixes and adding scrollbars (#50892) Change item list highlight color to white (#51447) * Change item list highlight color to white * Position the cursor on the selected item in the item list [Not done in this port, cause based on the PR discussion after the merge, it doesn't work] Merge pull request #58609 from ZeroInternalReflection/CraftingFilterSmallWindowFix Allow scrolling of string_input_popup descriptions Merge pull request #47585 from Qrox/crafting-gui-recp-info Implement scrolling for recipe info in crafting gui Merge pull request #47584 from Qrox/crafting-gui-keybind Display actual bound keys in crafting gui Merge pull request #57266 from Qrox/string-input-context Allow customization of text input UI hotkeys Add max length to worldname input length (#55152) Made a constant to manage this attribute so it is easy to change if the current max is deemed too short in the future. fixes #55148 Merge pull request #46796 from Qrox/string-input-popup Fix cursor movement of string input popup Merge pull request #56212 from Qrox/queue Properly fix conflicting `uilist` keys Merge pull request #47263 from Qrox/paste Allow pasting into input popup on tiles build Merge pull request #40961 from ZhilkinSerg/sa-2020-05-29 Code optimizations reported by static code analysis (2020-05-29) Co-Authored-By: Zhilkin Serg Co-Authored-By: Chaosvolt Co-Authored-By: Jianxiang Wang (王健翔) Co-Authored-By: Rob Kuijper Co-Authored-By: Brambor <13402666+Brambor@users.noreply.github.com> Co-Authored-By: eltank <8000047+eltank@users.noreply.github.com> Co-Authored-By: David Seguin Co-Authored-By: ZeroInternalReflection <89038572+zerointernalreflection@users.noreply.github.com> Co-Authored-By: martinrhan <53336429+martinrhan@users.noreply.github.com> Co-Authored-By: Mark Langsdorf Co-Authored-By: BevapDin <5095435+bevapdin@users.noreply.github.com> Co-Authored-By: Olanti Co-Authored-By: Coolthulhu style(autofix.ci): automated formatting Co-Authored-By: Zhilkin Serg Co-Authored-By: Chaosvolt Co-Authored-By: Jianxiang Wang (王健翔) Co-Authored-By: Rob Kuijper Co-Authored-By: Brambor <13402666+Brambor@users.noreply.github.com> Co-Authored-By: eltank <8000047+eltank@users.noreply.github.com> Co-Authored-By: David Seguin Co-Authored-By: ZeroInternalReflection <89038572+zerointernalreflection@users.noreply.github.com> Co-Authored-By: martinrhan <53336429+martinrhan@users.noreply.github.com> Co-Authored-By: Mark Langsdorf Co-Authored-By: BevapDin <5095435+bevapdin@users.noreply.github.com> Co-Authored-By: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-Authored-By: Olanti Co-Authored-By: Coolthulhu Co-Authored-By: Kenan Mamedov <43505148+Kenan2000@users.noreply.github.com> * Update Keybinds Delete key now properly shows up and is usable. Remove KEYPAD_ENTER which doesn't seem to do anything. * style(autofix.ci): automated formatting * Add check in keybind loading for NULL input cases They'll now throw an error instead of loading silently as Ctrl + @ --------- Co-authored-by: Zhilkin Serg Co-authored-by: Chaosvolt Co-authored-by: Jianxiang Wang (王健翔) Co-authored-by: Rob Kuijper Co-authored-by: Brambor <13402666+Brambor@users.noreply.github.com> Co-authored-by: eltank <8000047+eltank@users.noreply.github.com> Co-authored-by: David Seguin Co-authored-by: ZeroInternalReflection <89038572+zerointernalreflection@users.noreply.github.com> Co-authored-by: martinrhan <53336429+martinrhan@users.noreply.github.com> Co-authored-by: Mark Langsdorf Co-authored-by: BevapDin <5095435+bevapdin@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Olanti Co-authored-by: Coolthulhu Co-authored-by: Kenan Mamedov <43505148+Kenan2000@users.noreply.github.com> --- data/raw/keybindings/keybindings.json | 142 ++++- .../docs/en/dev/explanation/accessibility.md | 45 +- src/action.cpp | 2 +- src/activity_handlers.cpp | 30 +- src/activity_item_handling.cpp | 4 +- src/armor_layers.cpp | 4 +- src/ballistics.cpp | 6 +- src/catalua_bindings.cpp | 2 +- src/catalua_bindings_creature.cpp | 2 +- src/catalua_luna_doc.h | 4 +- src/character.cpp | 26 +- src/character.h | 4 +- src/character_display.cpp | 439 +++++++------ src/character_display.h | 4 +- src/character_turn.cpp | 8 +- src/construction.cpp | 9 +- src/consumption.cpp | 4 +- src/craft_command.cpp | 68 +- src/craft_command.h | 26 +- src/crafting.cpp | 74 +-- src/crafting_gui.cpp | 591 +++++++++--------- src/crafting_gui.h | 2 +- src/creature.cpp | 61 +- src/creature.h | 130 +++- src/cursesdef.h | 1 + src/enum_conversions.cpp | 14 +- src/enums.h | 20 +- src/examine_item_menu.cpp | 4 +- src/explosion.cpp | 14 +- src/game.cpp | 66 +- src/game.h | 2 +- src/input.cpp | 302 ++++++--- src/input.h | 36 +- src/item.cpp | 4 +- src/item.h | 1 - src/iuse.cpp | 11 +- src/iuse_actor.cpp | 4 +- src/loading_ui.cpp | 4 +- src/mapgen.cpp | 2 +- src/mapgen_functions.cpp | 8 +- src/melee.cpp | 2 +- src/monattack.cpp | 10 +- src/mondeath.cpp | 12 +- src/monmove.cpp | 49 +- src/monster.cpp | 34 +- src/monster.h | 2 +- src/monstergenerator.cpp | 12 +- src/mtype.cpp | 2 +- src/mtype.h | 6 +- src/mutation.cpp | 2 +- src/mutation.h | 2 +- src/mutation_ui.cpp | 10 +- src/ncurses_def.cpp | 25 +- src/npctrade.cpp | 4 +- src/omdata.h | 23 +- src/output.cpp | 2 +- src/overmap.cpp | 12 +- src/overmap.h | 70 +-- src/overmap_ui.cpp | 15 +- src/popup.cpp | 4 +- src/ranged.cpp | 45 +- src/savegame.cpp | 2 +- src/sdltiles.cpp | 98 +-- src/string_editor_window.cpp | 88 ++- src/string_editor_window.h | 5 +- src/string_input_popup.cpp | 330 ++++++---- src/string_input_popup.h | 25 +- src/trapfunc.cpp | 42 +- src/ui.cpp | 129 ++-- src/ui.h | 8 +- src/ui_manager.cpp | 75 ++- src/ui_manager.h | 60 +- src/veh_interact.cpp | 114 ++-- src/veh_interact.h | 1 - src/wincurse.cpp | 8 +- src/worldfactory.cpp | 125 ++-- tests/char_biometrics_test.cpp | 8 +- tests/crafting_test.cpp | 4 +- tests/creature_test.cpp | 31 +- 79 files changed, 2255 insertions(+), 1421 deletions(-) diff --git a/data/raw/keybindings/keybindings.json b/data/raw/keybindings/keybindings.json index c7cc5aada769..4762f3845b14 100644 --- a/data/raw/keybindings/keybindings.json +++ b/data/raw/keybindings/keybindings.json @@ -525,10 +525,17 @@ }, { "type": "keybinding", - "id": "CYCLE_MODE", + "id": "SCROLL_RECIPE_INFO_UP", "category": "CRAFTING", - "name": "Cycle display mode", - "bindings": [ { "input_method": "keyboard", "key": "m" } ] + "name": "Scroll recipe info up", + "bindings": [ { "input_method": "keyboard", "key": "[" } ] + }, + { + "type": "keybinding", + "id": "SCROLL_RECIPE_INFO_DOWN", + "category": "CRAFTING", + "name": "Scroll recipe info down", + "bindings": [ { "input_method": "keyboard", "key": "]" } ] }, { "type": "keybinding", @@ -3351,5 +3358,134 @@ "name": "Scroll to the bottom", "category": "LUA_CONSOLE", "bindings": [ { "input_method": "keyboard", "key": "END" } ] + }, + { + "type": "keybinding", + "id": "TEXT.QUIT", + "name": "Cancel text input", + "bindings": [ { "input_method": "keyboard", "key": "ESC" } ] + }, + { + "type": "keybinding", + "id": "TEXT.CONFIRM", + "name": "Confirm text input", + "bindings": [ { "input_method": "keyboard", "key": "RETURN" } ] + }, + { + "type": "keybinding", + "id": "TEXT.UP", + "name": "Move cursor up", + "bindings": [ { "input_method": "keyboard", "key": "UP" } ] + }, + { + "type": "keybinding", + "id": "TEXT.DOWN", + "name": "Move cursor down", + "bindings": [ { "input_method": "keyboard", "key": "DOWN" } ] + }, + { + "type": "keybinding", + "id": "TEXT.LEFT", + "name": "Move cursor left", + "bindings": [ { "input_method": "keyboard", "key": "LEFT" } ] + }, + { + "type": "keybinding", + "id": "TEXT.RIGHT", + "name": "Move cursor right", + "bindings": [ { "input_method": "keyboard", "key": "RIGHT" } ] + }, + { + "type": "keybinding", + "id": "TEXT.PAGE_UP", + "name": "Move cursor to previous page", + "bindings": [ { "input_method": "keyboard", "key": "PPAGE" } ] + }, + { + "type": "keybinding", + "id": "TEXT.PAGE_DOWN", + "name": "Move cursor to next page", + "bindings": [ { "input_method": "keyboard", "key": "NPAGE" } ] + }, + { + "type": "keybinding", + "id": "TEXT.CLEAR", + "name": "Clear text", + "bindings": [ { "input_method": "keyboard", "key": "CTRL+U" } ] + }, + { + "type": "keybinding", + "id": "TEXT.BACKSPACE", + "name": "Remove previous character", + "bindings": [ { "input_method": "keyboard", "key": "BACKSPACE" } ] + }, + { + "type": "keybinding", + "id": "TEXT.HOME", + "name": "Move cursor to start", + "bindings": [ { "input_method": "keyboard", "key": "HOME" } ] + }, + { + "type": "keybinding", + "id": "TEXT.END", + "name": "Move cursor to end", + "bindings": [ { "input_method": "keyboard", "key": "END" } ] + }, + { + "type": "keybinding", + "id": "TEXT.DELETE", + "name": "Remove current character", + "bindings": [ { "input_method": "keyboard", "key": "DELETE" } ] + }, + { + "type": "keybinding", + "id": "TEXT.PASTE", + "name": "Paste from clipboard", + "bindings": [ { "input_method": "keyboard", "key": "CTRL+V" } ] + }, + { + "type": "keybinding", + "id": "TEXT.INPUT_FROM_FILE", + "name": "Input text from file", + "bindings": [ { "input_method": "keyboard", "key": "F2" } ] + }, + { + "type": "keybinding", + "id": "HISTORY_UP", + "name": "Show previous history", + "category": "STRING_INPUT", + "bindings": [ { "input_method": "keyboard", "key": "UP" } ] + }, + { + "type": "keybinding", + "id": "HISTORY_DOWN", + "name": "Show next history", + "category": "STRING_INPUT", + "bindings": [ { "input_method": "keyboard", "key": "DOWN" } ] + }, + { + "type": "keybinding", + "id": "HELP_KEYBINDINGS", + "name": "Display keybindings menu", + "category": "STRING_INPUT", + "//": "'?' is treated as text input, so a different key is used.", + "bindings": [ { "input_method": "keyboard", "key": "F1" } ] + }, + { + "type": "keybinding", + "id": "TEXT.CONFIRM", + "name": "Confirm text input", + "category": "STRING_EDITOR", + "//": "RETURN is treated as text input, so use a different keybinding", + "//2": "CTRL+S is not available on curses, so add CTRL+Y as an alternative", + "bindings": [ { "input_method": "keyboard", "key": "CTRL+S" }, { "input_method": "keyboard", "key": "CTRL+Y" } ] + }, + { + "type": "keybinding", + "id": "HELP_KEYBINDINGS", + "name": "Display keybindings menu", + "category": "STRING_EDITOR", + "//": "'?' is treated as text input, so a different key is used.", + "bindings": [ { "input_method": "keyboard", "key": "F1" } ] } ] diff --git a/doc/src/content/docs/en/dev/explanation/accessibility.md b/doc/src/content/docs/en/dev/explanation/accessibility.md index d64fb6b36617..db6f1c234f96 100644 --- a/doc/src/content/docs/en/dev/explanation/accessibility.md +++ b/doc/src/content/docs/en/dev/explanation/accessibility.md @@ -2,37 +2,14 @@ title: Compatibility with screen readers --- -There are people who uses screen readers to play Cataclysm DDA. In order for screen readers to -announce the most important information in a UI, the terminal cursor has to be placed at the correct -location. This information may be text such as selected item names in a list, etc, and the cursor -has to be placed exactly at the beginning of the text for screen readers to announce it. - -`wmove` in `output.h|cpp` is the function to move the cursor to a specific location. After calling -`wmove` with the target `catacurses::window` and cursor position, `wrefresh` needs to be called -immediately afterwards for `wmove` to take effect. - -Here is an example of placing the cursor explicitly at the beginning of a piece of text: - -```cpp -catacurses::window win = ...; // target window - -... - -// display code -point cursor_position = ...; // default cursor position - -... - -cursor_position = point_zero; // record the start position of the text -fold_and_print( win, cursor_position, getmaxx( win ), c_white, _( "This text is important" ) ); - -... - -// at the end of display code -wmove( win, cursor_position ); -wrefresh( win ); -// no output code should follow as they might change the cursor position -``` - -As shown in the above example, it is preferable to record the intended cursor position in a variable -when the text is printed, and move the cursor later using the variable to ensure consisitency. +There are people who use screen readers to play Cataclysm Bright Nights. In order for screen readers +to announce the most important information in a UI, the terminal cursor has to be placed at the +correct location. This information may be text such as selected item names in a list, etc, and the +cursor has to be placed exactly at the beginning of the text for screen readers to announce it. + +The recommended way to place the cursor is to use `ui_adaptor`. This ensures the desired cursor +position is preserved when subsequent output code changes the cursor position. You can call +`ui_adaptor::set_cursor` and similar methods at any position in a redrawing callback, and the last +cursor position of the topmost UI set via the call will be used as the final cursor position. You +can also call `ui_adaptor::disable_cursor` to prevent a UI's cursor from being used as the final +cursor position. diff --git a/src/action.cpp b/src/action.cpp index 07181f50829e..a08d42e79e02 100644 --- a/src/action.cpp +++ b/src/action.cpp @@ -61,7 +61,7 @@ std::vector keys_bound_to( action_id act, const bool restrict_to_printable action_id action_from_key( char ch ) { input_context ctxt = get_default_mode_input_context(); - const input_event event( ch, CATA_INPUT_KEYBOARD ); + const input_event event( ch, input_event_t::keyboard ); const std::string &action = ctxt.input_to_action( event ); return look_up_action( action ); } diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index a2086b6388b1..dd7a257532a4 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -105,6 +105,8 @@ #include "vehicle_part.h" #include "vpart_position.h" +enum creature_size : int; + static const activity_id ACT_ADV_INVENTORY( "ACT_ADV_INVENTORY" ); static const activity_id ACT_ARMOR_LAYERS( "ACT_ARMOR_LAYERS" ); static const activity_id ACT_ATM( "ACT_ATM" ); @@ -595,7 +597,7 @@ butchery_setup consider_butchery( const item &corpse_item, player &u, butcher_ty inv.has_amount( itype_hd_tow_cable, 1 ) || inv.has_amount( itype_vine_30, 1 ) || inv.has_amount( itype_grapnel, 1 ); - const bool big_corpse = corpse.size >= MS_MEDIUM; + const bool big_corpse = corpse.size >= creature_size::medium; if( big_corpse ) { if( has_rope && !has_tree_nearby && !b_rack_present ) { @@ -635,7 +637,7 @@ butchery_setup consider_butchery( const item &corpse_item, player &u, butcher_ty } if( action == QUARTER ) { - if( corpse.size == MS_TINY ) { + if( corpse.size == creature_size::tiny ) { not_this_one( _( "This corpse is too small to quarter without damaging." ), butcherable_rating::too_small ); } @@ -721,22 +723,22 @@ static void set_up_butchery_activity( player_activity &act, player &u, const but act.index = false; } -static int size_factor_in_time_to_cut( m_size size ) +static int size_factor_in_time_to_cut( creature_size size ) { switch( size ) { // Time (roughly) in turns to cut up the corpse - case MS_TINY: + case creature_size::tiny: return 150; - case MS_SMALL: + case creature_size::small: return 300; - case MS_MEDIUM: + case creature_size::medium: return 450; - case MS_LARGE: + case creature_size::large: return 600; - case MS_HUGE: + case creature_size::huge: return 1800; default: - debugmsg( "Invalid m_size value for butchering corpse: %d", static_cast( size ) ); + debugmsg( "Invalid creature_size value for butchering corpse: %d", static_cast( size ) ); break; } return 0; @@ -950,7 +952,7 @@ static void butchery_drops_harvest( item *corpse_item, const mtype &mt, player & roll = roll / 4; } else if( entry.type == "bone" ) { roll /= 2; - } else if( corpse_item->get_mtype()->size >= MS_MEDIUM && ( entry.type == "skin" ) ) { + } else if( corpse_item->get_mtype()->size >= creature_size::medium && ( entry.type == "skin" ) ) { roll /= 2; } else if( entry.type == "offal" ) { roll /= 5; @@ -1094,7 +1096,8 @@ static void butchery_drops_harvest( item *corpse_item, const mtype &mt, player & // 20% of the original corpse weight is not an item, but liquid gore if( action != DISSECT ) { - p.practice( skill_survival, std::max( 0, practice ), std::max( mt.size - MS_MEDIUM, 0 ) + 4 ); + p.practice( skill_survival, std::max( 0, practice ), std::max( mt.size - creature_size::medium, + 0 ) + 4 ); } } @@ -1252,8 +1255,9 @@ void activity_handlers::butcher_finish( player_activity *act, player *p ) extract_or_wreck_cbms( cbms, roll, *p ); // those lines are for XP gain with dissecting. It depends on the size of the corpse, time to dissect the corpse and the amount of bionics you would gather. int time_to_cut = size_factor_in_time_to_cut( corpse->size ); - int level_cap = std::min( MAX_SKILL, ( corpse->size + ( cbms.size() * 2 + 1 ) ) ); - int size_mult = corpse->size > MS_MEDIUM ? ( corpse->size * corpse->size ) : 8; + int level_cap = std::min( MAX_SKILL, + ( static_cast( corpse->size ) + ( cbms.size() * 2 + 1 ) ) ); + int size_mult = corpse->size > creature_size::medium ? ( corpse->size * corpse->size ) : 8; int practice_amt = ( size_mult + 1 ) * ( ( time_to_cut / 150 ) + 1 ) * ( cbms.size() * cbms.size() / 2 + 1 ); p->practice( skill_firstaid, practice_amt, level_cap ); diff --git a/src/activity_item_handling.cpp b/src/activity_item_handling.cpp index b35c39068b01..4529a2ecfdf6 100644 --- a/src/activity_item_handling.cpp +++ b/src/activity_item_handling.cpp @@ -1540,7 +1540,7 @@ static activity_reason_info can_do_activity_there( const activity_id &act, playe // make sure nobody else is working on that corpse right now if( i->is_corpse() && !i->has_var( "activity_var" ) ) { const mtype corpse = *i->get_mtype(); - if( corpse.size >= MS_MEDIUM ) { + if( corpse.size >= creature_size::medium ) { big_count += 1; } else { small_count += 1; @@ -2140,7 +2140,7 @@ static bool butcher_corpse_activity( player &p, const tripoint &src_loc, for( auto &elem : items ) { if( elem->is_corpse() && !elem->has_var( "activity_var" ) ) { const mtype corpse = *elem->get_mtype(); - if( corpse.size >= MS_MEDIUM && reason != do_activity_reason::NEEDS_BIG_BUTCHERING ) { + if( corpse.size >= creature_size::medium && reason != do_activity_reason::NEEDS_BIG_BUTCHERING ) { continue; } elem->set_var( "activity_var", p.name ); diff --git a/src/armor_layers.cpp b/src/armor_layers.cpp index e58925d85413..be33418dba0f 100644 --- a/src/armor_layers.cpp +++ b/src/armor_layers.cpp @@ -559,7 +559,7 @@ void show_armor_layers_ui( Character &who ) int leftListSize = 0; int rightListSize = 0; - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { draw_grid( w_sort_armor, left_w, middle_w ); werase( w_sort_cat ); @@ -638,7 +638,7 @@ void show_armor_layers_ui( Character &who ) } mvwprintz( w_encumb, point_east, c_white, _( "Encumbrance and Warmth" ) ); - character_display::print_encumbrance( w_encumb, who, -1, + character_display::print_encumbrance( ui, w_encumb, who, -1, ( leftListSize > 0 ) ? *access_tmp_worn( leftListIndex ) : nullptr ); // Right header diff --git a/src/ballistics.cpp b/src/ballistics.cpp index 9d2a37c89938..dc657a86afe0 100644 --- a/src/ballistics.cpp +++ b/src/ballistics.cpp @@ -108,10 +108,10 @@ static void drop_or_embed_projectile( dealt_projectile_attack &attack ) !proj.has_effect( ammo_effect_TANGLE ); // Don't embed in small creatures if( embed ) { - const m_size critter_size = mon->get_size(); + const creature_size critter_size = mon->get_size(); const units::volume vol = drop_item.volume(); - embed = embed && ( critter_size > MS_TINY || vol < 250_ml ); - embed = embed && ( critter_size > MS_SMALL || vol < 500_ml ); + embed = embed && ( critter_size > creature_size::tiny || vol < 250_ml ); + embed = embed && ( critter_size > creature_size::small || vol < 500_ml ); // And if we deal enough damage // Item volume bumps up the required damage too embed = embed && diff --git a/src/catalua_bindings.cpp b/src/catalua_bindings.cpp index 939ae192f447..63bbaebd68ce 100644 --- a/src/catalua_bindings.cpp +++ b/src/catalua_bindings.cpp @@ -794,7 +794,7 @@ void cata::detail::reg_enums( sol::state &lua ) reg_enum( lua ); reg_enum( lua ); reg_enum( lua ); - reg_enum( lua ); + reg_enum( lua ); reg_enum( lua ); reg_enum( lua ); reg_enum( lua ); diff --git a/src/catalua_bindings_creature.cpp b/src/catalua_bindings_creature.cpp index 4b53b0e3d820..b95f950f6164 100644 --- a/src/catalua_bindings_creature.cpp +++ b/src/catalua_bindings_creature.cpp @@ -201,7 +201,7 @@ void cata::detail::reg_creature( sol::state &lua ) SET_FX_T( get_hit, float() const ); SET_FX_T( get_speed, int() const ); - SET_FX_T( get_size, m_size() const ); + SET_FX_T( get_size, creature_size() const ); luna::set_fx( ut, "get_hp", []( const Creature & cr, sol::optional bpid ) -> int { if( bpid.has_value() ) diff --git a/src/catalua_luna_doc.h b/src/catalua_luna_doc.h index 574a777c3c3e..142fc175ba3a 100644 --- a/src/catalua_luna_doc.h +++ b/src/catalua_luna_doc.h @@ -13,7 +13,7 @@ enum color_id : int; enum damage_type : int; enum game_message_type : int; enum m_flag : int; -enum m_size : int; +enum creature_size : int; enum mf_attitude : int; enum monster_attitude : int; enum npc_attitude : int; @@ -164,7 +164,7 @@ LUNA_ENUM( game_message_type, "MsgType" ) LUNA_ENUM( mf_attitude, "MonsterFactionAttitude" ) LUNA_ENUM( m_flag, "MonsterFlag" ) LUNA_ENUM( monster_attitude, "MonsterAttitude" ) -LUNA_ENUM( m_size, "MonsterSize" ) +LUNA_ENUM( creature_size, "MonsterSize" ) LUNA_ENUM( npc_attitude, "NpcAttitude" ) LUNA_ENUM( npc_need, "NpcNeed" ) LUNA_ENUM( sfx::channel, "SfxChannel" ) diff --git a/src/character.cpp b/src/character.cpp index 921453c25386..ebf26c93723f 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -909,7 +909,7 @@ void Character::mod_stat( const std::string &stat, float modifier ) } } -m_size Character::get_size() const +creature_size Character::get_size() const { return size_class; } @@ -1404,7 +1404,7 @@ bool Character::check_mount_is_spooked() // / 2 if horse has full tack and saddle. // Monster in spear reach monster and average stat (8) player on saddled horse, 14% -2% -0.8% / 2 = ~5% if( mounted_creature && mounted_creature->type->has_fear_trigger( mon_trigger::HOSTILE_CLOSE ) ) { - const m_size mount_size = mounted_creature->get_size(); + const creature_size mount_size = mounted_creature->get_size(); const bool saddled = mounted_creature->has_effect( effect_saddled ); for( const monster &critter : g->all_monsters() ) { double chance = 1.0; @@ -2495,7 +2495,7 @@ detached_ptr Character::wear_item( detached_ptr &&wear, } const bool was_deaf = is_deaf(); - const bool supertinymouse = get_size() == MS_TINY; + const bool supertinymouse = get_size() == creature_size::tiny; last_item = to_wear.typeId(); @@ -5402,7 +5402,7 @@ void Character::update_needs( int rate_multiplier ) } // Huge folks take penalties for cramming themselves in vehicles - if( in_vehicle && ( get_size() == MS_HUGE ) + if( in_vehicle && ( get_size() == creature_size::huge ) && !( has_trait( trait_NOPAIN ) || has_effect( effect_narcosis ) ) ) { vehicle *veh = veh_pointer_or_null( get_map().veh_at( pos() ) ); // it's painful to work the controls, but passengers in open topped vehicles are fine @@ -7341,15 +7341,15 @@ std::string Character::height_string() const int Character::height() const { switch( get_size() ) { - case MS_TINY: + case creature_size::tiny: return init_height - 100; - case MS_SMALL: + case creature_size::small: return init_height - 50; - case MS_MEDIUM: + case creature_size::medium: return init_height; - case MS_LARGE: + case creature_size::large: return init_height + 50; - case MS_HUGE: + case creature_size::huge: return init_height + 100; default: break; @@ -10556,7 +10556,7 @@ float Character::power_rating() const } else if( dmg > 12 ) { ret = 3; // Melee weapon or weapon-y tool } - if( get_size() == MS_HUGE ) { + if( get_size() == creature_size::huge ) { ret += 1; } if( is_wearing_power_armor( nullptr ) ) { @@ -11569,7 +11569,8 @@ void Character::knock_back_to( const tripoint &to ) // First, see if we hit a monster if( monster *const critter = g->critter_at( to ) ) { - deal_damage( critter, bodypart_id( "torso" ), damage_instance( DT_BASH, critter->type->size ) ); + deal_damage( critter, bodypart_id( "torso" ), damage_instance( DT_BASH, + static_cast( critter->type->size ) ) ); add_effect( effect_stunned, 1_turns ); /** @EFFECT_STR_MAX allows knocked back player to knock back, damage, stun some monsters */ if( ( str_max - 6 ) / 4 > critter->type->size ) { @@ -11588,7 +11589,8 @@ void Character::knock_back_to( const tripoint &to ) } if( npc *const np = g->critter_at( to ) ) { - deal_damage( np, bodypart_id( "torso" ), damage_instance( DT_BASH, np->get_size() + 1 ) ); + deal_damage( np, bodypart_id( "torso" ), damage_instance( DT_BASH, + static_cast( np->get_size() + 1 ) ) ); add_effect( effect_stunned, 1_turns ); np->deal_damage( this, bodypart_id( "torso" ), damage_instance( DT_BASH, 3 ) ); add_msg_player_or_npc( _( "You bounce off %s!" ), _( " bounces off %s!" ), diff --git a/src/character.h b/src/character.h index 8cfdff40435d..70d93701cd1a 100644 --- a/src/character.h +++ b/src/character.h @@ -369,7 +369,7 @@ class Character : public Creature, public location_visitable void mod_stat( const std::string &stat, float modifier ) override; /** Get size class of character **/ - m_size get_size() const override; + creature_size get_size() const override; /** Recalculate size class of character **/ void recalculate_size(); @@ -2209,7 +2209,7 @@ class Character : public Creature, public location_visitable /**height at character creation*/ int init_height = 175; /** Size class of character. */ - m_size size_class = MS_MEDIUM; + creature_size size_class = creature_size::medium; trap_map known_traps; pimpl encumbrance_cache; diff --git a/src/character_display.cpp b/src/character_display.cpp index d88bdaa2494c..ee654f87ac5a 100644 --- a/src/character_display.cpp +++ b/src/character_display.cpp @@ -100,7 +100,28 @@ static std::vector> list_and_combine_bps( const Chara return bps; } -void character_display::print_encumbrance( const catacurses::window &win, const Character &ch, +static std::pair subindex_around_cursor( + const int num_entries, const int available_space, const int cursor_pos, const bool focused ) +/** + * Return indexes [start, end) that should be displayed from list long `num_entries`, + * given that cursor is at position `cursor_pos` and we have `available_space` spaces. + * + * Example: + * num_entries = 6, available_space = 3, cursor_pos = 2, focused = true; + * so choose 3 from indexes [0, 1, 2, 3, 4, 5] + * return {1, 4} + */ +{ + if( !focused || num_entries <= available_space ) { + return std::make_pair( 0, std::min( available_space, num_entries ) ); + } + int slice_start = std::min( std::max( 0, cursor_pos - available_space / 2 ), + num_entries - available_space ); + return std::make_pair( slice_start, slice_start + available_space ); +} + +void character_display::print_encumbrance( ui_adaptor &ui, const catacurses::window &win, + const Character &ch, const int line, const item *selected_clothing ) { // bool represents whether the part has been combined with its other half @@ -108,8 +129,8 @@ void character_display::print_encumbrance( const catacurses::window &win, const // width/height excluding title & scrollbar const int height = getmaxy( win ) - 1; - bool draw_scrollbar = height < static_cast( bps.size() ); - const int width = getmaxx( win ) - ( draw_scrollbar ? 1 : 0 ); + const bool do_draw_scrollbar = height < static_cast( bps.size() ); + const int width = getmaxx( win ) - ( do_draw_scrollbar ? 1 : 0 ); // index of the first printed bodypart from `bps` const int firstline = clamp( line - height / 2, 0, std::max( 0, static_cast( bps.size() ) - height ) ); @@ -139,31 +160,29 @@ void character_display::print_encumbrance( const catacurses::window &win, const // Two different highlighting schemes, highlight if the line is selected as per line being set. // Make the text green if this part is covered by the passed in item. - nc_color limb_color = thisline == line ? + const bool highlight_line = thisline == line; + nc_color limb_color = highlight_line ? ( highlighted ? h_green : h_light_gray ) : ( highlighted ? c_green : c_light_gray ); - mvwprintz( win, point( 1, 1 + i ), limb_color, "%s", out ); + const int y_pos = 1 + i; + if( highlight_line ) { + ui.set_cursor( win, point( 1, y_pos ) ); + } + mvwprintz( win, point( 1, y_pos ), limb_color, "%s", out ); // accumulated encumbrance from clothing, plus extra encumbrance from layering mvwprintz( win, point( 8, 1 + i ), encumb_color( e.encumbrance ), "%3d", e.encumbrance - e.layer_penalty ); // separator in low toned color - mvwprintz( win, point( 11, 1 + i ), c_light_gray, "+" ); + mvwprintz( win, point( 11, y_pos ), c_light_gray, "+" ); // take into account the new encumbrance system for layers - mvwprintz( win, point( 12, 1 + i ), encumb_color( e.encumbrance ), "%-3d", e.layer_penalty ); + mvwprintz( win, point( 12, y_pos ), encumb_color( e.encumbrance ), "%-3d", e.layer_penalty ); // print warmth, tethered to right hand side of the window - mvwprintz( win, point( width - 6, 1 + i ), ch.bodytemp_color( bp ), "(% 3d)", + mvwprintz( win, point( width - 6, y_pos ), ch.bodytemp_color( bp ), "(% 3d)", temperature_print_rescaling( ch.temp_conv[bp] ) ); } - if( draw_scrollbar ) { - scrollbar(). - offset_x( width ). - offset_y( 1 ). - content_size( bps.size() ). - viewport_pos( firstline ). - viewport_size( height ). - scroll_to_last( false ). - apply( win ); + if( do_draw_scrollbar ) { + draw_scrollbar( win, firstline, height, bps.size(), point( width, 1 ), c_white, true ); } } @@ -323,24 +342,39 @@ static player_display_tab prev_tab( const player_display_tab tab ) } } -static void draw_stats_tab( const catacurses::window &w_stats, - const Character &you, const unsigned int line, const player_display_tab curtab ) +static void draw_stats_tab( ui_adaptor &ui, const catacurses::window &w_stats, const Character &you, + const unsigned line, const player_display_tab curtab ) { werase( w_stats ); - const nc_color title_col = curtab == player_display_tab::stats ? h_light_gray : c_light_gray; + const bool is_current_tab = curtab == player_display_tab::stats; + const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; + if( is_current_tab ) { + ui.set_cursor( w_stats, point_zero ); + } center_print( w_stats, 0, title_col, _( title_STATS ) ); - const auto line_color = [curtab, line]( const unsigned int line_to_draw ) { - if( curtab == player_display_tab::stats && line == line_to_draw ) { + const auto highlight_line = [is_current_tab, line]( const unsigned line_to_draw ) { + return is_current_tab && line == line_to_draw; + }; + + const auto line_color = [&highlight_line]( const unsigned line_to_draw ) { + if( highlight_line( line_to_draw ) ) { return h_light_gray; } else { return c_light_gray; } }; + const auto set_highlight_cursor = [&highlight_line, &ui, &w_stats] + ( const unsigned line_to_draw ) { + if( highlight_line( line_to_draw ) ) { + ui.set_cursor( w_stats, point( 1, line_to_draw + 1 ) ); + } + }; + // Stats - const auto display_stat = [&w_stats]( const char *name, int cur, int max, int line_n, - const nc_color col ) { + const auto display_stat = [&line_color, &set_highlight_cursor, &w_stats] + ( const char *name, const int cur, const int max, const unsigned line_to_draw ) { nc_color cstatus; if( cur <= 0 ) { cstatus = c_dark_gray; @@ -356,18 +390,23 @@ static void draw_stats_tab( const catacurses::window &w_stats, cstatus = c_green; } - mvwprintz( w_stats, point( 1, line_n ), col, name ); - mvwprintz( w_stats, point( 18, line_n ), cstatus, "%2d", cur ); - mvwprintz( w_stats, point( 21, line_n ), c_light_gray, "(%2d)", max ); + set_highlight_cursor( line_to_draw ); + mvwprintz( w_stats, point( 1, line_to_draw + 1 ), line_color( line_to_draw ), name ); + mvwprintz( w_stats, point( 18, line_to_draw + 1 ), cstatus, "%2d", cur ); + mvwprintz( w_stats, point( 21, line_to_draw + 1 ), c_light_gray, "(%2d)", max ); }; - display_stat( _( "Strength:" ), you.get_str(), you.get_str_base(), 1, line_color( 0 ) ); - display_stat( _( "Dexterity:" ), you.get_dex(), you.get_dex_base(), 2, line_color( 1 ) ); - display_stat( _( "Intelligence:" ), you.get_int(), you.get_int_base(), 3, line_color( 2 ) ); - display_stat( _( "Perception:" ), you.get_per(), you.get_per_base(), 4, line_color( 3 ) ); + display_stat( _( "Strength:" ), you.get_str(), you.get_str_base(), 0 ); + display_stat( _( "Dexterity:" ), you.get_dex(), you.get_dex_base(), 1 ); + display_stat( _( "Intelligence:" ), you.get_int(), you.get_int_base(), 2 ); + display_stat( _( "Perception:" ), you.get_per(), you.get_per_base(), 3 ); + + set_highlight_cursor( 4 ); mvwprintz( w_stats, point( 1, 5 ), line_color( 4 ), _( "Height:" ) ); mvwprintz( w_stats, point( 25 - utf8_width( you.height_string() ), 5 ), line_color( 4 ), you.height_string() ); + + set_highlight_cursor( 5 ); mvwprintz( w_stats, point( 1, 6 ), line_color( 5 ), _( "Age:" ) ); mvwprintz( w_stats, point( 25 - utf8_width( you.age_string() ), 6 ), line_color( 5 ), you.age_string() ); @@ -375,8 +414,8 @@ static void draw_stats_tab( const catacurses::window &w_stats, wnoutrefresh( w_stats ); } -static void draw_stats_info( const catacurses::window &w_info, - const Character &you, const unsigned int line ) +static void draw_stats_info( const catacurses::window &w_info, const Character &you, + const unsigned line ) { werase( w_info ); nc_color col_temp = c_light_gray; @@ -450,23 +489,25 @@ static void draw_stats_info( const catacurses::window &w_info, wnoutrefresh( w_info ); } -static void draw_encumbrance_tab( const catacurses::window &w_encumb, - const Character &you, const unsigned int line, const player_display_tab curtab ) +static void draw_encumbrance_tab( ui_adaptor &ui, const catacurses::window &w_encumb, + const Character &you, + const unsigned line, const player_display_tab curtab ) { werase( w_encumb ); const bool is_current_tab = curtab == player_display_tab::encumbrance; const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; center_print( w_encumb, 0, title_col, _( title_ENCUMB ) ); if( is_current_tab ) { - character_display::print_encumbrance( w_encumb, you, line ); + ui.set_cursor( w_encumb, point_zero ); + character_display::print_encumbrance( ui, w_encumb, you, line ); } else { - character_display::print_encumbrance( w_encumb, you ); + character_display::print_encumbrance( ui, w_encumb, you ); } wnoutrefresh( w_encumb ); } -static void draw_encumbrance_info( const catacurses::window &w_info, - const Character &you, const unsigned int line ) +static void draw_encumbrance_info( const catacurses::window &w_info, const Character &you, + const unsigned line ) { const std::vector> bps = list_and_combine_bps( you, nullptr ); @@ -483,46 +524,43 @@ static void draw_encumbrance_info( const catacurses::window &w_info, wnoutrefresh( w_info ); } -static void draw_traits_tab( const catacurses::window &w_traits, - const unsigned int line, const player_display_tab curtab, - const std::vector &traitslist, - const size_t trait_win_size_y ) +static void draw_traits_tab( ui_adaptor &ui, const catacurses::window &w_traits, + const unsigned line, const player_display_tab curtab, + const std::vector &traitslist ) { werase( w_traits ); const bool is_current_tab = curtab == player_display_tab::traits; const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; + if( is_current_tab ) { + ui.set_cursor( w_traits, point_zero ); + } center_print( w_traits, 0, title_col, _( title_TRAITS ) ); - size_t min = 0; - size_t max = 0; - - if( !is_current_tab || line <= ( trait_win_size_y - 2 ) / 2 ) { - min = 0; - max = trait_win_size_y - 1; - if( traitslist.size() < max ) { - max = traitslist.size(); - } - } else if( line >= traitslist.size() - trait_win_size_y / 2 ) { - min = ( traitslist.size() < trait_win_size_y - 1 ? 0 : traitslist.size() - trait_win_size_y + 1 ); - max = traitslist.size(); - } else { - min = line - ( trait_win_size_y - 2 ) / 2; - max = line + ( trait_win_size_y + 1 ) / 2; - if( traitslist.size() < max ) { - max = traitslist.size(); - } - } + const int height = getmaxy( w_traits ) - 1; + const bool do_draw_scrollbar = height < static_cast( traitslist.size() ); + const int width = getmaxx( w_traits ) - 1 - ( do_draw_scrollbar ? 1 : 0 ); + const std::pair range = subindex_around_cursor( traitslist.size(), height, + line, is_current_tab ); - for( size_t i = min; i < max; i++ ) { + for( size_t i = range.first; i < static_cast( range.second ); ++i ) { const auto &mdata = traitslist[i].obj(); const auto color = mdata.get_display_color(); - trim_and_print( w_traits, point( 1, static_cast( 1 + i - min ) ), getmaxx( w_traits ) - 1, - is_current_tab && i == line ? hilite( color ) : color, mdata.name() ); + const bool highlight_line = is_current_tab && i == line; + const point pos( 1, 1 + i - range.first ); + if( highlight_line ) { + ui.set_cursor( w_traits, pos ); + } + trim_and_print( w_traits, pos, width, + highlight_line ? hilite( color ) : color, mdata.name() ); + } + if( do_draw_scrollbar ) { + draw_scrollbar( w_traits, range.first, height, traitslist.size(), point( width + 1, 1 ), c_white, + true ); } wnoutrefresh( w_traits ); } -static void draw_traits_info( const catacurses::window &w_info, const unsigned int line, +static void draw_traits_info( const catacurses::window &w_info, const unsigned line, const std::vector &traitslist ) { werase( w_info ); @@ -535,45 +573,48 @@ static void draw_traits_info( const catacurses::window &w_info, const unsigned i wnoutrefresh( w_info ); } -static void draw_bionics_tab( const catacurses::window &w_bionics, - const Character &you, const unsigned int line, const player_display_tab curtab, - const std::vector &bionicslist, const size_t bionics_win_size_y ) +static void draw_bionics_tab( ui_adaptor &ui, const catacurses::window &w_bionics, + const Character &you, const unsigned line, + const player_display_tab curtab, + const std::vector &bionicslist ) { werase( w_bionics ); const bool is_current_tab = curtab == player_display_tab::bionics; const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; center_print( w_bionics, 0, title_col, _( title_BIONICS ) ); // NOLINTNEXTLINE(cata-use-named-point-constants) - trim_and_print( w_bionics, point( 1, 1 ), getmaxx( w_bionics ) - 1, c_white, + const point pow_pos( 1, 1 ); + if( is_current_tab ) { + ui.set_cursor( w_bionics, pow_pos ); + } + trim_and_print( w_bionics, pow_pos, getmaxx( w_bionics ) - 1, c_white, string_format( _( "Bionic Power: %1$d" " / %2$d" ), units::to_kilojoule( you.get_power_level() ), units::to_kilojoule( you.get_max_power_level() ) ) ); - const size_t useful_y = bionics_win_size_y - 2; - const size_t half_y = useful_y / 2; - - size_t min = 0; - size_t max = 0; - - if( !is_current_tab || line <= half_y ) { // near the top - min = 0; - max = std::min( bionicslist.size(), useful_y ); - } else if( line >= bionicslist.size() - half_y ) { // near the bottom - min = ( bionicslist.size() <= useful_y ? 0 : bionicslist.size() - useful_y ); - max = bionicslist.size(); - } else { // scrolling - min = line - half_y; - max = std::min( bionicslist.size(), line + useful_y - half_y ); + const int height = getmaxy( w_bionics ) - 2; // -2 for headline and power_level + const bool do_draw_scrollbar = height < static_cast( bionicslist.size() ); + const int width = getmaxx( w_bionics ) - 1 - ( do_draw_scrollbar ? 1 : 0 ); + const std::pair range = subindex_around_cursor( bionicslist.size(), height, + line, is_current_tab ); + + for( size_t i = range.first; i < static_cast( range.second ); ++i ) { + const bool highlight_line = is_current_tab && i == line; + const point pos( 1, 2 + i - range.first ); + if( highlight_line ) { + ui.set_cursor( w_bionics, pos ); + } + trim_and_print( w_bionics, pos, width, + highlight_line ? hilite( c_white ) : c_white, "%s", bionicslist[i].info().name ); } - - for( size_t i = min; i < max; i++ ) { - trim_and_print( w_bionics, point( 1, static_cast( 2 + i - min ) ), getmaxx( w_bionics ) - 1, - is_current_tab && i == line ? hilite( c_white ) : c_white, "%s", bionicslist[i].info().name ); + if( do_draw_scrollbar ) { + draw_scrollbar( w_bionics, range.first, height, bionicslist.size(), point( width + 1, 2 ), c_white, + true ); } wnoutrefresh( w_bionics ); } -static void draw_bionics_info( const catacurses::window &w_info, const unsigned int line, +static void draw_bionics_info( const catacurses::window &w_info, const unsigned line, const std::vector &bionicslist ) { werase( w_info ); @@ -585,48 +626,41 @@ static void draw_bionics_info( const catacurses::window &w_info, const unsigned wnoutrefresh( w_info ); } -static void draw_effects_tab( const catacurses::window &w_effects, - const unsigned int line, const player_display_tab curtab, - const std::vector> &effect_name_and_text, - const size_t effect_win_size_y ) +static void draw_effects_tab( ui_adaptor &ui, const catacurses::window &w_effects, + const unsigned line, const player_display_tab curtab, + const std::vector> &effect_name_and_text ) { werase( w_effects ); const bool is_current_tab = curtab == player_display_tab::effects; const nc_color title_col = is_current_tab ? h_light_gray : c_light_gray; + if( is_current_tab ) { + ui.set_cursor( w_effects, point_zero ); + } center_print( w_effects, 0, title_col, _( title_EFFECTS ) ); - const size_t half_y = ( effect_win_size_y - 1 ) / 2; - - size_t min = 0; - size_t max = 0; - - const size_t actual_size = effect_name_and_text.size(); - - if( !is_current_tab || line <= half_y ) { - min = 0; - max = effect_win_size_y - 1; - if( actual_size < max ) { - max = actual_size; - } - } else if( line >= actual_size - half_y ) { - min = ( actual_size < effect_win_size_y - 1 ? 0 : actual_size - effect_win_size_y + 1 ); - max = actual_size; - } else { - min = line - half_y; - max = line - half_y + effect_win_size_y - 1; - if( actual_size < max ) { - max = actual_size; + const int height = getmaxy( w_effects ) - 1; + const bool do_draw_scrollbar = height < static_cast( effect_name_and_text.size() ); + const int width = getmaxx( w_effects ) - 1 - ( do_draw_scrollbar ? 1 : 0 ); + const std::pair range = subindex_around_cursor( effect_name_and_text.size(), + height, line, is_current_tab ); + + for( size_t i = range.first; i < static_cast( range.second ); ++i ) { + const bool highlight_line = is_current_tab && i == line; + const point pos( 1, 1 + i - range.first ); + if( highlight_line ) { + ui.set_cursor( w_effects, pos ); } + trim_and_print( w_effects, pos, width, + highlight_line ? h_light_gray : c_light_gray, effect_name_and_text[i].first ); } - - for( size_t i = min; i < max; i++ ) { - trim_and_print( w_effects, point( 0, static_cast( 1 + i - min ) ), getmaxx( w_effects ) - 1, - is_current_tab && i == line ? h_light_gray : c_light_gray, effect_name_and_text[i].first ); + if( do_draw_scrollbar ) { + draw_scrollbar( w_effects, range.first, height, effect_name_and_text.size(), point( width + 1, 1 ), + c_white, true ); } wnoutrefresh( w_effects ); } -static void draw_effects_info( const catacurses::window &w_info, const unsigned int line, +static void draw_effects_info( const catacurses::window &w_info, const unsigned line, const std::vector> &effect_name_and_text ) { werase( w_info ); @@ -697,12 +731,12 @@ int character_display::display_empty_handed_base_damage( const Character &you ) } } -static void draw_skills_tab( const catacurses::window &w_skills, +static void draw_skills_tab( ui_adaptor &ui, const catacurses::window &w_skills, Character &you, unsigned int line, const player_display_tab curtab, std::vector &skillslist, const size_t skill_win_size_y ) { - const int col_width = 25; + const int col_width = getmaxx( w_skills ) - 1; if( line == 0 ) { //can't point to a header; line = 1; } @@ -710,6 +744,9 @@ static void draw_skills_tab( const catacurses::window &w_skills, werase( w_skills ); const bool is_current_tab = curtab == player_display_tab::skills; nc_color cstatus = is_current_tab ? h_light_gray : c_light_gray; + if( is_current_tab ) { + ui.set_cursor( w_skills, point_zero ); + } center_print( w_skills, 0, cstatus, _( title_SKILLS ) ); size_t min = 0; @@ -728,7 +765,7 @@ static void draw_skills_tab( const catacurses::window &w_skills, max = std::min( min + skill_win_size_y - 1, skillslist.size() ); int y_pos = 1; - for( size_t i = min; i < max; i++, y_pos++ ) { + for( size_t i = min; i < max; ++i, ++y_pos ) { const Skill *aSkill = skillslist[i].skill; const SkillLevel &level = you.get_skill_level_object( aSkill->ident() ); @@ -750,6 +787,7 @@ static void draw_skills_tab( const catacurses::window &w_skills, locked = true; } if( is_current_tab && i == line ) { + ui.set_cursor( w_skills, point( 1, y_pos ) ); if( locked ) { cstatus = h_yellow; } else if( !can_train ) { @@ -839,35 +877,35 @@ static void draw_speed_tab( const catacurses::window &w_speed, //~ %s: Overburdened (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Overburdened" ), 20 ), pen ); - line++; + ++line; } pen = character_effects::get_pain_penalty( you ).speed; if( pen >= 1 ) { //~ %s: Pain (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Pain" ), 20 ), pen ); - line++; + ++line; } if( you.get_thirst() > thirst_levels::very_thirsty ) { pen = std::abs( character_effects::get_thirst_speed_penalty( you.get_thirst() ) ); //~ %s: Thirsty/Parched (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Thirst" ), 20 ), pen ); - line++; + ++line; } if( character_effects::get_kcal_speed_penalty( you.get_kcal_percent() ) < 0 ) { pen = std::abs( character_effects::get_kcal_speed_penalty( you.get_kcal_percent() ) ); //~ %s: Starving/Underfed (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Starving" ), 20 ), pen ); - line++; + ++line; } if( you.has_trait( trait_id( "SUNLIGHT_DEPENDENT" ) ) && !g->is_in_sunlight( you.pos() ) ) { pen = ( g->light_level( you.posz() ) >= 12 ? 5 : 10 ); //~ %s: Out of Sunlight (already left-justified), %2d%%: speed penalty mvwprintz( w_speed, point( 1, line ), c_red, pgettext( "speed penalty", "%s-%2d%%" ), left_justify( _( "Out of Sunlight" ), 20 ), pen ); - line++; + ++line; } const float temperature_speed_modifier = you.mutation_value( "temperature_speed_modifier" ); @@ -887,7 +925,7 @@ static void draw_speed_tab( const catacurses::window &w_speed, //~ %s: Cold-Blooded (already left-justified), %s: sign of bonus/penalty, %2d%%: speed modifier mvwprintz( w_speed, point( 1, line ), pen_color, pgettext( "speed modifier", "%s%s%2d%%" ), left_justify( _( "Cold-Blooded" ), 20 ), pen_sign, std::abs( pen ) ); - line++; + ++line; } } @@ -900,12 +938,12 @@ static void draw_speed_tab( const catacurses::window &w_speed, //~ %s: Mutations (already left-justified), %s: sign of bonus/penalty, %2d%%: speed modifier mvwprintz( w_speed, point( 1, line ), pen_color, pgettext( "speed bonus", "%s%s%2d%%" ), left_justify( _( "Mutations" ), 20 ), pen_sign, std::abs( quick_bonus ) ); - line++; + ++line; } if( you.has_bionic( bionic_id( "bio_speed" ) ) ) { mvwprintz( w_speed, point( 1, line ), c_green, pgettext( "speed bonus", "Bionic Speed +%2d%%" ), bio_speed_bonus ); - line++; + ++line; } for( const std::pair &speed_effect : speed_effects ) { @@ -914,7 +952,7 @@ static void draw_speed_tab( const catacurses::window &w_speed, mvwprintz( w_speed, point( 21, line ), col, ( speed_effect.second > 0 ? "+" : "-" ) ); mvwprintz( w_speed, point( std::abs( speed_effect.second ) >= 10 ? 22 : 23, line ), col, "%d%%", std::abs( speed_effect.second ) ); - line++; + ++line; } int runcost = you.run_cost( 100 ); @@ -928,7 +966,7 @@ static void draw_speed_tab( const catacurses::window &w_speed, } static void draw_info_window( const catacurses::window &w_info, const Character &you, - const unsigned int line, const player_display_tab curtab, + const unsigned line, const player_display_tab curtab, const std::vector &traitslist, const std::vector &bionicslist, const std::vector> &effect_name_and_text, @@ -1130,6 +1168,30 @@ static bool handle_player_display_action( Character &you, unsigned int &line, return done; } +static std::pair calculate_shared_column_win_height( + const unsigned available_height, unsigned first_win_size_y_max, unsigned second_win_size_y_max ) +/** + * Calculate max allowed height of two windows sharing column space. + */ +{ + if( ( second_win_size_y_max + 1 + first_win_size_y_max ) > available_height ) { + // maximum space for either window if they're both the same size + unsigned max_shared_y = ( available_height - 1 ) / 2; + if( std::min( second_win_size_y_max, first_win_size_y_max ) > max_shared_y ) { + // both are larger than the shared size + second_win_size_y_max = max_shared_y; + first_win_size_y_max = available_height - 1 - second_win_size_y_max; + } else if( first_win_size_y_max <= max_shared_y ) { + // first window is less than the shared size, so give space to second window + second_win_size_y_max = available_height - 1 - first_win_size_y_max; + } else { + // second window is less than the shared size + first_win_size_y_max = available_height - 1 - second_win_size_y_max; + } + } + return std::make_pair( first_win_size_y_max, second_win_size_y_max ); +} + void character_display::disp_info( Character &ch ) { std::vector> effect_name_and_text; @@ -1214,7 +1276,7 @@ void character_display::disp_info( Character &ch ) } } - const unsigned int effect_win_size_y = 1 + static_cast( effect_name_and_text.size() ); + const unsigned int effect_win_size_y_max = 1 + static_cast( effect_name_and_text.size() ); std::vector traitslist = ch.get_mutations( false ); std::sort( traitslist.begin(), traitslist.end(), trait_display_sort ); @@ -1250,40 +1312,16 @@ void character_display::disp_info( Character &ch ) const unsigned int infooffsetytop = grid_height + 2; unsigned int infooffsetybottom = infooffsetytop + 1 + info_win_size_y; - const auto calculate_trait_and_bionic_height = [&]() { - const unsigned int maxy = static_cast( TERMY ); - unsigned int trait_win_size_y = trait_win_size_y_max; - unsigned int bionics_win_size_y = bionics_win_size_y_max; - if( ( bionics_win_size_y_max + 1 + trait_win_size_y_max + infooffsetybottom ) > maxy ) { - // maximum space for either window if they're both the same size - unsigned max_shared_y = ( maxy - infooffsetybottom - 1 ) / 2; - if( std::min( bionics_win_size_y_max, trait_win_size_y_max ) > max_shared_y ) { - // both are larger than the shared size - bionics_win_size_y = max_shared_y; - trait_win_size_y = maxy - infooffsetybottom - 1 - bionics_win_size_y; - } else if( trait_win_size_y_max <= max_shared_y ) { - // trait window is less than the shared size, so give space to bionics - bionics_win_size_y = maxy - infooffsetybottom - 1 - trait_win_size_y_max; - } else { - // bionics window is less than the shared size - trait_win_size_y = maxy - infooffsetybottom - 1 - bionics_win_size_y; - } - } - return std::make_pair( trait_win_size_y, bionics_win_size_y ); - }; - // Print name and header // Post-humanity trumps your pre-Cataclysm life // Unless you have a custom profession. std::string race; - if( ch.custom_profession.empty() ) { - if( ch.crossed_threshold() ) { - for( const trait_id &mut : ch.get_mutations() ) { - const mutation_branch &mdata = mut.obj(); - if( mdata.threshold ) { - race = mdata.name(); - break; - } + if( ch.custom_profession.empty() && ch.crossed_threshold() ) { + for( const trait_id &mut : ch.get_mutations() ) { + const mutation_branch &mdata = mut.obj(); + if( mdata.threshold ) { + race = mdata.name(); + break; } } } @@ -1322,10 +1360,12 @@ void character_display::disp_info( Character &ch ) ui_tip.position_from_window( w_tip ); } ); ui_tip.mark_resize(); - ui_tip.on_redraw( [&]( const ui_adaptor & ) { + ui_tip.on_redraw( [&]( ui_adaptor & ui_tip ) { + ui_tip.disable_cursor(); draw_tip( w_tip, ch, race, ctxt ); } ); + // STATS catacurses::window w_stats; catacurses::window w_stats_border; border_helper::border_info &border_stats = borders.add_border(); @@ -1344,58 +1384,66 @@ void character_display::disp_info( Character &ch ) ui_stats.position_from_window( w_stats_border ); } ); ui_stats.mark_resize(); - ui_stats.on_redraw( [&]( const ui_adaptor & ) { + ui_stats.on_redraw( [&]( ui_adaptor & ui_stats ) { borders.draw_border( w_stats_border ); wnoutrefresh( w_stats_border ); - draw_stats_tab( w_stats, ch, line, curtab ); + ui_stats.disable_cursor(); + draw_stats_tab( ui_stats, w_stats, ch, line, curtab ); } ); - unsigned int trait_win_size_y = 0; + // TRAITS & BIONICS + unsigned trait_win_size_y; + unsigned bionics_win_size_y; + // TRAITS catacurses::window w_traits; catacurses::window w_traits_border; border_helper::border_info &border_traits = borders.add_border(); ui_adaptor ui_traits; ui_traits.on_screen_resize( [&]( ui_adaptor & ui_traits ) { - trait_win_size_y = calculate_trait_and_bionic_height().first; + std::tie( trait_win_size_y, bionics_win_size_y ) = calculate_shared_column_win_height( + static_cast( TERMY ) - infooffsetybottom, trait_win_size_y_max, bionics_win_size_y_max ); w_traits = catacurses::newwin( trait_win_size_y, grid_width, point( grid_width + 1, infooffsetybottom ) ); - w_traits_border = catacurses::newwin( trait_win_size_y + 1, grid_width + 1, - point( grid_width + 1, infooffsetybottom ) ); + w_traits_border = catacurses::newwin( trait_win_size_y + 1, grid_width + 2, + point( grid_width, infooffsetybottom ) ); border_traits.set( point( grid_width, infooffsetybottom - 1 ), point( grid_width + 2, trait_win_size_y + 2 ) ); ui_traits.position_from_window( w_traits_border ); } ); ui_traits.mark_resize(); - ui_traits.on_redraw( [&]( const ui_adaptor & ) { + ui_traits.on_redraw( [&]( ui_adaptor & ui_traits ) { borders.draw_border( w_traits_border ); wnoutrefresh( w_traits_border ); - draw_traits_tab( w_traits, line, curtab, traitslist, trait_win_size_y ); + ui_traits.disable_cursor(); + draw_traits_tab( ui_traits, w_traits, line, curtab, traitslist ); } ); - unsigned int bionics_win_size_y = 0; + // BIONICS catacurses::window w_bionics; catacurses::window w_bionics_border; border_helper::border_info &border_bionics = borders.add_border(); ui_adaptor ui_bionics; ui_bionics.on_screen_resize( [&]( ui_adaptor & ui_bionics ) { - bionics_win_size_y = calculate_trait_and_bionic_height().second; + std::tie( trait_win_size_y, bionics_win_size_y ) = calculate_shared_column_win_height( + static_cast( TERMY ) - infooffsetybottom, trait_win_size_y_max, bionics_win_size_y_max ); w_bionics = catacurses::newwin( bionics_win_size_y, grid_width, point( grid_width + 1, infooffsetybottom + trait_win_size_y + 1 ) ); - w_bionics_border = catacurses::newwin( bionics_win_size_y + 1, grid_width + 1, - point( grid_width + 1, - infooffsetybottom + trait_win_size_y + 1 ) ); + w_bionics_border = catacurses::newwin( bionics_win_size_y + 1, grid_width + 2, + point( grid_width, infooffsetybottom + trait_win_size_y + 1 ) ); border_bionics.set( point( grid_width, infooffsetybottom + trait_win_size_y ), point( grid_width + 2, bionics_win_size_y + 2 ) ); ui_bionics.position_from_window( w_bionics_border ); } ); ui_bionics.mark_resize(); - ui_bionics.on_redraw( [&]( const ui_adaptor & ) { + ui_bionics.on_redraw( [&]( ui_adaptor & ui_bionics ) { borders.draw_border( w_bionics_border ); wnoutrefresh( w_bionics_border ); - draw_bionics_tab( w_bionics, ch, line, curtab, bionicslist, bionics_win_size_y ); + ui_bionics.disable_cursor(); + draw_bionics_tab( ui_bionics, w_bionics, ch, line, curtab, bionicslist ); } ); + // ENCUMBRANCE catacurses::window w_encumb; catacurses::window w_encumb_border; border_helper::border_info &border_encumb = borders.add_border(); @@ -1407,17 +1455,25 @@ void character_display::disp_info( Character &ch ) ui_encumb.position_from_window( w_encumb_border ); } ); ui_encumb.mark_resize(); - ui_encumb.on_redraw( [&]( const ui_adaptor & ) { + ui_encumb.on_redraw( [&]( ui_adaptor & ui_encumb ) { borders.draw_border( w_encumb_border ); wnoutrefresh( w_encumb_border ); - draw_encumbrance_tab( w_encumb, ch, line, curtab ); + ui_encumb.disable_cursor(); + draw_encumbrance_tab( ui_encumb, w_encumb, ch, line, curtab ); } ); + // EFFECTS + unsigned int effect_win_size_y = 0; catacurses::window w_effects; catacurses::window w_effects_border; border_helper::border_info &border_effects = borders.add_border(); ui_adaptor ui_effects; ui_effects.on_screen_resize( [&]( ui_adaptor & ui_effects ) { + const unsigned int maxy = static_cast( TERMY ); + effect_win_size_y = effect_win_size_y_max; + if( effect_win_size_y + infooffsetybottom > maxy ) { + effect_win_size_y = maxy - infooffsetybottom; + } w_effects = catacurses::newwin( effect_win_size_y, grid_width, point( grid_width * 2 + 2, infooffsetybottom ) ); w_effects_border = catacurses::newwin( effect_win_size_y + 1, grid_width + 1, @@ -1427,12 +1483,14 @@ void character_display::disp_info( Character &ch ) ui_effects.position_from_window( w_effects_border ); } ); ui_effects.mark_resize(); - ui_effects.on_redraw( [&]( const ui_adaptor & ) { + ui_effects.on_redraw( [&]( ui_adaptor & ui_effects ) { borders.draw_border( w_effects_border ); wnoutrefresh( w_effects_border ); - draw_effects_tab( w_effects, line, curtab, effect_name_and_text, effect_win_size_y ); + ui_effects.disable_cursor(); + draw_effects_tab( ui_effects, w_effects, line, curtab, effect_name_and_text ); } ); + // SPEED catacurses::window w_speed; catacurses::window w_speed_border; border_helper::border_info &border_speed = borders.add_border(); @@ -1446,12 +1504,14 @@ void character_display::disp_info( Character &ch ) ui_speed.position_from_window( w_speed_border ); } ); ui_speed.mark_resize(); - ui_speed.on_redraw( [&]( const ui_adaptor & ) { + ui_speed.on_redraw( [&]( ui_adaptor & ui_speed ) { borders.draw_border( w_speed_border ); wnoutrefresh( w_speed_border ); + ui_speed.disable_cursor(); draw_speed_tab( w_speed, ch, speed_effects ); } ); + // SKILLS unsigned int skill_win_size_y = 0; catacurses::window w_skills; catacurses::window w_skills_border; @@ -1472,12 +1532,14 @@ void character_display::disp_info( Character &ch ) ui_skills.position_from_window( w_skills_border ); } ); ui_skills.mark_resize(); - ui_skills.on_redraw( [&]( const ui_adaptor & ) { + ui_skills.on_redraw( [&]( ui_adaptor & ui_skills ) { borders.draw_border( w_skills_border ); wnoutrefresh( w_skills_border ); - draw_skills_tab( w_skills, ch, line, curtab, skillslist, skill_win_size_y ); + ui_skills.disable_cursor(); + draw_skills_tab( ui_skills, w_skills, ch, line, curtab, skillslist, skill_win_size_y ); } ); + // info panel catacurses::window w_info; catacurses::window w_info_border; border_helper::border_info &border_info = borders.add_border(); @@ -1492,9 +1554,10 @@ void character_display::disp_info( Character &ch ) ui_info.position_from_window( w_info_border ); } ); ui_info.mark_resize(); - ui_info.on_redraw( [&]( const ui_adaptor & ) { + ui_info.on_redraw( [&]( ui_adaptor & ui_info ) { borders.draw_border( w_info_border ); wnoutrefresh( w_info_border ); + ui_info.disable_cursor(); draw_info_window( w_info, ch, line, curtab, traitslist, bionicslist, effect_name_and_text, skillslist ); } ); diff --git a/src/character_display.h b/src/character_display.h index 3d45dadb7335..db5c1dda1f0a 100644 --- a/src/character_display.h +++ b/src/character_display.h @@ -7,6 +7,7 @@ class Character; class item; class avatar; +class ui_adaptor; namespace catacurses { @@ -19,7 +20,8 @@ namespace character_display /** * Formats and prints encumbrance info to specified window */ -void print_encumbrance( const catacurses::window &win, const Character &ch, int line = -1, +void print_encumbrance( ui_adaptor &ui, const catacurses::window &win, const Character &ch, + int line = -1, const item *selected_clothing = nullptr ); /** diff --git a/src/character_turn.cpp b/src/character_turn.cpp index b6dd4d9c47f6..1c6660883e1d 100644 --- a/src/character_turn.cpp +++ b/src/character_turn.cpp @@ -456,10 +456,10 @@ void Character::process_one_effect( effect &it, bool is_new ) if( has_trait( trait_FAT ) ) { mod *= 1.5; } - if( get_size() == MS_LARGE ) { + if( get_size() == creature_size::large ) { mod *= 2; } - if( get_size() == MS_HUGE ) { + if( get_size() == creature_size::huge ) { mod *= 3; } } @@ -480,10 +480,10 @@ void Character::process_one_effect( effect &it, bool is_new ) if( has_trait( trait_FAT ) ) { mod *= 1.5; } - if( get_size() == MS_LARGE ) { + if( get_size() == creature_size::large ) { mod *= 2; } - if( get_size() == MS_HUGE ) { + if( get_size() == creature_size::huge ) { mod *= 3; } } diff --git a/src/construction.cpp b/src/construction.cpp index f69510285051..3c1742b96143 100644 --- a/src/construction.cpp +++ b/src/construction.cpp @@ -643,7 +643,7 @@ std::optional construction_menu( const bool blueprint ) } ); ui.mark_resize(); - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { draw_grid( w_con, w_list_width + w_list_x0 ); // Erase existing tab selection & list of constructions @@ -655,7 +655,6 @@ std::optional construction_menu( const bool blueprint ) // Determine where in the master list to start printing calcStartPos( offset, select, w_list_height, constructs.size() ); // Print the constructions between offset and max (or how many will fit) - std::optional cursor_pos; for( size_t i = 0; static_cast( i ) < w_list_height && ( i + offset ) < constructs.size(); i++ ) { int current = i + offset; @@ -663,7 +662,7 @@ std::optional construction_menu( const bool blueprint ) bool highlight = ( current == select ); const point print_from( 0, i ); if( highlight ) { - cursor_pos = print_from; + ui.set_cursor( w_list, print_from ); } const std::string group_name = is_favorite( group ) ? "* " + group->name() : group->name(); trim_and_print( w_list, print_from, w_list_width, @@ -721,10 +720,6 @@ std::optional construction_menu( const bool blueprint ) draw_scrollbar( w_con, select, w_list_height, constructs.size(), point( 0, 3 ) ); wnoutrefresh( w_con ); - // place the cursor at the selected construction name as expected by screen readers - if( cursor_pos ) { - wmove( w_list, cursor_pos.value() ); - } wnoutrefresh( w_list ); } ); diff --git a/src/consumption.cpp b/src/consumption.cpp index d965f3bccf4f..3e2d10a524f8 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -169,14 +169,14 @@ struct prepared_item_consumption { case item_consumption_t::tool: { comp_selection selection = comp_selection(); selection.comp = tool_comp( it.typeId(), it.type->charges_to_use() ); - selection.use_from = use_from_both; + selection.use_from = usage_from::both; p.consume_tools( selection, it.type->charges_to_use() ); return true; } case item_consumption_t::component: { comp_selection selection = comp_selection(); selection.comp = item_comp( it.typeId(), 1 ); - selection.use_from = use_from_both; + selection.use_from = usage_from::both; return !p.consume_items( selection, 1, is_crafting_component ).empty(); } } diff --git a/src/craft_command.cpp b/src/craft_command.cpp index b4b30fa8f90e..6e716df0b8ed 100644 --- a/src/craft_command.cpp +++ b/src/craft_command.cpp @@ -27,15 +27,15 @@ template std::string comp_selection::nname() const { switch( use_from ) { - case use_from_map: + case usage_from::map: return item::nname( comp.type, comp.count ) + _( " (nearby)" ); - case use_from_both: + case usage_from::both: return item::nname( comp.type, comp.count ) + _( " (person & nearby)" ); - case use_from_player: + case usage_from::player: // Is the same as the default return; - case use_from_none: - case cancel: - case num_usages: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } @@ -46,17 +46,17 @@ namespace io { template<> -std::string enum_to_string( usage data ) +std::string enum_to_string( usage_from data ) { switch( data ) { // *INDENT-OFF* - case usage::use_from_map: return "map"; - case usage::use_from_player: return "player"; - case usage::use_from_both: return "both"; - case usage::use_from_none: return "none"; - case usage::cancel: return "cancel"; + case usage_from::map: return "map"; + case usage_from::player: return "player"; + case usage_from::both: return "both"; + case usage_from::none: return "none"; + case usage_from::cancel: return "cancel"; // *INDENT-ON* - case usage::num_usages: + case usage_from::num_usages_from: break; } debugmsg( "Invalid usage" ); @@ -84,7 +84,7 @@ void comp_selection::deserialize( JsonIn &jsin ) std::string use_from_str; data.read( "use_from", use_from_str ); - use_from = io::string_to_enum( use_from_str ); + use_from = io::string_to_enum( use_from_str ); data.read( "type", comp.type ); data.read( "count", comp.count ); } @@ -154,7 +154,7 @@ void craft_command::execute( const tripoint &new_loc ) for( const auto &it : needs->get_components() ) { comp_selection is = crafter->select_item_component( it, batch_size, map_inv, true, filter ); - if( is.use_from == cancel ) { + if( is.use_from == usage_from::cancel ) { return; } item_selections.push_back( is ); @@ -166,7 +166,7 @@ void craft_command::execute( const tripoint &new_loc ) it, batch_size, map_inv, crafter, true, DEFAULT_HOTKEYS, cost_adjustment::start_only ); - if( ts.use_from == cancel ) { + if( ts.use_from == usage_from::cancel ) { return; } tool_selections.push_back( ts ); @@ -301,49 +301,49 @@ std::vector> craft_command::check_item_components_miss if( item::count_by_charges( type ) && count > 0 ) { switch( item_sel.use_from ) { - case use_from_player: + case usage_from::player: if( !crafter->has_charges( type, count, filter ) ) { missing.push_back( item_sel ); } break; - case use_from_map: + case usage_from::map: if( !map_inv.has_charges( type, count, filter ) ) { missing.push_back( item_sel ); } break; - case use_from_both: + case usage_from::both: if( !( crafter->charges_of( type, INT_MAX, filter ) + map_inv.charges_of( type, INT_MAX, filter ) >= count ) ) { missing.push_back( item_sel ); } break; - case use_from_none: - case cancel: - case num_usages: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } } else { // Counting by units, not charges. switch( item_sel.use_from ) { - case use_from_player: + case usage_from::player: if( !crafter->has_amount( type, count, false, filter ) ) { missing.push_back( item_sel ); } break; - case use_from_map: + case usage_from::map: if( !map_inv.has_components( type, count, filter ) ) { missing.push_back( item_sel ); } break; - case use_from_both: + case usage_from::both: if( !( crafter->amount_of( type, false, std::numeric_limits::max(), filter ) + map_inv.amount_of( type, false, std::numeric_limits::max(), filter ) >= count ) ) { missing.push_back( item_sel ); } break; - case use_from_none: - case cancel: - case num_usages: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } } @@ -362,20 +362,20 @@ std::vector> craft_command::check_tool_components_miss if( tool_sel.comp.count > 0 ) { const int count = tool_sel.comp.count * batch_size; switch( tool_sel.use_from ) { - case use_from_player: + case usage_from::player: if( !crafter->has_charges( type, count ) ) { missing.push_back( tool_sel ); } break; - case use_from_map: + case usage_from::map: if( !map_inv.has_charges( type, count ) ) { missing.push_back( tool_sel ); } break; - case use_from_both: - case use_from_none: - case cancel: - case num_usages: + case usage_from::both: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } } else if( !crafter->has_amount( type, 1 ) && !map_inv.has_tools( type, 1 ) ) { diff --git a/src/craft_command.h b/src/craft_command.h index cdae2a04729b..f57e56cc58cd 100644 --- a/src/craft_command.h +++ b/src/craft_command.h @@ -20,33 +20,39 @@ template struct enum_traits; /** * enum used by comp_selection to indicate where a component should be consumed from. */ -enum usage { - use_from_none = 0, - use_from_map = 1, - use_from_player = 2, - use_from_both = 1 | 2, +enum class usage_from : int { + none = 0, + map = 1, + player = 2, + both = 1 | 2, cancel = 4, // FIXME: hacky. - num_usages + num_usages_from }; template<> -struct enum_traits { - static constexpr usage last = usage::num_usages; +struct enum_traits { + static constexpr usage_from last = usage_from::num_usages_from; }; +inline bool operator&( usage_from l, usage_from r ) +{ + using I = std::underlying_type_t; + return static_cast( l ) & static_cast( r ); +} + /** * Struct that represents a selection of a component for crafting. */ template struct comp_selection { comp_selection() = default; - comp_selection( usage use_from, const CompType &comp = CompType() ) + comp_selection( usage_from use_from, const CompType &comp = CompType() ) : use_from( use_from ), comp( comp ) {} comp_selection( const comp_selection & ) = default; comp_selection &operator = ( const comp_selection & ) = default; /** Tells us where the selected component should be used from. */ - usage use_from = use_from_none; + usage_from use_from = usage_from::none; CompType comp; /** provides a translated name for 'comp', suffixed with it's location e.g '(nearby)'. */ diff --git a/src/crafting.cpp b/src/crafting.cpp index 7fde3e6ea528..6930ee2c633b 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -1229,7 +1229,7 @@ bool player::can_continue_craft( item &craft ) std::vector> item_selections; for( const auto &it : continue_reqs.get_components() ) { comp_selection is = select_item_component( it, batch_size, map_inv, true, filter ); - if( is.use_from == cancel ) { + if( is.use_from == usage_from::cancel ) { cancel_activity(); add_msg( _( "You stop crafting." ) ); return false; @@ -1285,7 +1285,7 @@ bool player::can_continue_craft( item &craft ) comp_selection selection = crafting::select_tool_component( alternatives, batch_size, map_inv, this, true, DEFAULT_HOTKEYS, cost_adjustment::continue_only ); - if( selection.use_from == cancel ) { + if( selection.use_from == usage_from::cancel ) { return false; } new_tool_selections.push_back( selection ); @@ -1349,7 +1349,7 @@ comp_selection player::select_item_component( const std::vector player::select_item_component( const std::vector player::select_item_component( const std::vector player::select_item_component( const std::vector( cmenu.ret ) >= map_has.size() + player_has.size() + mixed.size() ) { - selected.use_from = cancel; + selected.use_from = usage_from::cancel; return selected; } size_t uselection = static_cast( cmenu.ret ); if( uselection < map_has.size() ) { - selected.use_from = usage::use_from_map; + selected.use_from = usage_from::map; selected.comp = map_has[uselection]; } else if( uselection < map_has.size() + player_has.size() ) { uselection -= map_has.size(); - selected.use_from = usage::use_from_player; + selected.use_from = usage_from::player; selected.comp = player_has[uselection]; } else { uselection -= map_has.size() + player_has.size(); - selected.use_from = usage::use_from_both; + selected.use_from = usage_from::both; selected.comp = mixed[uselection]; } } @@ -1557,7 +1557,7 @@ std::vector> player::consume_items( map &m, const comp_select int real_count = ( selected_comp.count > 0 ) ? selected_comp.count * batch : std::abs( selected_comp.count ); // First try to get everything from the map, than (remaining amount) from player - if( is.use_from & use_from_map ) { + if( is.use_from & usage_from::map ) { if( by_charges ) { std::vector> tmp = m.use_charges( loc, radius, selected_comp.type, real_count, filter ); @@ -1576,7 +1576,7 @@ std::vector> player::consume_items( map &m, const comp_select std::make_move_iterator( tmp.end() ) ); } } - if( is.use_from & use_from_player ) { + if( is.use_from & usage_from::player ) { if( by_charges ) { std::vector> tmp = use_charges( selected_comp.type, real_count, filter ); ret.insert( ret.end(), std::make_move_iterator( tmp.begin() ), @@ -1671,28 +1671,28 @@ find_tool_component( const Character *player_with_inv, const std::vectorhas_charges( type, count ) ) { int total_charges = player_with_inv->charges_of( type ); - comp_selection sel( use_from_player, *it ); + comp_selection sel( usage_from::player, *it ); available_tools.emplace_back( sel, total_charges, ideal ); } } if( map_inv.has_charges( type, count ) ) { int total_charges = map_inv.charges_of( type ); - comp_selection sel( use_from_map, *it ); + comp_selection sel( usage_from::map, *it ); available_tools.emplace_back( sel, total_charges, ideal ); } } else if( ( player_with_inv && player_with_inv->has_amount( type, 1 ) ) || map_inv.has_tools( type, 1 ) ) { - comp_selection sel( use_from_none, *it ); + comp_selection sel( usage_from::none, *it ); available_tools.emplace_back( sel, 0, 0 ); } } std::sort( available_tools.begin(), available_tools.end(), []( const avail_tool_comp & lhs, const avail_tool_comp & rhs ) { - if( lhs.comp.use_from == use_from_none && rhs.comp.use_from != use_from_none ) { + if( lhs.comp.use_from == usage_from::none && rhs.comp.use_from != usage_from::none ) { return true; } - if( rhs.comp.use_from == use_from_none ) { + if( rhs.comp.use_from == usage_from::none ) { return false; } if( lhs.charges >= lhs.ideal && rhs.charges < rhs.ideal ) { @@ -1715,9 +1715,9 @@ query_tool_selection( const std::vector &available_tools, if( available_tools.empty() ) { // This SHOULD only happen if cooking with a fire, // and the fire goes out. - return comp_selection( use_from_none ); + return comp_selection( usage_from::none ); } - if( available_tools.front().comp.use_from == use_from_none ) { + if( available_tools.front().comp.use_from == usage_from::none ) { // Default to using a tool that doesn't require charges return available_tools.front().comp; } @@ -1727,7 +1727,7 @@ query_tool_selection( const std::vector &available_tools, if( is_npc ) { auto iter = std::find_if( available_tools.begin(), available_tools.end(), []( const avail_tool_comp & tool ) { - return tool.comp.use_from == use_from_player; + return tool.comp.use_from == usage_from::player; } ); if( iter != available_tools.end() ) { return iter->comp; @@ -1741,7 +1741,7 @@ query_tool_selection( const std::vector &available_tools, for( const avail_tool_comp &tool : available_tools ) { const itype_id &comp_type = tool.comp.comp.type; if( tool.ideal > 1 ) { - const char *format = tool.comp.use_from == use_from_map + const char *format = tool.comp.use_from == usage_from::map ? _( "%s (%d/%d charges nearby)" ) : _( "%s (%d/%d charges on person)" ); std::string str = string_format( format, @@ -1749,7 +1749,7 @@ query_tool_selection( const std::vector &available_tools, tool.charges ); tmenu.addentry( str ); } else { - std::string str = tool.comp.use_from == use_from_map + std::string str = tool.comp.use_from == usage_from::map ? item::nname( comp_type ) + _( " (nearby)" ) : item::nname( comp_type ); tmenu.addentry( str ); @@ -1763,7 +1763,7 @@ query_tool_selection( const std::vector &available_tools, tmenu.query(); if( tmenu.ret < 0 || static_cast( tmenu.ret ) >= available_tools.size() ) { - return comp_selection( cancel ); + return comp_selection( usage_from::cancel ); } return available_tools.at( static_cast( tmenu.ret ) ).comp; @@ -1837,7 +1837,7 @@ bool player::craft_consume_tools( item &craft, int mulitplier, bool start_craft if( tool_sel.comp.count > 0 ) { const int count = calc_charges( tool_sel.comp.count ); switch( tool_sel.use_from ) { - case use_from_player: + case usage_from::player: if( !has_charges( type, count ) ) { add_msg_player_or_npc( _( "You have insufficient %s charges and can't continue crafting" ), @@ -1847,7 +1847,7 @@ bool player::craft_consume_tools( item &craft, int mulitplier, bool start_craft return false; } break; - case use_from_map: + case usage_from::map: if( !map_inv.has_charges( type, count ) ) { add_msg_player_or_npc( _( "You have insufficient %s charges and can't continue crafting" ), @@ -1857,10 +1857,10 @@ bool player::craft_consume_tools( item &craft, int mulitplier, bool start_craft return false; } break; - case use_from_both: - case use_from_none: - case cancel: - case num_usages: + case usage_from::both: + case usage_from::none: + case usage_from::cancel: + case usage_from::num_usages_from: break; } } else if( !has_amount( type, 1 ) && !map_inv.has_tools( type, 1 ) ) { @@ -1896,14 +1896,14 @@ void player::consume_tools( map &m, const comp_selection &tool, int b } int quantity = tool.comp.count * batch; - if( tool.use_from & use_from_player ) { + if( tool.use_from & usage_from::player ) { use_charges( tool.comp.type, quantity ); } - if( tool.use_from & use_from_map ) { + if( tool.use_from & usage_from::map ) { m.use_charges( origin, radius, tool.comp.type, quantity, return_true ); } - // else, use_from_none (or cancel), so we don't use up any tools; + // else, usage_from::none (or usage_from::cancel), so we don't use up any tools; } /* This call is in-efficient when doing it for multiple items with the same map inventory. diff --git a/src/crafting_gui.cpp b/src/crafting_gui.cpp index ad38959267e3..740e57ca891c 100644 --- a/src/crafting_gui.cpp +++ b/src/crafting_gui.cpp @@ -29,6 +29,7 @@ #include "json.h" #include "mod_manager.h" #include "output.h" +#include "player.h" #include "point.h" #include "recipe.h" #include "recipe_dictionary.h" @@ -133,35 +134,202 @@ void reset_recipe_categories() craft_subcat_list.clear(); } -static int print_items( const recipe &r, const catacurses::window &w, point pos, - nc_color col, int batch ) +namespace { - if( !r.has_byproducts() ) { - return 0; +struct availability { + explicit availability( const recipe *r, int batch_size, bool known ) { + this->known = known; + const inventory &inv = get_avatar().crafting_inventory(); + auto all_items_filter = r->get_component_filter( recipe_filter_flags::none ); + auto no_rotten_filter = r->get_component_filter( recipe_filter_flags::no_rotten ); + const deduped_requirement_data &req = r->deduped_requirements(); + could_craft_if_knew = req.can_make_with_inventory( + inv, all_items_filter, batch_size, cost_adjustment::start_only ); + can_craft = known && could_craft_if_knew; + can_craft_non_rotten = req.can_make_with_inventory( + inv, no_rotten_filter, batch_size, cost_adjustment::start_only ); + const requirement_data &simple_req = r->simple_requirements(); + apparently_craftable = simple_req.can_make_with_inventory( + inv, all_items_filter, batch_size, cost_adjustment::start_only ); + has_all_skills = r->skill_used.is_null() || + get_player_character().get_skill_level( r->skill_used ) >= r->difficulty; + for( const std::pair &e : r->required_skills ) { + if( get_player_character().get_skill_level( e.first ) < e.second ) { + has_all_skills = false; + break; + } + } + } + bool can_craft; + bool can_craft_non_rotten; + bool could_craft_if_knew; + bool apparently_craftable; + bool has_all_skills; + bool known; + + nc_color selected_color() const { + return can_craft + ? ( can_craft_non_rotten && has_all_skills ? h_white : h_brown ) + : ( could_craft_if_knew && has_all_skills ? h_yellow : h_dark_gray ); + } + + nc_color color( bool ignore_missing_skills = false ) const { + return can_craft + ? ( ( can_craft_non_rotten && has_all_skills ) || ignore_missing_skills ? c_white : c_brown ) + : ( ( could_craft_if_knew && has_all_skills ) || ignore_missing_skills ? c_yellow : c_dark_gray ); } - const int oldy = pos.y; +}; +} // namespace + +static std::vector recipe_info( + const recipe &recp, + const availability &avail, + player &u, + const std::string qry_comps, + const int batch_size, + const int fold_width, + const nc_color &color ) +{ + std::ostringstream oss; - mvwprintz( w, point( pos.x, pos.y++ ), col, _( "Byproducts:" ) ); - for( const auto &bp : r.byproducts ) { - const itype *t = &*bp.first; - int amount = bp.second * batch; - std::string desc; - if( t->count_by_charges() ) { - amount *= t->charges_default(); - desc = string_format( "> %s (%d)", t->nname( 1 ), amount ); - } else { - desc = string_format( "> %d %s", amount, - t->nname( static_cast( amount ) ) ); + oss << string_format( _( "Primary skill: %s\n" ), + recp.primary_skill_string( &u, false ) ); + + oss << string_format( _( "Other skills: %s\n" ), + recp.required_skills_string( &u, false, false ) ); + + + const int expected_turns = u.expected_time_to_craft( recp, batch_size ) + / to_moves( 1_turns ); + oss << string_format( _( "Time to complete: %s\n" ), + to_string( time_duration::from_turns( expected_turns ) ) ); + + oss << string_format( _( "Batch time savings: %s\n" ), + recp.batch_savings_string() ); + + const int makes = recp.makes_amount(); + if( makes > 1 ) { + oss << string_format( _( "Recipe makes: %d\n" ), makes ); + } + + oss << string_format( _( "Craftable in the dark? %s\n" ), + recp.has_flag( flag_BLIND_EASY ) ? _( "Easy" ) : + recp.has_flag( flag_BLIND_HARD ) ? _( "Hard" ) : + _( "Impossible" ) ); + + std::string nearby_string; + const inventory &crafting_inv = u.crafting_inventory(); + const int nearby_amount = crafting_inv.count_item( recp.result() ); + if( nearby_amount == 0 ) { + nearby_string = "0"; + } else if( nearby_amount > 9000 ) { + // at some point you get too many to count at a glance and just know you have a lot + nearby_string = _( "It's Over 9000!!!" ); + } else { + nearby_string = string_format( "%d", nearby_amount ); + } + oss << string_format( _( "Nearby: %s\n" ), nearby_string ); + + const bool can_craft_this = avail.can_craft; + if( can_craft_this && !avail.can_craft_non_rotten ) { + oss << _( "Will use rotten ingredients\n" ); + } + const bool too_complex = recp.deduped_requirements().is_too_complex(); + if( can_craft_this && too_complex ) { + oss << _( "Due to the complex overlapping requirements, this " + "recipe may appear to be craftable " + "when it is not.\n" ); + } + if( !can_craft_this && avail.apparently_craftable && avail.known ) { + oss << _( "Cannot be crafted because the same item is needed " + "for multiple components\n" ); + } + if( !avail.known ) { + oss << _( "Not known\n" ); + } + + if( recp.has_byproducts() ) { + oss << _( "Byproducts:\n" ); + for( const std::pair &bp : recp.byproducts ) { + const itype *t = &*bp.first; + int amount = bp.second * batch_size; + if( t->count_by_charges() ) { + amount *= t->charges_default(); + oss << string_format( "> %s (%d)\n", t->nname( 1 ), amount ); + } else { + oss << string_format( "> %d %s\n", amount, + t->nname( static_cast( amount ) ) ); + } } - mvwprintz( w, point( pos.x, pos.y++ ), col, desc ); } - return pos.y - oldy; + std::vector result = foldstring( oss.str(), fold_width ); + + bool show_unavailable = false; + const requirement_data &req = recp.simple_requirements(); + const std::vector tools = req.get_folded_tools_list( + fold_width, color, crafting_inv, batch_size ); + const std::vector comps = req.get_folded_components_list( + fold_width, color, crafting_inv, recp.get_component_filter(), batch_size, qry_comps ); + result.insert( result.end(), tools.begin(), tools.end() ); + result.insert( result.end(), comps.begin(), comps.end() ); + + oss = std::ostringstream(); + if( !u.knows_recipe( &recp ) ) { + oss << _( "Recipe not memorized yet\n" ); + const std::set books_with_recipe = show_unavailable + ? crafting::get_books_for_recipe( &recp ) + : crafting::get_books_for_recipe( u, crafting_inv, &recp ); + const std::string enumerated_books = + enumerate_as_string( books_with_recipe.begin(), books_with_recipe.end(), + []( const itype_id & type_id ) { + return colorize( item::nname( type_id ), c_cyan ); + } ); + oss << string_format( _( "Written in: %s\n" ), enumerated_books ); + } + std::vector tmp = foldstring( oss.str(), fold_width ); + result.insert( result.end(), tmp.begin(), tmp.end() ); + + return result; } -const recipe *select_crafting_recipe( int &batch_size ) +const recipe *select_crafting_recipe( int &batch_size_out ) { + struct { + const recipe *recp = nullptr; + std::string qry_comps; + int batch_size; + int fold_width; + std::vector text; + } recipe_info_cache; + int recipe_info_scroll = 0; + + const auto cached_recipe_info = + [&]( + const recipe & recp, + const availability & avail, + player & u, + const std::string qry_comps, + const int batch_size, + const int fold_width, + const nc_color & color + ) -> const std::vector & { // *NOPAD* + if( recipe_info_cache.recp != &recp + || recipe_info_cache.qry_comps != qry_comps + || recipe_info_cache.batch_size != batch_size + || recipe_info_cache.fold_width != fold_width ) + { + recipe_info_cache.recp = &recp; + recipe_info_cache.qry_comps = qry_comps; + recipe_info_cache.batch_size = batch_size; + recipe_info_cache.fold_width = fold_width; + recipe_info_cache.text = recipe_info( + recp, avail, u, qry_comps, batch_size, fold_width, color ); + } + return recipe_info_cache.text; + }; + struct { const recipe *last_recipe = nullptr; detached_ptr dummy; @@ -198,12 +366,36 @@ const recipe *select_crafting_recipe( int &batch_size ) int dataLines = 0; int dataHalfLines = 0; int dataHeight = 0; - int componentPrintHeight = 0; int item_info_width = 0; + + input_context ctxt( "CRAFTING" ); + ctxt.register_cardinal(); + ctxt.register_action( "QUIT" ); + ctxt.register_action( "CONFIRM" ); + ctxt.register_action( "SCROLL_RECIPE_INFO_UP" ); + ctxt.register_action( "SCROLL_RECIPE_INFO_DOWN" ); + ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) ); + ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) ); + ctxt.register_action( "SCROLL_ITEM_INFO_UP" ); + ctxt.register_action( "SCROLL_ITEM_INFO_DOWN" ); + ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "FILTER" ); + ctxt.register_action( "RESET_FILTER" ); + ctxt.register_action( "TOGGLE_FAVORITE" ); + ctxt.register_action( "HELP_RECIPE" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "CYCLE_BATCH" ); + ctxt.register_action( "RELATED_RECIPES" ); + ctxt.register_action( "HIDE_SHOW_RECIPE" ); + ctxt.register_action( "TOGGLE_UNAVAILABLE" ); + catacurses::window w_head; catacurses::window w_subhead; catacurses::window w_data; catacurses::window w_iteminfo; + std::vector keybinding_tips; + int keybinding_x = 0; ui_adaptor ui; ui.on_screen_resize( [&]( ui_adaptor & ui ) { const int freeWidth = TERMX - FULL_SCREEN_WIDTH; @@ -212,11 +404,39 @@ const recipe *select_crafting_recipe( int &batch_size ) width = isWide ? ( freeWidth > FULL_SCREEN_WIDTH ? FULL_SCREEN_WIDTH * 2 : TERMX ) : FULL_SCREEN_WIDTH; const int wStart = ( TERMX - width ) / 2; - const int tailHeight = isWide ? 3 : 4; + + // Keybinding tips + static const translation inline_fmt = to_translation( + //~ %1$s: action description text before key, + //~ %2$s: key description, + //~ %3$s: action description text after key. + "keybinding", "%1$s[%2$s]%3$s" ); + static const translation separate_fmt = to_translation( + //~ %1$s: key description, + //~ %2$s: action description. + "keybinding", "[%1$s]%2$s" ); + std::vector act_descs; + const auto add_action_desc = [&]( const std::string & act, const std::string & txt ) { + act_descs.emplace_back( ctxt.get_desc( act, txt, input_context::allow_all_keys, + inline_fmt, separate_fmt ) ); + }; + add_action_desc( "CONFIRM", pgettext( "crafting gui", "Craft" ) ); + add_action_desc( "HELP_RECIPE", pgettext( "crafting gui", "Describe" ) ); + add_action_desc( "FILTER", pgettext( "crafting gui", "Filter" ) ); + add_action_desc( "RESET_FILTER", pgettext( "crafting gui", "Reset filter" ) ); + add_action_desc( "HIDE_SHOW_RECIPE", pgettext( "crafting gui", "Show/hide" ) ); + add_action_desc( "RELATED_RECIPES", pgettext( "crafting gui", "Related" ) ); + add_action_desc( "TOGGLE_FAVORITE", pgettext( "crafting gui", "Favorite" ) ); + add_action_desc( "CYCLE_BATCH", pgettext( "crafting gui", "Batch" ) ); + add_action_desc( "HELP_KEYBINDINGS", pgettext( "crafting gui", "Keybindings" ) ); + keybinding_x = isWide ? 5 : 2; + keybinding_tips = foldstring( enumerate_as_string( act_descs, enumeration_conjunction::none ), + width - keybinding_x * 2 ); + + const int tailHeight = keybinding_tips.size() + 2; dataLines = TERMY - ( headHeight + subHeadHeight ) - tailHeight; dataHalfLines = dataLines / 2; dataHeight = TERMY - ( headHeight + subHeadHeight ); - componentPrintHeight = dataHeight - tailHeight - 1; w_head = catacurses::newwin( headHeight, width, point( wStart, 0 ) ); w_subhead = catacurses::newwin( subHeadHeight, width, point( wStart, 3 ) ); @@ -224,8 +444,8 @@ const recipe *select_crafting_recipe( int &batch_size ) headHeight + subHeadHeight ) ); if( isWide ) { - item_info_width = width - FULL_SCREEN_WIDTH - 2; - const int item_info_height = dataHeight - 3; + item_info_width = width - FULL_SCREEN_WIDTH - 1; + const int item_info_height = dataHeight - tailHeight; const point item_info( wStart + width - item_info_width, headHeight + subHeadHeight ); w_iteminfo = catacurses::newwin( item_info_height, item_info_width, @@ -242,48 +462,7 @@ const recipe *select_crafting_recipe( int &batch_size ) list_circularizer tab( craft_cat_list ); list_circularizer subtab( craft_subcat_list[tab.cur()] ); std::vector current; - - struct availability { - availability( const recipe *r, int batch_size, bool known ) { - this->known = known; - const inventory &inv = get_avatar().crafting_inventory(); - auto all_items_filter = r->get_component_filter( recipe_filter_flags::none ); - auto no_rotten_filter = r->get_component_filter( recipe_filter_flags::no_rotten ); - const deduped_requirement_data &req = r->deduped_requirements(); - could_craft_if_knew = req.can_make_with_inventory( - inv, all_items_filter, batch_size, cost_adjustment::start_only ); - can_craft = known && could_craft_if_knew; - can_craft_non_rotten = req.can_make_with_inventory( - inv, no_rotten_filter, batch_size, cost_adjustment::start_only ); - const requirement_data &simple_req = r->simple_requirements(); - apparently_craftable = simple_req.can_make_with_inventory( - inv, all_items_filter, batch_size, cost_adjustment::start_only ); - } - bool can_craft; - bool can_craft_non_rotten; - bool could_craft_if_knew; - bool apparently_craftable; - bool known; - - nc_color selected_color() const { - return can_craft - ? ( can_craft_non_rotten ? h_white : h_brown ) - : ( could_craft_if_knew ? h_yellow : h_dark_gray ); - } - - nc_color color() const { - return can_craft - ? ( can_craft_non_rotten ? c_white : c_brown ) - : ( could_craft_if_knew ? c_yellow : c_dark_gray ); - } - }; - std::vector available; - //preserves component color printout between mode rotations - nc_color rotated_color = c_white; - int previous_item_line = -1; - std::string previous_tab; - std::string previous_subtab; int line = 0; bool recalc = true; bool keepline = false; @@ -294,28 +473,8 @@ const recipe *select_crafting_recipe( int &batch_size ) size_t num_hidden = 0; int num_recipe = 0; int batch_line = 0; - int display_mode = 0; const recipe *chosen = nullptr; - input_context ctxt( "CRAFTING" ); - ctxt.register_cardinal(); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "CONFIRM" ); - ctxt.register_action( "CYCLE_MODE" ); - ctxt.register_action( "SCROLL_UP" ); - ctxt.register_action( "SCROLL_DOWN" ); - ctxt.register_action( "PREV_TAB" ); - ctxt.register_action( "NEXT_TAB" ); - ctxt.register_action( "FILTER" ); - ctxt.register_action( "RESET_FILTER" ); - ctxt.register_action( "TOGGLE_FAVORITE" ); - ctxt.register_action( "HELP_RECIPE" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - ctxt.register_action( "CYCLE_BATCH" ); - ctxt.register_action( "RELATED_RECIPES" ); - ctxt.register_action( "HIDE_SHOW_RECIPE" ); - ctxt.register_action( "TOGGLE_UNAVAILABLE" ); - const inventory &crafting_inv = u.crafting_inventory(); const std::vector helpers = character_funcs::get_crafting_helpers( u ); std::string filterstring; @@ -330,7 +489,7 @@ const recipe *select_crafting_recipe( int &batch_size ) const auto &all_recipes = recipe_subset( {}, all_recipes_flat ); int recipe_scroll_window_min = 0; - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { const TAB_MODE m = ( batch ) ? BATCH : ( filterstring.empty() ) ? NORMAL : FILTERED; draw_recipe_tabs( w_head, tab.cur(), m ); const auto &shown_recipes = show_unavailable ? all_recipes : available_recipes; @@ -343,39 +502,12 @@ const recipe *select_crafting_recipe( int &batch_size ) // Clear the screen of recipe data, and draw it anew werase( w_data ); - if( isWide ) { - if( !filterstring.empty() ) { - fold_and_print( w_data, point( 5, dataLines + 1 ), 0, c_white, - _( "Press [ENTER] to attempt to craft object. " - "D[e]scribe, [F]ind, " - "[R]eset, [m]ode, " - "[s]how/hide, Re[L]ated, " - "[*]Favorite, %s, [u]navailable, " - "[?]keybindings" ), - ( batch ) ? _( "cancel " - "[b]atch" ) : _( "[b]atch" ) ); - } else { - fold_and_print( w_data, point( 5, dataLines + 1 ), 0, c_white, - _( "Press [ENTER] to attempt to craft object. " - "D[e]scribe, [F]ind, " - "[m]ode, [s]how/hide, " - "Re[L]ated, [*]Favorite, " - "%s, [u]navailable, " - "[?]keybindings" ), - ( batch ) ? _( "cancel " - "[b]atch" ) : _( "[b]atch" ) ); - } - } else { - if( !filterstring.empty() ) { - mvwprintz( w_data, point( 2, dataLines + 2 ), c_white, - _( "[F]ind, [R]eset, [m]ode, [s]how/hide, Re[L]ated, [*]Fav, [b]atch." ) ); - } else { - mvwprintz( w_data, point( 2, dataLines + 2 ), c_white, - _( "[F]ind, [m]ode, [s]how/hide, Re[L]ated, [*]Fav, [b]atch." ) ); - } - mvwprintz( w_data, point( 2, dataLines + 1 ), c_white, - _( "Press [ENTER] to attempt to craft object. D[e]scribe, [?]keybindings," ) ); + for( size_t i = 0; i < keybinding_tips.size(); ++i ) { + nc_color dummy = c_white; + print_colored_text( w_data, point( keybinding_x, dataLines + 1 + i ), + dummy, c_white, keybinding_tips[i] ); } + // Draw borders for( int i = 1; i < width - 1; ++i ) { // - mvwputch( w_data, point( i, dataHeight - 1 ), BORDER_COLOR, LINE_OXOX ); @@ -387,7 +519,7 @@ const recipe *select_crafting_recipe( int &batch_size ) mvwputch( w_data, point( 0, dataHeight - 1 ), BORDER_COLOR, LINE_XXOO ); // |_ mvwputch( w_data, point( width - 1, dataHeight - 1 ), BORDER_COLOR, LINE_XOOX ); // _| - std::optional cursor_pos; + const int max_recipe_name_width = 27; int recmax = current.size(); // Draw recipes with scroll list @@ -404,187 +536,50 @@ const recipe *select_crafting_recipe( int &batch_size ) const bool highlight = i == line; const nc_color col = highlight ? available[i].selected_color() : available[i].color(); if( highlight ) { - cursor_pos = print_from; + ui.set_cursor( w_data, print_from ); } - mvwprintz( w_data, print_from, col, trim_by_length( tmp_name, 27 ) ); + mvwprintz( w_data, print_from, col, trim_by_length( tmp_name, max_recipe_name_width ) ); } - const int count = batch ? line + 1 : 1; // batch size + const int batch_size = batch ? line + 1 : 1; if( !current.empty() ) { - int pane = FULL_SCREEN_WIDTH - 30 - 1; - nc_color col = available[line].color(); - - const auto &req = current[line]->simple_requirements(); + const recipe &recp = *current[line]; - draw_can_craft_indicator( w_head, *current[line] ); + draw_can_craft_indicator( w_head, recp ); wnoutrefresh( w_head ); - int ypos = 0; - - auto qry = trim( filterstring ); + const availability &avail = available[line]; + // border + padding + name + padding + const int xpos = 1 + 1 + max_recipe_name_width + 3; + const int fold_width = FULL_SCREEN_WIDTH - xpos - 2; + const nc_color color = avail.color( true ); + const std::string qry = trim( filterstring ); std::string qry_comps; if( qry.compare( 0, 2, "c:" ) == 0 ) { qry_comps = qry.substr( 2 ); } - std::vector component_print_buffer; - auto tools = req.get_folded_tools_list( pane, col, crafting_inv, count ); - auto comps = req.get_folded_components_list( pane, col, crafting_inv, - current[line]->get_component_filter(), count, qry_comps ); - component_print_buffer.insert( component_print_buffer.end(), tools.begin(), tools.end() ); - component_print_buffer.insert( component_print_buffer.end(), comps.begin(), comps.end() ); - - if( !u.knows_recipe( current[line] ) ) { - component_print_buffer.emplace_back( _( "Recipe not memorized yet" ) ); - auto books_with_recipe = show_unavailable - ? crafting::get_books_for_recipe( current[line] ) - : crafting::get_books_for_recipe( u, crafting_inv, current[line] ); - std::string enumerated_books = - enumerate_as_string( books_with_recipe.begin(), books_with_recipe.end(), - []( itype_id type_id ) { - return colorize( item::nname( type_id ), c_cyan ); - } ); - const std::string text = string_format( _( "Written in: %s" ), enumerated_books ); - std::vector folded_lines = foldstring( text, pane ); - component_print_buffer.insert( - component_print_buffer.end(), folded_lines.begin(), folded_lines.end() ); - } - - //handle positioning of component list if it needed to be scrolled - int componentPrintOffset = 0; - if( display_mode > 1 ) { - componentPrintOffset = ( display_mode - 1 ) * componentPrintHeight; - } - if( component_print_buffer.size() < static_cast( componentPrintOffset ) ) { - componentPrintOffset = 0; - if( previous_tab != tab.cur() || previous_subtab != subtab.cur() || previous_item_line != line ) { - display_mode = 1; - } else { - display_mode = 0; - } - } - - //only used to preserve mode position on components when - //moving to another item and the view is already scrolled - previous_tab = tab.cur(); - previous_subtab = subtab.cur(); - previous_item_line = line; - const int xpos = 30; - - if( display_mode == 0 ) { - if( display_mod_source ) { - const std::string source = enumerate_as_string( current[line]->src.begin(), - current[line]->src.end(), []( const std::pair &content_source ) { - return string_format( "'%s'", content_source.second->name() ); - }, enumeration_conjunction::arrow ); - - const std::string text = string_format( _( "Origin: %s" ), colorize( source, c_light_blue ) ); - print_colored_text( w_data, point( xpos, ypos++ ), col, col, text ); - } - if( display_object_ids ) { - const std::string ident = string_format( "[%s]", current[line]->ident() ); - const std::string text = string_format( _( "ID: %s" ), colorize( ident, c_light_blue ) ); - print_colored_text( w_data, point( xpos, ypos++ ), col, col, text ); - } + const std::vector &info = cached_recipe_info( + recp, avail, u, qry_comps, batch_size, fold_width, color ); - print_colored_text( - w_data, point( xpos, ypos++ ), col, col, - string_format( _( "Primary skill: %s" ), - current[line]->primary_skill_string( &u, false ) ) ); - - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Other skills: %s" ), - current[line]->required_skills_string( &u, false, false ) ); - - const int expected_turns = u.expected_time_to_craft( *current[line], - count ) / to_moves( 1_turns ); - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Time to complete: %s" ), - to_string( time_duration::from_turns( expected_turns ) ) ); - - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Batch time savings: %s" ), - current[line]->batch_savings_string() ); - - const int makes = current[line]->makes_amount(); - if( makes > 1 ) { - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "One batch makes: %d" ), - makes ); - } - - print_colored_text( - w_data, point( xpos, ypos++ ), col, col, - string_format( _( "Dark craftable? %s" ), - current[line]->has_flag( flag_BLIND_EASY ) ? _( "Easy" ) : - current[line]->has_flag( flag_BLIND_HARD ) ? _( "Hard" ) : - _( "Impossible" ) ) ); - - std::string nearby_string; - const int nearby_amount = crafting_inv.count_item( current[line]->result() ); - - if( nearby_amount == 0 ) { - nearby_string = "0"; - } else if( nearby_amount > 9000 ) { - // at some point you get too many to count at a glance and just know you have a lot - nearby_string = _( "It's Over 9000!!!" ); - } else { - nearby_string = string_format( "%d", nearby_amount ); - } - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Nearby: %s" ), nearby_string ); - - if( available[line].can_craft && !available[line].can_craft_non_rotten ) { - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Will use rotten ingredients" ) ); - } - const bool too_complex = current[line]->deduped_requirements().is_too_complex(); - if( available[line].can_craft && too_complex ) { - ypos += fold_and_print( w_data, point( xpos, ypos ), pane, col, - _( "Due to the complex overlapping requirements, this " - "recipe may appear to be craftable " - "when it is not." ) ); - } - if( !available[line].can_craft && - available[line].apparently_craftable && - available[line].known ) { - ypos += fold_and_print( - w_data, point( xpos, ypos ), pane, col, - _( "Cannot be crafted because the same item is needed " - "for multiple components" ) ); - } - if( !available[line].known ) { - ypos += fold_and_print( - w_data, point( xpos, ypos ), pane, col, - _( "Not known" ) ); - } - ypos += print_items( *current[line], w_data, point( xpos, ypos ), col, batch ? line + 1 : 1 ); + const int total_lines = info.size(); + if( recipe_info_scroll < 0 ) { + recipe_info_scroll = 0; + } else if( recipe_info_scroll + dataLines > total_lines ) { + recipe_info_scroll = std::max( 0, total_lines - dataLines ); } - - //color needs to be preserved in case part of the previous page was cut off - nc_color stored_color = col; - if( display_mode > 1 ) { - stored_color = rotated_color; - } else { - rotated_color = col; - } - int components_printed = 0; - for( size_t i = static_cast( componentPrintOffset ); - i < component_print_buffer.size(); i++ ) { - if( ypos >= componentPrintHeight ) { - break; - } - - components_printed++; - print_colored_text( w_data, point( xpos, ypos++ ), stored_color, col, component_print_buffer[i] ); + for( int i = recipe_info_scroll; + i < std::min( recipe_info_scroll + dataLines, total_lines ); + ++i ) { + nc_color dummy = color; + print_colored_text( w_data, point( xpos, i - recipe_info_scroll ), + dummy, color, info[i] ); } - if( ypos >= componentPrintHeight && - component_print_buffer.size() > static_cast( components_printed ) ) { - mvwprintz( w_data, point( xpos, ypos++ ), col, - _( "v (%s for more)" ), - ctxt.press_x( "CYCLE_MODE" ) ); - rotated_color = stored_color; + if( total_lines > dataLines ) { + scrollbar().offset_x( xpos + fold_width + 1 ).content_size( total_lines ) + .viewport_pos( recipe_info_scroll ).viewport_size( dataLines ) + .apply( w_data ); } } @@ -592,19 +587,13 @@ const recipe *select_crafting_recipe( int &batch_size ) wnoutrefresh( w_data ); if( isWide && !current.empty() ) { - item_info_data data = item_info_data_from_recipe( current[line], count, item_info_scroll ); + item_info_data data = item_info_data_from_recipe( current[line], batch_size, item_info_scroll ); data.without_getch = true; data.without_border = true; data.scrollbar_left = false; data.use_full_win = true; draw_item_info( w_iteminfo, data ); } - - if( cursor_pos ) { - // place the cursor at the selected item name as expected by screen readers - wmove( w_data, cursor_pos.value() ); - wnoutrefresh( w_data ); - } } ); do { @@ -618,10 +607,6 @@ const recipe *select_crafting_recipe( int &batch_size ) keepline = false; } - if( display_mode > 1 ) { - display_mode = 1; - } - show_hidden = false; available.clear(); @@ -764,11 +749,10 @@ const recipe *select_crafting_recipe( int &batch_size ) ui_manager::redraw(); const std::string action = ctxt.handle_input(); - if( action == "CYCLE_MODE" ) { - display_mode = display_mode + 1; - if( display_mode <= 0 ) { - display_mode = 0; - } + if( action == "SCROLL_RECIPE_INFO_UP" ) { + recipe_info_scroll -= dataLines; + } else if( action == "SCROLL_RECIPE_INFO_DOWN" ) { + recipe_info_scroll += dataLines; } else if( action == "LEFT" ) { std::string start = subtab.cur(); do { @@ -809,7 +793,7 @@ const recipe *select_crafting_recipe( int &batch_size ) // popup is already inside check } else { chosen = current[line]; - batch_size = ( batch ) ? line + 1 : 1; + batch_size_out = ( batch ) ? line + 1 : 1; done = true; } } else if( action == "HELP_RECIPE" ) { @@ -876,7 +860,6 @@ const recipe *select_crafting_recipe( int &batch_size ) description += _( "\nUse up/down arrow to go through your search history." ); - description += "\n\n\n"; string_input_popup() .title( _( "Search:" ) ) @@ -947,6 +930,10 @@ const recipe *select_crafting_recipe( int &batch_size ) } recalc = true; + } else if( action == "HELP_KEYBINDINGS" ) { + // Regenerate keybinding tips + ui.mark_resize(); + } else if( action == "TOGGLE_UNAVAILABLE" ) { show_unavailable = !show_unavailable; diff --git a/src/crafting_gui.h b/src/crafting_gui.h index a7e087b85e9a..b77e5416c747 100644 --- a/src/crafting_gui.h +++ b/src/crafting_gui.h @@ -5,7 +5,7 @@ class recipe; class JsonObject; -const recipe *select_crafting_recipe( int &batch_size ); +const recipe *select_crafting_recipe( int &batch_size_out ); void load_recipe_category( const JsonObject &jsobj ); void reset_recipe_categories(); diff --git a/src/creature.cpp b/src/creature.cpp index 3cc4d0ed0b77..ad1af01bb4f6 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -82,9 +82,12 @@ static const efftype_id effect_stunned( "stunned" ); static const efftype_id effect_tied( "tied" ); static const efftype_id effect_zapped( "zapped" ); -const std::map Creature::size_map = { - {"TINY", MS_TINY}, {"SMALL", MS_SMALL}, {"MEDIUM", MS_MEDIUM}, - {"LARGE", MS_LARGE}, {"HUGE", MS_HUGE} +const std::map Creature::size_map = { + {"TINY", creature_size::tiny}, + {"SMALL", creature_size::small}, + {"MEDIUM", creature_size::medium}, + {"LARGE", creature_size::large}, + {"HUGE", creature_size::huge} }; const std::set Creature::cmat_flesh{ @@ -305,18 +308,18 @@ bool Creature::sees( const Creature &critter ) const } float size_modifier = 1.0; switch( ch->get_size() ) { - case MS_TINY: + case creature_size::tiny: size_modifier = 2.0; break; - case MS_SMALL: + case creature_size::small: size_modifier = 1.4; break; - case MS_MEDIUM: + case creature_size::medium: break; - case MS_LARGE: + case creature_size::large: size_modifier = 0.6; break; - case MS_HUGE: + case creature_size::huge: size_modifier = 0.15; break; default: @@ -538,15 +541,15 @@ Creature *Creature::auto_find_hostile_target( int range, int &boo_hoo, int area int Creature::size_melee_penalty() const { switch( get_size() ) { - case MS_TINY: + case creature_size::tiny: return 30; - case MS_SMALL: + case creature_size::small: return 15; - case MS_MEDIUM: + case creature_size::medium: return 0; - case MS_LARGE: + case creature_size::large: return -10; - case MS_HUGE: + case creature_size::huge: return -20; default: break; @@ -726,23 +729,23 @@ dealt_damage_instance hit_with_aoe( Creature &target, Creature *source, const da namespace { -auto get_stun_srength( const projectile &proj, m_size size ) -> int +auto get_stun_srength( const projectile &proj, creature_size size ) -> int { const int stun_strength = proj.has_effect( ammo_effect_BEANBAG ) ? 4 : proj.has_effect( ammo_effect_LARGE_BEANBAG ) ? 16 : 0; switch( size ) { - case MS_TINY: + case creature_size::tiny: return stun_strength * 4; - case MS_SMALL: + case creature_size::small: return stun_strength * 2; - case MS_MEDIUM: + case creature_size::medium: default: return stun_strength; - case MS_LARGE: + case creature_size::large: return stun_strength / 2; - case MS_HUGE: + case creature_size::huge: return stun_strength / 4; } } @@ -1953,19 +1956,19 @@ units::mass Creature::weight_capacity() const { units::mass base_carry = 13_kilogram; switch( get_size() ) { - case MS_TINY: + case creature_size::tiny: base_carry /= 4; break; - case MS_SMALL: + case creature_size::small: base_carry /= 2; break; - case MS_MEDIUM: + case creature_size::medium: default: break; - case MS_LARGE: + case creature_size::large: base_carry *= 2; break; - case MS_HUGE: + case creature_size::huge: base_carry *= 4; break; } @@ -2155,19 +2158,19 @@ void Creature::describe_infrared( std::vector &buf ) const { std::string size_str; switch( get_size() ) { - case m_size::MS_TINY: + case creature_size::tiny: size_str = pgettext( "infrared size", "tiny" ); break; - case m_size::MS_SMALL: + case creature_size::small: size_str = pgettext( "infrared size", "small" ); break; - case m_size::MS_MEDIUM: + case creature_size::medium: size_str = pgettext( "infrared size", "medium" ); break; - case m_size::MS_LARGE: + case creature_size::large: size_str = pgettext( "infrared size", "large" ); break; - case m_size::MS_HUGE: + case creature_size::huge: size_str = pgettext( "infrared size", "huge" ); break; default: diff --git a/src/creature.h b/src/creature.h index 0e69a7597b57..0b46c7183fb6 100644 --- a/src/creature.h +++ b/src/creature.h @@ -55,6 +55,132 @@ struct trap; template struct enum_traits; +using I = std::underlying_type_t; +constexpr I operator+( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) + static_cast( rhs ); +} + +constexpr I operator+( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) + rhs; +} + +constexpr I operator+( const I lhs, const creature_size rhs ) +{ + return lhs + static_cast( rhs ); +} + +constexpr I operator-( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) - static_cast( rhs ); +} + +constexpr I operator-( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) - rhs; +} + +constexpr I operator-( const I lhs, const creature_size rhs ) +{ + return lhs - static_cast( rhs ); +} + +constexpr I operator*( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) * static_cast( rhs ); +} + +constexpr I operator*( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) * rhs; +} + +constexpr I operator*( const I lhs, const creature_size rhs ) +{ + return lhs * static_cast( rhs ); +} + +constexpr I operator/( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) / static_cast( rhs ); +} + +constexpr I operator/( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) / rhs; +} + +constexpr bool operator<=( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) <= static_cast( rhs ); +} + +constexpr bool operator<=( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) <= rhs; +} + +constexpr bool operator<=( const I lhs, const creature_size rhs ) +{ + return lhs <= static_cast( rhs ); +} + +constexpr bool operator<( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) < static_cast( rhs ); +} + +constexpr bool operator<( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) < rhs; +} + +constexpr bool operator<( const I lhs, const creature_size rhs ) +{ + return lhs < static_cast( rhs ); +} + +constexpr bool operator>=( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) >= static_cast( rhs ); +} + +constexpr bool operator>=( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) >= rhs; +} + +constexpr bool operator>=( const I lhs, const creature_size rhs ) +{ + return lhs >= static_cast( rhs ); +} + +constexpr bool operator>( const creature_size lhs, const creature_size rhs ) +{ + return static_cast( lhs ) > static_cast( rhs ); +} + +constexpr bool operator>( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) > rhs; +} + +constexpr bool operator>( const I lhs, const creature_size rhs ) +{ + return lhs > static_cast( rhs ); +} + +constexpr bool operator==( const creature_size lhs, const I rhs ) +{ + return static_cast( lhs ) == rhs; +} + +constexpr bool operator==( const I lhs, const creature_size rhs ) +{ + return lhs == static_cast( rhs ); +} + enum FacingDirection { FD_NONE = 0, FD_LEFT = 1, @@ -66,7 +192,7 @@ class Creature public: virtual ~Creature(); - static const std::map size_map; + static const std::map size_map; // Like disp_name, but without any "the" virtual std::string get_name() const = 0; @@ -446,7 +572,7 @@ class Creature virtual float get_hit() const; virtual int get_speed() const; - virtual m_size get_size() const = 0; + virtual creature_size get_size() const = 0; virtual int get_hp( const bodypart_id &bp ) const; virtual int get_hp() const; virtual int get_hp_max( const bodypart_id &bp ) const; diff --git a/src/cursesdef.h b/src/cursesdef.h index 9fd142872950..27b56c133bf8 100644 --- a/src/cursesdef.h +++ b/src/cursesdef.h @@ -89,6 +89,7 @@ enum base_color : short { using chtype = int; using attr_t = unsigned short; +extern window newscr; extern window stdscr; window newwin( int nlines, int ncols, point begin ); diff --git a/src/enum_conversions.cpp b/src/enum_conversions.cpp index f258ef10cc2b..c2f45937fcc1 100644 --- a/src/enum_conversions.cpp +++ b/src/enum_conversions.cpp @@ -25,20 +25,20 @@ std::string io::enum_to_string( Attitude att ) } template<> -std::string io::enum_to_string( m_size data ) +std::string io::enum_to_string( creature_size data ) { switch( data ) { - case m_size::MS_TINY: + case creature_size::tiny: return "TINY"; - case m_size::MS_SMALL: + case creature_size::small: return "SMALL"; - case m_size::MS_MEDIUM: + case creature_size::medium: return "MEDIUM"; - case m_size::MS_LARGE: + case creature_size::large: return "LARGE"; - case m_size::MS_HUGE: + case creature_size::huge: return "HUGE"; - case m_size::num_m_size: + case creature_size::num_creature_size: break; } debugmsg( "Invalid body_size value: %d", static_cast( data ) ); diff --git a/src/enums.h b/src/enums.h index da4806916927..50f1f3e80701 100644 --- a/src/enums.h +++ b/src/enums.h @@ -61,18 +61,18 @@ struct enum_traits { static constexpr holiday last = holiday::num_holiday; }; -enum m_size : int { - MS_TINY = 0, // Squirrel - MS_SMALL, // Dog - MS_MEDIUM, // Human - MS_LARGE, // Cow - MS_HUGE, // TAAAANK - num_m_size // last +enum creature_size : int { + tiny = 0, // Squirrel + small, // Dog + medium, // Human + large, // Cow + huge, // TAAAANK + num_creature_size // last }; template<> -struct enum_traits { - static constexpr m_size last = m_size::num_m_size; +struct enum_traits { + static constexpr creature_size last = creature_size::num_creature_size; }; enum class temperature_flag : int { @@ -100,7 +100,7 @@ enum visibility_type { }; // Matching rules for comparing a string to an overmap terrain id. -enum class ot_match_type { +enum class ot_match_type : int { // The provided string must completely match the overmap terrain id, including // linear direction suffixes for linear terrain types or rotation suffixes // for rotated terrain types. diff --git a/src/examine_item_menu.cpp b/src/examine_item_menu.cpp index 9d521aae5f6a..327337aa8f36 100644 --- a/src/examine_item_menu.cpp +++ b/src/examine_item_menu.cpp @@ -245,9 +245,9 @@ bool run( } }; ui->mark_resize(); - ui->on_redraw( [&]( const ui_adaptor & ) { + ui->on_redraw( [&]( ui_adaptor & ui ) { draw_item_info( w_info, data ); - action_list.show(); + action_list.show( ui ); } ); bool exit = false; diff --git a/src/explosion.cpp b/src/explosion.cpp index 06b2caf4bfa6..79c803cd78b9 100644 --- a/src/explosion.cpp +++ b/src/explosion.cpp @@ -100,15 +100,15 @@ static float critter_blast_percentage( Creature *c, float range, float distance const float radius_reduction = distance > range ? 0.0f : distance > range / 2 ? 0.5f : 1.0f; switch( c->get_size() ) { - case( m_size::MS_TINY ): + case( creature_size::tiny ): return 0.5 * radius_reduction; - case( m_size::MS_SMALL ): + case( creature_size::small ): return 0.8 * radius_reduction; - case( m_size::MS_MEDIUM ): + case( creature_size::medium ): return 1.0 * radius_reduction; - case( m_size::MS_LARGE ): + case( creature_size::large ): return 1.5 * radius_reduction; - case( m_size::MS_HUGE ): + case( creature_size::huge ): return 2.0 * radius_reduction; default: return 1.0 * radius_reduction; @@ -1685,10 +1685,10 @@ void emp_blast( const tripoint &p ) int deact_chance = 0; const auto mon_item_id = critter.type->revert_to_itype; switch( critter.get_size() ) { - case MS_TINY: + case creature_size::tiny: deact_chance = 6; break; - case MS_SMALL: + case creature_size::small: deact_chance = 3; break; default: diff --git a/src/game.cpp b/src/game.cpp index 39893dc92ee3..44afc044e2a4 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2041,7 +2041,7 @@ std::pair game::mouse_edge_scrolling( input_context &ctxt, c last_mouse_edge_scroll = now; } const input_event event = ctxt.get_raw_input(); - if( event.type == CATA_INPUT_MOUSE ) { + if( event.type == input_event_t::mouse ) { const point threshold( projected_window_width() / 100, projected_window_height() / 100 ); if( event.mouse_pos.x <= threshold.x ) { ret.first.x -= speed; @@ -2066,7 +2066,7 @@ std::pair game::mouse_edge_scrolling( input_context &ctxt, c } } ret.second = ret.first; - } else if( event.type == CATA_INPUT_TIMEOUT ) { + } else if( event.type == input_event_t::timeout ) { ret.first = ret.second; } #endif @@ -2927,8 +2927,8 @@ shared_ptr_fast game::create_or_get_main_ui_adaptor() shared_ptr_fast ui = main_ui_adaptor.lock(); if( !ui ) { main_ui_adaptor = ui = make_shared_fast(); - ui->on_redraw( []( const ui_adaptor & ) { - g->draw(); + ui->on_redraw( []( ui_adaptor & ui ) { + g->draw( ui ); } ); ui->on_screen_resize( [this]( ui_adaptor & ui ) { // remove some space for the sidebar, this is the maximal space @@ -3097,7 +3097,7 @@ static shared_ptr_fast create_trail_callback( } ); } -void game::draw() +void game::draw( ui_adaptor &ui ) { if( test_mode ) { return; @@ -3122,6 +3122,12 @@ void game::draw() wnoutrefresh( w_terrain ); draw_panels( true ); + + // Ensure that the cursor lands on the character when everything is drawn. + // This allows screen readers to describe the area around the player, making it + // much easier to play with them + // (e.g. for blind players) + ui.set_cursor( w_terrain, -u.view_offset.xy() + point( POSX, POSY ) ); } void game::draw_panels( bool force_draw ) @@ -5070,13 +5076,13 @@ bool game::forced_door_closing( const tripoint &p, const ter_id &door_type, int if( can_see ) { add_msg( _( "The %1$s hits the %2$s." ), door_name, critter.name() ); } - if( critter.type->size <= MS_SMALL ) { + if( critter.type->size <= creature_size::small ) { critter.die_in_explosion( nullptr ); } else { critter.apply_damage( nullptr, bodypart_id( "torso" ), bash_dmg ); critter.check_dead_state(); } - if( !critter.is_dead() && critter.type->size >= MS_HUGE ) { + if( !critter.is_dead() && critter.type->size >= creature_size::huge ) { // big critters simply prevent the gate from closing // TODO: perhaps damage/destroy the gate // if the critter was really big? @@ -7404,14 +7410,14 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) std::optional filter_type; - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { reset_item_list_state( w_items_border, iInfoHeight, sort_radius ); + int iStartPos = 0; if( ground_items.empty() ) { wnoutrefresh( w_items_border ); mvwprintz( w_items, point( 2, 10 ), c_white, _( "You don't see any items around you!" ) ); } else { - int iStartPos = 0; werase( w_items ); calcStartPos( iStartPos, iActive, iMaxRows, iItemNum ); int iNum = 0; @@ -7455,15 +7461,15 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) sText += string_format( "[%d]", iter->vIG[iThisPage].count ); } - nc_color col = c_light_green; - if( iNum != iActive ) { - if( high ) { - col = c_yellow; - } else if( low ) { - col = c_red; - } else { - col = iter->example->color_in_inventory(); - } + nc_color col = c_light_gray; + if( iNum == iActive ) { + col = hilite( c_white ); + } else if( high ) { + col = c_yellow; + } else if( low ) { + col = c_red; + } else { + col = iter->example->color_in_inventory(); } trim_and_print( w_items, point( 1, iNum - iStartPos ), width - 9, col, sText ); const int numw = iItemNum > 9 ? 2 : 1; @@ -7516,6 +7522,8 @@ game::vmenu_ret game::list_items( const std::vector &item_list ) trim_and_print( w_item_info, point( 4, 0 ), width - 8, activeItem->example->color_in_inventory(), activeItem->example->display_name() ); wprintw( w_item_info, " >" ); + // move the cursor to the selected item (for screen readers) + ui.set_cursor( w_items, point( 1, iActive - iStartPos ) ); } wnoutrefresh( w_items ); @@ -8749,13 +8757,13 @@ std::vector game::get_dangerous_tile( const tripoint &dest_loc ) co bool game::walk_move( const tripoint &dest_loc, const bool via_ramp ) { if( m.has_flag_ter( TFLAG_SMALL_PASSAGE, dest_loc ) ) { - if( u.get_size() > MS_MEDIUM ) { + if( u.get_size() > creature_size::medium ) { add_msg( m_warning, _( "You can't fit there." ) ); return false; // character too large to fit through a tight passage } if( u.is_mounted() ) { monster *mount = u.mounted_creature.get(); - if( mount->get_size() > MS_MEDIUM ) { + if( mount->get_size() > creature_size::medium ) { add_msg( m_warning, _( "Your mount can't fit there." ) ); return false; // char's mount is too large for tight passages } @@ -9003,18 +9011,18 @@ bool game::walk_move( const tripoint &dest_loc, const bool via_ramp ) if( u.is_mounted() ) { auto mons = u.mounted_creature.get(); switch( mons->get_size() ) { - case MS_TINY: + case creature_size::tiny: volume = 0; // No sound for the tinies break; - case MS_SMALL: + case creature_size::small: volume /= 3; break; - case MS_MEDIUM: + case creature_size::medium: break; - case MS_LARGE: + case creature_size::large: volume *= 1.5; break; - case MS_HUGE: + case creature_size::huge: volume *= 2; break; default: @@ -10708,12 +10716,12 @@ void game::vertical_notes( int z_before, int z_after ) } const oter_id &ter = overmap_buffer.ter( cursp_before ); const oter_id &ter2 = overmap_buffer.ter( cursp_after ); - if( z_after > z_before && ter->has_flag( known_up ) && - !ter2->has_flag( known_down ) ) { + if( z_after > z_before && ter->has_flag( oter_flags::known_up ) && + !ter2->has_flag( oter_flags::known_down ) ) { overmap_buffer.set_seen( cursp_after, true ); overmap_buffer.add_note( cursp_after, string_format( ">:W;%s", _( "AUTO: goes down" ) ) ); - } else if( z_after < z_before && ter->has_flag( known_down ) && - !ter2->has_flag( known_up ) ) { + } else if( z_after < z_before && ter->has_flag( oter_flags::known_down ) && + !ter2->has_flag( oter_flags::known_up ) ) { overmap_buffer.set_seen( cursp_after, true ); overmap_buffer.add_note( cursp_after, string_format( "<:W;%s", _( "AUTO: goes up" ) ) ); } diff --git a/src/game.h b/src/game.h index c87604bfdf3c..5ce0b31a05e0 100644 --- a/src/game.h +++ b/src/game.h @@ -196,7 +196,7 @@ class game shared_ptr_fast create_or_get_main_ui_adaptor(); void invalidate_main_ui_adaptor() const; void mark_main_ui_adaptor_resize() const; - void draw(); + void draw( ui_adaptor &ui ); void draw_ter( bool draw_sounds = true ); void draw_ter( const tripoint ¢er, bool looking = false, bool draw_sounds = true ); diff --git a/src/input.cpp b/src/input.cpp index f0a682af52ce..20e2cfe8292d 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -157,7 +157,7 @@ void input_manager::init() action_contexts[action_id].clear(); touched.insert( a->second ); } - add_input_for_action( action_id, context, input_event( a->first, CATA_INPUT_KEYBOARD ) ); + add_input_for_action( action_id, context, input_event( a->first, input_event_t::keyboard ) ); } // Unmap actions that are explicitly not mapped for( const auto &elem : unbound_keymap ) { @@ -223,21 +223,30 @@ void input_manager::load( const std::string &file_name, bool is_user_preferences std::string input_method = keybinding.get_string( "input_method" ); input_event new_event; if( input_method == "keyboard" ) { - new_event.type = CATA_INPUT_KEYBOARD; + new_event.type = input_event_t::keyboard; } else if( input_method == "gamepad" ) { - new_event.type = CATA_INPUT_GAMEPAD; + new_event.type = input_event_t::gamepad; } else if( input_method == "mouse" ) { - new_event.type = CATA_INPUT_MOUSE; + new_event.type = input_event_t::mouse; } if( keybinding.has_array( "key" ) ) { for( const std::string line : keybinding.get_array( "key" ) ) { - new_event.sequence.push_back( get_keycode( line ) ); + int loaded_keycode = get_keycode( line ); + if( loaded_keycode == '\0' ) { + debugmsg( "Invalid keybind %s detected for action %s", line, action_id ); + } else { + new_event.sequence.push_back( loaded_keycode ); + } } } else { // assume string if not array, and throw if not string - new_event.sequence.push_back( - get_keycode( keybinding.get_string( "key" ) ) - ); + std::string line = keybinding.get_string( "key" ); + int loaded_keycode = get_keycode( line ); + if( loaded_keycode == '\0' ) { + debugmsg( "Invalid keybind %s detected for action %s", line, action_id ); + } else { + new_event.sequence.push_back( loaded_keycode ); + } } events.push_back( new_event ); @@ -293,13 +302,13 @@ void input_manager::save() for( const auto &event : events ) { jsout.start_object(); switch( event.type ) { - case CATA_INPUT_KEYBOARD: + case input_event_t::keyboard: jsout.member( "input_method", "keyboard" ); break; - case CATA_INPUT_GAMEPAD: + case input_event_t::gamepad: jsout.member( "input_method", "gamepad" ); break; - case CATA_INPUT_MOUSE: + case input_event_t::mouse: jsout.member( "input_method", "mouse" ); break; default: @@ -361,6 +370,16 @@ void input_manager::init_keycode_mapping() add_keycode_pair( KEY_BREAK, translate_marker_context( "key name", "BREAK" ) ); add_keycode_pair( KEY_END, translate_marker_context( "key name", "END" ) ); add_keycode_pair( '\n', translate_marker_context( "key name", "RETURN" ) ); + add_keycode_pair( KEY_DC, translate_marker_context( "key name", "DELETE" ) ); + + for( int c = 0; IS_CTRL_CHAR( c ); c++ ) { + // Some codes fall into this range but have more common names we'd prefer to use. + if( !IS_NAMED_CTRL_CHAR( c ) ) { + // These are directly translated in `get_keyname()` + // NOLINTNEXTLINE(cata-translate-string-literal) + add_keycode_pair( c, string_format( "CTRL+%c", c + 64 ) ); + } + } // function keys, as defined by ncurses for( int i = F_KEY_NUM_BEG; i <= F_KEY_NUM_END; i++ ) { @@ -409,8 +428,9 @@ int input_manager::get_keycode( const std::string &name ) const std::string input_manager::get_keyname( int ch, input_event_t inp_type, bool portable ) const { + std::optional raw; - if( inp_type == CATA_INPUT_KEYBOARD ) { + if( inp_type == input_event_t::keyboard ) { const t_key_to_name_map::const_iterator a = keycode_to_keyname.find( ch ); if( a != keycode_to_keyname.end() ) { if( IS_F_KEY( ch ) ) { @@ -421,13 +441,19 @@ std::string input_manager::get_keyname( int ch, input_event_t inp_type, bool por } else { return string_format( pgettext( "function key name", "F%d" ), F_KEY_NUM( ch ) ); } + } else if( IS_CTRL_CHAR( ch ) && !IS_NAMED_CTRL_CHAR( ch ) ) { + if( portable ) { + return a->second; + } else { + return string_format( pgettext( "control key name", "CTRL+%c" ), ch + 64 ); + } } else if( ch >= char_key_beg && ch <= char_key_end && ch != ' ' ) { // character keys except space need no translation return a->second; } raw = a->second; } - } else if( inp_type == CATA_INPUT_MOUSE ) { + } else if( inp_type == input_event_t::mouse ) { if( ch == MOUSE_BUTTON_LEFT ) { raw = translate_marker_context( "key name", "MOUSE_LEFT" ); } else if( ch == MOUSE_BUTTON_RIGHT ) { @@ -439,7 +465,7 @@ std::string input_manager::get_keyname( int ch, input_event_t inp_type, bool por } else if( ch == MOUSE_MOVE ) { raw = translate_marker_context( "key name", "MOUSE_MOVE" ); } - } else if( inp_type == CATA_INPUT_GAMEPAD ) { + } else if( inp_type == input_event_t::gamepad ) { const t_key_to_name_map::const_iterator a = gamepad_keycode_to_keyname.find( ch ); if( a != gamepad_keycode_to_keyname.end() ) { raw = a->second; @@ -674,7 +700,8 @@ void input_context::register_action( const std::string &action_descriptor ) register_action( action_descriptor, translation() ); } -void input_context::register_action( const std::string &action_descriptor, const translation &name ) +void input_context::register_action( const std::string &action_descriptor, + const translation &name ) { if( action_descriptor == "ANY_INPUT" ) { registered_any_input = true; @@ -697,7 +724,7 @@ std::vector input_context::keys_bound_to( const std::string &action_descri for( const auto &events_event : events ) { // Ignore multi-key input and non-keyboard input // TODO: fix for Unicode. - if( events_event.type == CATA_INPUT_KEYBOARD && events_event.sequence.size() == 1 ) { + if( events_event.type == input_event_t::keyboard && events_event.sequence.size() == 1 ) { if( !restrict_to_printable || ( events_event.sequence.front() < 0xFF && isprint( events_event.sequence.front() ) ) ) { result.push_back( static_cast( events_event.sequence.front() ) ); @@ -722,7 +749,7 @@ std::string input_context::get_available_single_char_hotkeys( std::string reques for( const auto &events_event : events ) { // Only consider keyboard events without modifiers - if( events_event.type == CATA_INPUT_KEYBOARD && events_event.modifiers.empty() ) { + if( events_event.type == input_event_t::keyboard && events_event.modifiers.empty() ) { requested_keys.erase( std::remove_if( requested_keys.begin(), requested_keys.end(), ContainsPredicate, char>( events_event.sequence ) ), @@ -736,7 +763,7 @@ std::string input_context::get_available_single_char_hotkeys( std::string reques const input_context::input_event_filter input_context::disallow_lower_case = []( const input_event &evt ) -> bool { - return evt.type != CATA_INPUT_KEYBOARD || + return evt.type != input_event_t::keyboard || // std::lower from is undefined outside unsigned char range // and std::lower from may throw bad_cast for some locales evt.get_first_input() < 'a' || evt.get_first_input() > 'z'; @@ -769,7 +796,7 @@ std::string input_context::get_desc( const std::string &action_descriptor, if( evt_filter( event ) && // Only display gamepad buttons if a gamepad is available. - ( gamepad_available() || event.type != CATA_INPUT_GAMEPAD ) ) { + ( gamepad_available() || event.type != input_event_t::gamepad ) ) { inputs_to_show.push_back( event ); } @@ -799,14 +826,16 @@ std::string input_context::get_desc( const std::string &action_descriptor, return rval; } -std::string input_context::get_desc( const std::string &action_descriptor, - const std::string &text, - const input_context::input_event_filter &evt_filter ) const +std::string input_context::get_desc( + const std::string &action_descriptor, + const std::string &text, + const input_context::input_event_filter &evt_filter, + const translation &inline_fmt, + const translation &separate_fmt ) const { if( action_descriptor == "ANY_INPUT" ) { - // \u00A0 is the non-breaking space //~ keybinding description for anykey - return string_format( pgettext( "keybinding", "[any]\u00A0%s" ), text ); + return string_format( separate_fmt, pgettext( "keybinding", "any" ), text ); } const auto &events = inp_mngr.get_input_for_action( action_descriptor, category ); @@ -815,15 +844,18 @@ std::string input_context::get_desc( const std::string &action_descriptor, for( const auto &evt : events ) { if( evt_filter( evt ) && // Only display gamepad buttons if a gamepad is available. - ( gamepad_available() || evt.type != CATA_INPUT_GAMEPAD ) ) { + ( gamepad_available() || evt.type != input_event_t::gamepad ) ) { na = false; - if( evt.type == CATA_INPUT_KEYBOARD && evt.sequence.size() == 1 ) { + if( evt.type == input_event_t::keyboard && evt.sequence.size() == 1 ) { const int ch = evt.get_first_input(); - const std::string key = utf32_to_utf8( ch ); - const auto pos = ci_find_substr( text, key ); - if( ch > ' ' && ch <= '~' && pos >= 0 ) { - return text.substr( 0, pos ) + "(" + key + ")" + text.substr( pos + key.size() ); + if( ch > ' ' && ch <= '~' ) { + const std::string key = utf32_to_utf8( ch ); + const int pos = ci_find_substr( text, key ); + if( pos >= 0 ) { + return string_format( inline_fmt, text.substr( 0, pos ), + key, text.substr( pos + key.size() ) ); + } } } } @@ -831,14 +863,30 @@ std::string input_context::get_desc( const std::string &action_descriptor, if( na ) { //~ keybinding description for unbound or disabled keys - return string_format( pgettext( "keybinding", "[n/a]\u00A0%s" ), text ); + return string_format( separate_fmt, pgettext( "keybinding", "n/a" ), text ); } else { - //~ keybinding description for bound keys - return string_format( pgettext( "keybinding", "[%s]\u00A0%s" ), - get_desc( action_descriptor, 1, evt_filter ), text ); + return string_format( separate_fmt, get_desc( action_descriptor, 1, evt_filter ), text ); } } +std::string input_context::get_desc( + const std::string &action_descriptor, + const std::string &text, + const input_event_filter &evt_filter ) const +{ + return get_desc( action_descriptor, text, evt_filter, + to_translation( + //~ %1$s: action description text before key, + //~ %2$s: key description, + //~ %3$s: action description text after key. + "keybinding", "%1$s(%2$s)%3$s" ), + to_translation( + // \u00A0 is the non-breaking space + //~ %1$s: key description, + //~ %2$s: action description. + "keybinding", "[%1$s]\u00A0%2$s" ) ); +} + std::string input_context::describe_key_and_name( const std::string &action_descriptor, const input_context::input_event_filter &evt_filter ) const { @@ -856,11 +904,11 @@ const std::string &input_context::handle_input( const int timeout ) if( timeout >= 0 ) { inp_mngr.set_timeout( timeout ); } - next_action.type = CATA_INPUT_ERROR; + next_action.type = input_event_t::error; const std::string *result = &CATA_ERROR; while( true ) { next_action = inp_mngr.get_input_event(); - if( next_action.type == CATA_INPUT_TIMEOUT ) { + if( next_action.type == input_event_t::timeout ) { result = &TIMEOUT; break; } @@ -876,7 +924,7 @@ const std::string &input_context::handle_input( const int timeout ) break; } - if( next_action.type == CATA_INPUT_MOUSE ) { + if( next_action.type == input_event_t::mouse ) { if( !handling_coordinate_input && action == CATA_ERROR ) { continue; // Ignore this mouse input. } @@ -990,6 +1038,22 @@ std::optional input_context::get_direction( const std::string &action const std::string display_help_hotkeys = "abcdefghijkpqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:;'\",/<>?!@#$%^&*()_[]\\{}|`~"; +namespace +{ +enum class fallback_action { + add_local, + add_global, + remove, + execute, +}; +} // namespace +static const std::map fallback_keys = { + { fallback_action::add_local, '+' }, + { fallback_action::add_global, '=' }, + { fallback_action::remove, '-' }, + { fallback_action::execute, '.' }, +}; + action_id input_context::display_menu( const bool permit_execute_action ) { action_id action_to_execute = ACTION_NULL; @@ -1003,8 +1067,22 @@ action_id input_context::display_menu( const bool permit_execute_action ) ctxt.register_action( "REMOVE" ); ctxt.register_action( "ADD_LOCAL" ); ctxt.register_action( "ADD_GLOBAL" ); - ctxt.register_action( "EXECUTE" ); + if( permit_execute_action ) { + ctxt.register_action( "EXECUTE" ); + } ctxt.register_action( "QUIT" ); + // String input actions + ctxt.register_action( "TEXT.LEFT" ); + ctxt.register_action( "TEXT.RIGHT" ); + ctxt.register_action( "TEXT.CLEAR" ); + ctxt.register_action( "TEXT.BACKSPACE" ); + ctxt.register_action( "TEXT.HOME" ); + ctxt.register_action( "TEXT.END" ); + ctxt.register_action( "TEXT.DELETE" ); +#if defined( TILES ) + ctxt.register_action( "TEXT.PASTE" ); +#endif + ctxt.register_action( "TEXT.INPUT_FROM_FILE" ); ctxt.register_action( "ANY_INPUT" ); if( category != "HELP_KEYBINDINGS" ) { @@ -1021,6 +1099,12 @@ action_id input_context::display_menu( const bool permit_execute_action ) size_t display_height = 0; size_t legwidth = 0; string_input_popup spopup; + // ignore hardcoded keys in string input popup + for( const std::pair &v : fallback_keys ) { + spopup.callbacks[v.second] = []() { + return true; + }; + } const auto recalc_size = [&]( ui_adaptor & ui ) { int maxwidth = std::max( FULL_SCREEN_WIDTH, TERMX ); width = min( 80, maxwidth ); @@ -1031,9 +1115,11 @@ action_id input_context::display_menu( const bool permit_execute_action ) point( maxwidth / 2 - width / 2, maxheight / 2 - height / 2 ) ); // height of the area usable for display of keybindings, excludes headers & borders display_height = height - LEGEND_HEIGHT - BORDER_SPACE; // -2 for the border + const point filter_pos( 4, 8 ); // width of the legend - legwidth = width - 4 - BORDER_SPACE; - spopup.window( w_help, point( 4, 8 ), legwidth ) + legwidth = width - filter_pos.x * 2 - BORDER_SPACE; + // +1 for end-of-text cursor + spopup.window( w_help, filter_pos, filter_pos.x + legwidth + 1 ) .max_length( legwidth ) .context( ctxt ); ui.position_from_window( w_help ); @@ -1066,9 +1152,15 @@ action_id input_context::display_menu( const bool permit_execute_action ) legend += colorize( _( "Unbound keys" ), unbound_key ) + "\n"; legend += colorize( _( "Keybinding active only on this screen" ), local_key ) + "\n"; legend += colorize( _( "Keybinding active globally" ), global_key ) + "\n"; - legend += _( "Press - to remove keybinding\nPress + to add local keybinding\nPress = to add global keybinding\n" ); + legend += string_format( + _( "Press %c to remove keybinding\nPress %c to add local keybinding\nPress %c to add global keybinding\n" ), + fallback_keys.at( fallback_action::remove ), + fallback_keys.at( fallback_action::add_local ), + fallback_keys.at( fallback_action::add_global ) ); if( permit_execute_action ) { - legend += _( "Press . to execute action\n" ); + legend += string_format( + _( "Press %c to execute action\n" ), + fallback_keys.at( fallback_action::execute ) ); } std::vector filtered_registered_actions = org_registered_actions; @@ -1076,7 +1168,7 @@ action_id input_context::display_menu( const bool permit_execute_action ) std::string action; int raw_input_char = 0; - const auto redraw = [&]( const ui_adaptor & ) { + const auto redraw = [&]( ui_adaptor & ui ) { werase( w_help ); draw_border( w_help, BORDER_COLOR, _( "Keybindings" ), c_light_red ); draw_scrollbar( w_help, scroll_offset, display_height, @@ -1123,9 +1215,12 @@ action_id input_context::display_menu( const bool permit_execute_action ) mvwprintz( w_help, point( 52, i + 10 ), col, "%s", get_desc( action_id ) ); } - // spopup.query_string() will call wnoutrefresh( w_help ) + // spopup.query_string() will call wnoutrefresh( w_help ), and should + // be called last to position the cursor at the correct place in the curses build. spopup.text( filter_phrase ); spopup.query_string( false, true ); + // Record cursor immediately after spopup drawing + ui.record_term_cursor(); }; ui.on_redraw( redraw ); @@ -1141,28 +1236,81 @@ action_id input_context::display_menu( const bool permit_execute_action ) action = ctxt.handle_input(); } raw_input_char = ctxt.get_raw_input().get_first_input(); + for( const std::pair &v : fallback_keys ) { + if( v.second == raw_input_char ) { + action.clear(); + } + } filtered_registered_actions = filter_strings_by_phrase( org_registered_actions, filter_phrase ); if( scroll_offset > filtered_registered_actions.size() ) { scroll_offset = 0; } - if( filtered_registered_actions.empty() && action != "QUIT" ) { - continue; - } - // In addition to the modifiable hotkeys, we also check for hardcoded // keys, e.g. '+', '-', '=', '.' in order to prevent the user from // entering an unrecoverable state. - if( action == "ADD_LOCAL" || raw_input_char == '+' ) { - status = s_add; - } else if( action == "ADD_GLOBAL" || raw_input_char == '=' ) { - status = s_add_global; - } else if( action == "REMOVE" || raw_input_char == '-' ) { - status = s_remove; - } else if( ( action == "EXECUTE" || raw_input_char == '.' ) && permit_execute_action ) { - status = s_execute; - } else if( action == "ANY_INPUT" ) { + if( action == "ADD_LOCAL" + || raw_input_char == fallback_keys.at( fallback_action::add_local ) ) { + if( !filtered_registered_actions.empty() ) { + status = s_add; + } + } else if( action == "ADD_GLOBAL" + || raw_input_char == fallback_keys.at( fallback_action::add_global ) ) { + if( !filtered_registered_actions.empty() ) { + status = s_add_global; + } + } else if( action == "REMOVE" + || raw_input_char == fallback_keys.at( fallback_action::remove ) ) { + if( !filtered_registered_actions.empty() ) { + status = s_remove; + } + } else if( ( action == "EXECUTE" + || raw_input_char == fallback_keys.at( fallback_action::execute ) ) + && permit_execute_action ) { + if( !filtered_registered_actions.empty() ) { + status = s_execute; + } + } else if( action == "DOWN" ) { + if( !filtered_registered_actions.empty() + && filtered_registered_actions.size() > display_height + && scroll_offset < filtered_registered_actions.size() - display_height ) { + scroll_offset++; + } + } else if( action == "UP" ) { + if( !filtered_registered_actions.empty() + && scroll_offset > 0 ) { + scroll_offset--; + } + } else if( action == "PAGE_DOWN" ) { + if( filtered_registered_actions.empty() ) { + // do nothing + } else if( scroll_offset + display_height < filtered_registered_actions.size() ) { + scroll_offset += std::min( display_height, filtered_registered_actions.size() - + display_height - scroll_offset ); + } else if( filtered_registered_actions.size() > display_height ) { + scroll_offset = 0; + } + } else if( action == "PAGE_UP" ) { + if( filtered_registered_actions.empty() ) { + // do nothing + } else if( scroll_offset >= display_height ) { + scroll_offset -= display_height; + } else if( scroll_offset > 0 ) { + scroll_offset = 0; + } else if( filtered_registered_actions.size() > display_height ) { + scroll_offset = filtered_registered_actions.size() - display_height; + } + } else if( action == "QUIT" ) { + if( status != s_show ) { + status = s_show; + } else { + break; + } + } else if( action == "HELP_KEYBINDINGS" ) { + // update available hotkeys in case they've changed + hotkeys = ctxt.get_available_single_char_hotkeys( display_help_hotkeys ); + } else if( !filtered_registered_actions.empty() && status != s_show ) { const size_t hotkey_index = hotkeys.find_first_of( raw_input_char ); if( hotkey_index == std::string::npos ) { continue; @@ -1240,39 +1388,6 @@ action_id input_context::display_menu( const bool permit_execute_action ) break; } status = s_show; - } else if( action == "DOWN" ) { - if( filtered_registered_actions.size() > display_height && - scroll_offset < filtered_registered_actions.size() - display_height ) { - scroll_offset++; - } - } else if( action == "UP" ) { - if( scroll_offset > 0 ) { - scroll_offset--; - } - } else if( action == "PAGE_DOWN" ) { - if( scroll_offset + display_height < filtered_registered_actions.size() ) { - scroll_offset += std::min( display_height, filtered_registered_actions.size() - - display_height - scroll_offset ); - } else if( filtered_registered_actions.size() > display_height ) { - scroll_offset = 0; - } - } else if( action == "PAGE_UP" ) { - if( scroll_offset >= display_height ) { - scroll_offset -= display_height; - } else if( scroll_offset > 0 ) { - scroll_offset = 0; - } else if( filtered_registered_actions.size() > display_height ) { - scroll_offset = filtered_registered_actions.size() - display_height; - } - } else if( action == "QUIT" ) { - if( status != s_show ) { - status = s_show; - } else { - break; - } - } else if( action == "HELP_KEYBINDINGS" ) { - // update available hotkeys in case they've changed - hotkeys = ctxt.get_available_single_char_hotkeys( display_help_hotkeys ); } } @@ -1308,13 +1423,13 @@ void input_manager::wait_for_any_key() while( true ) { const input_event evt = inp_mngr.get_input_event(); switch( evt.type ) { - case CATA_INPUT_KEYBOARD: + case input_event_t::keyboard: if( !evt.sequence.empty() ) { return; } break; // errors are accepted as well to avoid an infinite loop - case CATA_INPUT_ERROR: + case input_event_t::error: return; default: break; @@ -1393,7 +1508,8 @@ std::string input_context::press_x( const std::string &action_id, const std::str } // TODO: merge this with input_context::get_desc -std::string input_context::press_x( const std::string &action_id, const std::string &key_bound_pre, +std::string input_context::press_x( const std::string &action_id, + const std::string &key_bound_pre, const std::string &key_bound_suf, const std::string &key_unbound ) const { if( action_id == "ANY_INPUT" ) { diff --git a/src/input.h b/src/input.h index 4b65c1e7b3b7..ac50b36803ab 100644 --- a/src/input.h +++ b/src/input.h @@ -54,6 +54,17 @@ inline constexpr bool IS_F_KEY( const int key ) { return key >= KEY_F( F_KEY_NUM_BEG ) && key <= KEY_F( F_KEY_NUM_END ); } +/** @return true if the given character is in the range of basic ASCII control characters */ +inline constexpr bool IS_CTRL_CHAR( const int key ) +{ + // https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Basic_ASCII_control_codes + return key >= 0 && key < ' '; +} +/** @return true if the given character is an ASCII control char but should not be rendered with "CTRL+" */ +inline constexpr bool IS_NAMED_CTRL_CHAR( const int key ) +{ + return key == '\t' || key == '\n' || key == KEY_ESCAPE || key == KEY_BACKSPACE; +} inline constexpr int KEY_NUM( const int n ) { return 0x30 + n; /* Numbers 0, 1, ..., 9 */ @@ -72,12 +83,12 @@ std::string get_input_string_from_file( const std::string &fname = "input.txt" ) enum mouse_buttons { MOUSE_BUTTON_LEFT = 1, MOUSE_BUTTON_RIGHT, SCROLLWHEEL_UP, SCROLLWHEEL_DOWN, MOUSE_MOVE }; -enum input_event_t { - CATA_INPUT_ERROR, - CATA_INPUT_TIMEOUT, - CATA_INPUT_KEYBOARD, - CATA_INPUT_GAMEPAD, - CATA_INPUT_MOUSE +enum class input_event_t : int { + error, + timeout, + keyboard, + gamepad, + mouse }; /** @@ -112,7 +123,7 @@ struct input_event { #endif input_event() : edit_refresh( false ) { - type = CATA_INPUT_ERROR; + type = input_event_t::error; #if defined(__ANDROID__) shortcut_last_used_action_counter = 0; #endif @@ -574,7 +585,16 @@ class input_context * @param text The base text for action description * * @param evt_filter Only keys satisfying this function will be considered + * @param inline_fmt Action description format when a key is found in the + * text (for example "(a)ctive") + * @param separate_fmt Action description format when a key is not found + * in the text (for example "[X] active" or "[N/A] active") */ + std::string get_desc( const std::string &action_descriptor, + const std::string &text, + const input_event_filter &evt_filter, + const translation &inline_fmt, + const translation &separate_fmt ) const; std::string get_desc( const std::string &action_descriptor, const std::string &text, const input_event_filter &evt_filter = allow_all_keys ) const; @@ -680,7 +700,7 @@ class input_context * Sets input polling timeout as appropriate for the current interface system. * Use this method to set timeouts when using input_context, rather than calling * the old timeout() method or using input_manager::(re)set_timeout, as using - * this method will cause CATA_INPUT_TIMEOUT events to be generated correctly, + * this method will cause input_event_t::timeout events to be generated correctly, * and will reset timeout correctly when a new input context is entered. */ void set_timeout( int val ); diff --git a/src/item.cpp b/src/item.cpp index 4850df785ea5..fdce9ba2634a 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -1313,8 +1313,8 @@ item::sizing item::get_sizing( const Character &p ) const if( to_ignore ) { return sizing::ignore; } else { - const bool small = p.get_size() == MS_TINY; - const bool big = p.get_size() == MS_HUGE; + const bool small = p.get_size() == creature_size::tiny; + const bool big = p.get_size() == creature_size::huge; // due to the iterative nature of these features, something can fit and be undersized/oversized // but that is fine because we have separate logic to adjust encumberance per each. One day we diff --git a/src/item.h b/src/item.h index 0869959e4005..ffe99649022a 100644 --- a/src/item.h +++ b/src/item.h @@ -77,7 +77,6 @@ struct use_function; enum art_effect_passive : int; enum phase_id : int; enum body_part : int; -enum m_size : int; enum class side : int; class body_part_set; class map; diff --git a/src/iuse.cpp b/src/iuse.cpp index 44e118744cc0..a5ba432ad8ac 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -7438,7 +7438,8 @@ int iuse::camera( player *p, item *it, bool, const tripoint & ) monster &z = *mon; // shoot past small monsters and hallucinations - if( trajectory_point != aim_point && ( z.type->size <= MS_SMALL || z.is_hallucination() || + if( trajectory_point != aim_point && ( z.type->size <= creature_size::small || + z.is_hallucination() || z.type->in_species( HALLUCINATION ) ) ) { continue; } @@ -9171,13 +9172,13 @@ int iuse::capture_monster_act( player *p, item *it, bool, const tripoint &pos ) return 0; } } else { - if( !it->has_property( "monster_size_capacity" ) ) { - debugmsg( "%s has no monster_size_capacity.", it->tname() ); + if( !it->has_property( "creature_size_capacity" ) ) { + debugmsg( "%s has no creature_size_capacity.", it->tname() ); return 0; } - const std::string capacity = it->get_property_string( "monster_size_capacity" ); + const std::string capacity = it->get_property_string( "creature_size_capacity" ); if( Creature::size_map.count( capacity ) == 0 ) { - debugmsg( "%s has invalid monster_size_capacity %s.", + debugmsg( "%s has invalid creature_size_capacity %s.", it->tname(), capacity.c_str() ); return 0; } diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 5cd44e6b4c98..84ad8ad7557a 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -3261,7 +3261,7 @@ bool repair_item_actor::can_repair_target( player &pl, const item &fix, } const bool resizing_matters = fix.get_sizing( pl ) != item::sizing::ignore; - const bool small = pl.get_size() == MS_TINY; + const bool small = pl.get_size() == creature_size::tiny; const bool can_resize = small != fix.has_flag( flag_UNDERSIZE ); if( can_be_refitted && resizing_matters && can_resize ) { return true; @@ -3344,7 +3344,7 @@ repair_item_actor::repair_type repair_item_actor::default_action( const item &fi } Character &player_character = get_player_character(); - const bool smol = player_character.get_size() == MS_TINY; + const bool smol = player_character.get_size() == creature_size::tiny; const bool is_undersized = fix.has_flag( flag_UNDERSIZE ); const bool is_oversized = fix.has_flag( flag_OVERSIZE ); diff --git a/src/loading_ui.cpp b/src/loading_ui.cpp index abcd927fa605..5e5c8a120ab6 100644 --- a/src/loading_ui.cpp +++ b/src/loading_ui.cpp @@ -49,8 +49,8 @@ void loading_ui::init() menu->reposition( ui ); } ); menu->reposition( *ui ); - ui->on_redraw( [this]( const ui_adaptor & ) { - menu->show(); + ui->on_redraw( [this]( ui_adaptor & ui ) { + menu->show( ui ); } ); } } diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 4d49e6d49219..254b5d0aee96 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -6072,7 +6072,7 @@ void map::draw_connections( const mapgendata &dat ) } // finally, any terrain with SIDEWALKS should contribute sidewalks to neighboring diagonal roads - if( terrain_type->has_flag( has_sidewalk ) ) { + if( terrain_type->has_flag( oter_flags::has_sidewalk ) ) { for( int dir = 4; dir < 8; dir++ ) { // NE SE SW NW bool n_roads_nesw[4] = {}; int n_num_dirs = terrain_type_to_nesw_array( oter_id( dat.t_nesw[dir] ), n_roads_nesw ); diff --git a/src/mapgen_functions.cpp b/src/mapgen_functions.cpp index d3fb2eccf643..b3145931ce84 100644 --- a/src/mapgen_functions.cpp +++ b/src/mapgen_functions.cpp @@ -492,7 +492,7 @@ void mapgen_road( mapgendata &dat ) int neighbor_sidewalks = 0; // N E S W NE SE SW NW for( int dir = 0; dir < 8; dir++ ) { - sidewalks_neswx[dir] = dat.t_nesw[dir]->has_flag( has_sidewalk ); + sidewalks_neswx[dir] = dat.t_nesw[dir]->has_flag( oter_flags::has_sidewalk ); neighbor_sidewalks += sidewalks_neswx[dir]; } @@ -841,7 +841,7 @@ void mapgen_subway( mapgendata &dat ) // N E S W for( int dir = 0; dir < 4; dir++ ) { - if( dat.t_nesw[dir]->has_flag( subway_connection ) && !subway_nesw[dir] ) { + if( dat.t_nesw[dir]->has_flag( oter_flags::subway_connection ) && !subway_nesw[dir] ) { num_dirs++; subway_nesw[dir] = true; } @@ -856,7 +856,7 @@ void mapgen_subway( mapgendata &dat ) } if( dat.t_nesw[dir]->get_type_id().str() != "subway" && - !dat.t_nesw[dir]->has_flag( subway_connection ) ) { + !dat.t_nesw[dir]->has_flag( oter_flags::subway_connection ) ) { continue; } // n_* contain details about the neighbor being considered @@ -864,7 +864,7 @@ void mapgen_subway( mapgendata &dat ) // TODO: figure out how to call this function without creating a new oter_id object int n_num_dirs = terrain_type_to_nesw_array( dat.t_nesw[dir], n_subway_nesw ); for( int dir = 0; dir < 4; dir++ ) { - if( dat.t_nesw[dir]->has_flag( subway_connection ) && !n_subway_nesw[dir] ) { + if( dat.t_nesw[dir]->has_flag( oter_flags::subway_connection ) && !n_subway_nesw[dir] ) { n_num_dirs++; n_subway_nesw[dir] = true; } diff --git a/src/melee.cpp b/src/melee.cpp index 52b140360fb6..ca4829f19483 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -668,7 +668,7 @@ void Character::reach_attack( const tripoint &p ) map &here = get_map(); Creature *critter = g->critter_at( p ); // Original target size, used when there are monsters in front of our target - int target_size = critter != nullptr ? ( critter->get_size() + 1 ) : 2; + const int target_size = critter != nullptr ? static_cast( critter->get_size() + 1 ) : 2; // Reset last target pos as_player()->last_target_pos = std::nullopt; // Max out recoil diff --git a/src/monattack.cpp b/src/monattack.cpp index 2a676391c30e..5eef3c264b1a 100644 --- a/src/monattack.cpp +++ b/src/monattack.cpp @@ -2281,21 +2281,21 @@ static bool blobify( monster &blob, monster &target ) } switch( target.get_size() ) { - case MS_TINY: + case creature_size::tiny: // Just consume it target.set_hp( 0 ); blob.set_speed_base( blob.get_speed_base() + 5 ); return false; - case MS_SMALL: + case creature_size::small: target.poly( mon_blob_small ); break; - case MS_MEDIUM: + case creature_size::medium: target.poly( mon_blob ); break; - case MS_LARGE: + case creature_size::large: target.poly( mon_blob_large ); break; - case MS_HUGE: + case creature_size::huge: // No polymorphing huge stuff target.add_effect( effect_slimed, rng( 2_turns, 10_turns ) ); break; diff --git a/src/mondeath.cpp b/src/mondeath.cpp index 220a4ef06338..75f4f0cd8f86 100644 --- a/src/mondeath.cpp +++ b/src/mondeath.cpp @@ -210,7 +210,7 @@ void mdeath::splatter( monster &z ) const auto area = here.points_in_radius( z.pos(), 1 ); int number_of_gibs = std::min( std::floor( corpse_damage ) - 1, 1 + max_hp / 5.0f ); - if( pulverized && z.type->size >= MS_MEDIUM ) { + if( pulverized && z.type->size >= creature_size::medium ) { number_of_gibs += rng( 1, 6 ); sfx::play_variant_sound( "mon_death", "zombie_gibbed", sfx::get_heard_volume( z.pos() ) ); } @@ -587,19 +587,19 @@ void mdeath::explode( monster &z ) { int size = 0; switch( z.type->size ) { - case MS_TINY: + case creature_size::tiny: size = 4; break; - case MS_SMALL: + case creature_size::small: size = 8; break; - case MS_MEDIUM: + case creature_size::medium: size = 14; break; - case MS_LARGE: + case creature_size::large: size = 20; break; - case MS_HUGE: + case creature_size::huge: size = 26; break; default: diff --git a/src/monmove.cpp b/src/monmove.cpp index 647eb92e5f2a..60a5d4f2e75a 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -150,7 +150,7 @@ bool monster::will_move_to( const tripoint &p ) const return false; } - if( get_size() > MS_MEDIUM && g->m.has_flag_ter( TFLAG_SMALL_PASSAGE, p ) ) { + if( get_size() > creature_size::medium && g->m.has_flag_ter( TFLAG_SMALL_PASSAGE, p ) ) { return false; // if a large critter, can't move through tight passages } @@ -191,7 +191,7 @@ bool monster::will_move_to( const tripoint &p ) const } // Don't enter open pits ever unless tiny, can fly or climb well - if( !( type->size == MS_TINY || can_climb() ) && + if( !( type->size == creature_size::tiny || can_climb() ) && ( target == t_pit || target == t_pit_spiked || target == t_pit_glass ) ) { return false; } @@ -201,7 +201,7 @@ bool monster::will_move_to( const tripoint &p ) const if( attitude( &g->u ) != MATT_ATTACK ) { // Sharp terrain is ignored while attacking if( avoid_simple && g->m.has_flag( "SHARP", p ) && - !( type->size == MS_TINY || flies() ) ) { + !( type->size == creature_size::tiny || flies() ) ) { return false; } } @@ -1203,18 +1203,18 @@ void monster::footsteps( const tripoint &p ) volume = 10; } switch( type->size ) { - case MS_TINY: + case creature_size::tiny: volume = 0; // No sound for the tinies break; - case MS_SMALL: + case creature_size::small: volume /= 3; break; - case MS_MEDIUM: + case creature_size::medium: break; - case MS_LARGE: + case creature_size::large: volume *= 1.5; break; - case MS_HUGE: + case creature_size::huge: volume *= 2; break; default: @@ -1689,7 +1689,7 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, return true; } - if( type->size != MS_TINY && on_ground ) { + if( type->size != creature_size::tiny && on_ground ) { const int sharp_damage = rng( 1, 10 ); const int rough_damage = rng( 1, 2 ); if( g->m.has_flag( "SHARP", pos() ) && !one_in( 4 ) && @@ -1725,19 +1725,19 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter, if( digging() && g->m.has_flag( "DIGGABLE", pos() ) ) { int factor = 0; switch( type->size ) { - case MS_TINY: + case creature_size::tiny: factor = 100; break; - case MS_SMALL: + case creature_size::small: factor = 30; break; - case MS_MEDIUM: + case creature_size::medium: factor = 6; break; - case MS_LARGE: + case creature_size::large: factor = 3; break; - case MS_HUGE: + case creature_size::huge: factor = 1; break; default: @@ -1989,14 +1989,14 @@ void monster::knock_back_to( const tripoint &to ) // First, see if we hit another monster if( monster *const z = g->critter_at( to ) ) { - apply_damage( z, bodypart_id( "torso" ), z->type->size ); + apply_damage( z, bodypart_id( "torso" ), static_cast( z->type->size ) ); add_effect( effect_stunned, 1_turns ); if( type->size > 1 + z->type->size ) { z->knock_back_from( pos() ); // Chain reaction! - z->apply_damage( this, bodypart_id( "torso" ), type->size ); + z->apply_damage( this, bodypart_id( "torso" ), static_cast( type->size ) ); z->add_effect( effect_stunned, 1_turns ); } else if( type->size > z->type->size ) { - z->apply_damage( this, bodypart_id( "torso" ), type->size ); + z->apply_damage( this, bodypart_id( "torso" ), static_cast( type->size ) ); z->add_effect( effect_stunned, 1_turns ); } z->check_dead_state(); @@ -2011,7 +2011,8 @@ void monster::knock_back_to( const tripoint &to ) if( npc *const p = g->critter_at( to ) ) { apply_damage( p, bodypart_id( "torso" ), 3 ); add_effect( effect_stunned, 1_turns ); - p->deal_damage( this, bodypart_id( "torso" ), damage_instance( DT_BASH, type->size ) ); + p->deal_damage( this, bodypart_id( "torso" ), damage_instance( DT_BASH, + static_cast( type->size ) ) ); if( u_see ) { add_msg( _( "The %1$s bounces off %2$s!" ), name(), p->name ); } @@ -2033,7 +2034,7 @@ void monster::knock_back_to( const tripoint &to ) if( g->m.impassable( to ) ) { // It's some kind of wall. - apply_damage( nullptr, bodypart_id( "torso" ), type->size ); + apply_damage( nullptr, bodypart_id( "torso" ), static_cast( type->size ) ); add_effect( effect_stunned, 2_turns ); if( u_see ) { add_msg( _( "The %1$s bounces off a %2$s." ), name(), @@ -2131,10 +2132,10 @@ void monster::shove_vehicle( const tripoint &remote_destination, float shove_damage_min = 0.00F; float shove_damage_max = 0.00F; switch( this->get_size() ) { - case MS_TINY: - case MS_SMALL: + case creature_size::tiny: + case creature_size::small: break; - case MS_MEDIUM: + case creature_size::medium: if( veh_mass < 500_kilogram ) { shove_moves_minimal = 150; shove_veh_mass_moves_factor = 20; @@ -2143,7 +2144,7 @@ void monster::shove_vehicle( const tripoint &remote_destination, shove_damage_max = 0.01F; } break; - case MS_LARGE: + case creature_size::large: if( veh_mass < 1000_kilogram ) { shove_moves_minimal = 100; shove_veh_mass_moves_factor = 8; @@ -2152,7 +2153,7 @@ void monster::shove_vehicle( const tripoint &remote_destination, shove_damage_max = 0.03F; } break; - case MS_HUGE: + case creature_size::huge: if( veh_mass < 2000_kilogram ) { shove_moves_minimal = 50; shove_veh_mass_moves_factor = 4; diff --git a/src/monster.cpp b/src/monster.cpp index 2505be65216e..ba3dc32634aa 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -137,12 +137,12 @@ struct pathfinding_settings; // The rough formula is 2^(-x), e.g. for x = 5 it's 0.03125 (~ 3%). #define UPGRADE_MAX_ITERS 5 -static const std::map size_names { - { m_size::MS_TINY, to_translation( "size adj", "tiny" ) }, - { m_size::MS_SMALL, to_translation( "size adj", "small" ) }, - { m_size::MS_MEDIUM, to_translation( "size adj", "medium" ) }, - { m_size::MS_LARGE, to_translation( "size adj", "large" ) }, - { m_size::MS_HUGE, to_translation( "size adj", "huge" ) }, +static const std::map size_names { + { creature_size::tiny, to_translation( "size adj", "tiny" ) }, + { creature_size::small, to_translation( "size adj", "small" ) }, + { creature_size::medium, to_translation( "size adj", "medium" ) }, + { creature_size::large, to_translation( "size adj", "large" ) }, + { creature_size::huge, to_translation( "size adj", "huge" ) }, }; static const std::map> attitude_names { @@ -2229,16 +2229,16 @@ float monster::stability_roll() const { int size_bonus = 0; switch( type->size ) { - case MS_TINY: + case creature_size::tiny: size_bonus -= 7; break; - case MS_SMALL: + case creature_size::small: size_bonus -= 3; break; - case MS_LARGE: + case creature_size::large: size_bonus += 5; break; - case MS_HUGE: + case creature_size::huge: size_bonus += 10; break; default: @@ -2293,15 +2293,15 @@ float monster::fall_damage_mod() const } switch( type->size ) { - case MS_TINY: + case creature_size::tiny: return 0.2f; - case MS_SMALL: + case creature_size::small: return 0.6f; - case MS_MEDIUM: + case creature_size::medium: return 1.0f; - case MS_LARGE: + case creature_size::large: return 1.4f; - case MS_HUGE: + case creature_size::huge: return 2.0f; default: return 1.0f; @@ -2987,9 +2987,9 @@ field_type_id monster::gibType() const return type->gibType(); } -m_size monster::get_size() const +creature_size monster::get_size() const { - return m_size( type->size ); + return creature_size( type->size ); } units::mass monster::get_weight() const diff --git a/src/monster.h b/src/monster.h index e333bd365788..aa1adf5e6b69 100644 --- a/src/monster.h +++ b/src/monster.h @@ -128,7 +128,7 @@ class monster : public Creature, public location_visitable void reproduce(); void refill_udders(); void spawn( const tripoint &p ); - m_size get_size() const override; + creature_size get_size() const override; units::mass get_weight() const override; units::mass weight_capacity() const override; units::volume get_volume() const; diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 3bb7dc04ecf3..83aa515f2d6d 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -265,18 +265,18 @@ static int calc_bash_skill( const mtype &t ) return ret; } -static m_size volume_to_size( const units::volume &vol ) +static creature_size volume_to_size( const units::volume &vol ) { if( vol <= 7500_ml ) { - return MS_TINY; + return creature_size::tiny; } else if( vol <= 46250_ml ) { - return MS_SMALL; + return creature_size::small; } else if( vol <= 77500_ml ) { - return MS_MEDIUM; + return creature_size::medium; } else if( vol <= 483750_ml ) { - return MS_LARGE; + return creature_size::large; } - return MS_HUGE; + return creature_size::huge; } struct monster_adjustment { diff --git a/src/mtype.cpp b/src/mtype.cpp index cddf4dfc98a1..9556997f3ae7 100644 --- a/src/mtype.cpp +++ b/src/mtype.cpp @@ -31,7 +31,7 @@ mtype::mtype() name = pl_translation( "human", "humans" ); sym = " "; color = c_white; - size = MS_MEDIUM; + size = creature_size::medium; volume = 62499_ml; weight = 81499_gram; mat = { material_id( "flesh" ) }; diff --git a/src/mtype.h b/src/mtype.h index 2896a451d1f1..48d71aaa5df1 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -27,7 +27,7 @@ struct species_type; template struct enum_traits; enum body_part : int; -enum m_size : int; +enum creature_size : int; using mon_action_death = void ( * )( monster & ); using mon_action_attack = bool ( * )( monster * ); @@ -73,7 +73,7 @@ enum m_flag : int { MF_STUMBLES, // Stumbles in its movement MF_WARM, // Warm blooded MF_NOHEAD, // Headshots not allowed! - MF_HARDTOSHOOT, // It's one size smaller for ranged attacks, no less then MS_TINY + MF_HARDTOSHOOT, // It's one size smaller for ranged attacks, no less then creature_size::tiny MF_GRABS, // Its attacks may grab us! MF_BASHES, // Bashes down doors MF_DESTROYS, // Bashes down walls and more @@ -262,7 +262,7 @@ struct mtype { mfaction_id default_faction; bodytype_id bodytype; nc_color color = c_white; - m_size size; + creature_size size; units::volume volume; units::mass weight; phase_id phase; diff --git a/src/mutation.cpp b/src/mutation.cpp index 5440b5f48c5c..1c5e18d9a83a 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -238,7 +238,7 @@ const resistances &mutation_branch::damage_resistance( body_part bp ) const void Character::recalculate_size() { - size_class = MS_MEDIUM; + size_class = creature_size::medium; // Only one size-changing mutation is expected, so it will only use the first one it finds. for( const mutation_branch *mut : cached_mutations ) { if( mut->body_size ) { diff --git a/src/mutation.h b/src/mutation.h index eb173e458c35..8bfd1207e298 100644 --- a/src/mutation.h +++ b/src/mutation.h @@ -227,7 +227,7 @@ struct mutation_branch { std::set no_cbm_on_bp; // Body size from mutations, e.g. large, small, etc. - std::optional body_size; + std::optional body_size; // amount of mana added or subtracted from max float mana_modifier = 0.0f; diff --git a/src/mutation_ui.cpp b/src/mutation_ui.cpp index 81a8c927dc71..3fd2d298845f 100644 --- a/src/mutation_ui.cpp +++ b/src/mutation_ui.cpp @@ -354,7 +354,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) bool handled = false; const std::string action = ctxt.handle_input(); const input_event evt = ctxt.get_raw_input(); - if( evt.type == CATA_INPUT_KEYBOARD && !evt.sequence.empty() ) { + if( evt.type == input_event_t::keyboard && !evt.sequence.empty() ) { const int ch = evt.get_first_input(); if( ch == ' ' ) { //skip if space is pressed (space is used as an empty hotkey) continue; @@ -375,7 +375,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) while( !pop_exit ) { const query_popup::result ret = pop.query(); bool pop_handled = false; - if( ret.evt.type == CATA_INPUT_KEYBOARD && !ret.evt.sequence.empty() ) { + if( ret.evt.type == input_event_t::keyboard && !ret.evt.sequence.empty() ) { const int newch = ret.evt.get_first_input(); if( mutation_chars.valid( newch ) ) { const std::optional other_mut_id = trait_by_invlet( who.my_mutations, newch ); @@ -392,7 +392,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) if( ret.action == "QUIT" ) { pop_exit = true; } else if( ret.action != "HELP_KEYBINDINGS" && - ret.evt.type == CATA_INPUT_KEYBOARD ) { + ret.evt.type == input_event_t::keyboard ) { popup( _( "Invalid mutation letter. Only those characters are valid:\n\n%s" ), mutation_chars.get_allowed_chars() ); } @@ -539,7 +539,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) while( !pop_exit ) { const query_popup::result ret = pop.query(); bool pop_handled = false; - if( ret.evt.type == CATA_INPUT_KEYBOARD && !ret.evt.sequence.empty() ) { + if( ret.evt.type == input_event_t::keyboard && !ret.evt.sequence.empty() ) { const int newch = ret.evt.get_first_input(); if( mutation_chars.valid( newch ) ) { const std::optional other_mut_id = trait_by_invlet( who.my_mutations, newch ); @@ -560,7 +560,7 @@ detail::mutations_ui_result detail::show_mutations_ui_internal( Character &who ) if( ret.action == "QUIT" ) { pop_exit = true; } else if( ret.action != "HELP_KEYBINDINGS" && - ret.evt.type == CATA_INPUT_KEYBOARD ) { + ret.evt.type == input_event_t::keyboard ) { popup( _( "Invalid mutation letter. Only those characters are valid:\n\n%s" ), mutation_chars.get_allowed_chars() ); } diff --git a/src/ncurses_def.cpp b/src/ncurses_def.cpp index 5b7d9cfe8189..223ed7000ae0 100644 --- a/src/ncurses_def.cpp +++ b/src/ncurses_def.cpp @@ -215,6 +215,7 @@ void catacurses::init_pair( const short pair, const base_color f, const base_col OK, "init_pair" ); } +catacurses::window catacurses::newscr; catacurses::window catacurses::stdscr; void catacurses::resizeterm() @@ -237,6 +238,10 @@ void catacurses::init_interface() if( !stdscr ) { throw std::runtime_error( "initscr failed" ); } + newscr = window( std::shared_ptr( ::newscr, []( void *const ) { } ) ); + if( !newscr ) { + throw std::runtime_error( "null newscr" ); + } #if !defined(__CYGWIN__) // ncurses mouse registration mousemask( BUTTON1_CLICKED | BUTTON3_CLICKED | REPORT_MOUSE_POSITION, nullptr ); @@ -303,9 +308,9 @@ input_event input_manager::get_input_event() rval = input_event(); if( key == ERR ) { if( input_timeout > 0 ) { - rval.type = CATA_INPUT_TIMEOUT; + rval.type = input_event_t::timeout; } else { - rval.type = CATA_INPUT_ERROR; + rval.type = input_event_t::error; } // ncurses mouse handling } else if( key == KEY_RESIZE ) { @@ -313,7 +318,7 @@ input_event input_manager::get_input_event() } else if( key == KEY_MOUSE ) { MEVENT event; if( getmouse( &event ) == OK ) { - rval.type = CATA_INPUT_MOUSE; + rval.type = input_event_t::mouse; rval.mouse_pos = point( event.x, event.y ); if( event.bstate & BUTTON1_CLICKED ) { rval.add_input( MOUSE_BUTTON_LEFT ); @@ -326,17 +331,17 @@ input_event input_manager::get_input_event() set_timeout( input_timeout ); } } else { - rval.type = CATA_INPUT_ERROR; + rval.type = input_event_t::error; } } else { - rval.type = CATA_INPUT_ERROR; + rval.type = input_event_t::error; } } else { if( key == 127 ) { // == Unicode DELETE previously_pressed_key = KEY_BACKSPACE; - return input_event( KEY_BACKSPACE, CATA_INPUT_KEYBOARD ); + return input_event( KEY_BACKSPACE, input_event_t::keyboard ); } - rval.type = CATA_INPUT_KEYBOARD; + rval.type = input_event_t::keyboard; rval.text.append( 1, static_cast( key ) ); // Read the UTF-8 sequence (if any) if( key < 127 ) { @@ -354,7 +359,7 @@ input_event input_manager::get_input_event() // Other control character, etc. - no text at all, return an event // without the text property previously_pressed_key = key; - return input_event( key, CATA_INPUT_KEYBOARD ); + return input_event( key, input_event_t::keyboard ); } // Now we have loaded an UTF-8 sequence (possibly several bytes) // but we should only return *one* key, so return the code point of it. @@ -363,7 +368,7 @@ input_event input_manager::get_input_event() // Invalid UTF-8 sequence, this should never happen, what now? // Maybe return any error instead? previously_pressed_key = key; - return input_event( key, CATA_INPUT_KEYBOARD ); + return input_event( key, input_event_t::keyboard ); } previously_pressed_key = cp; // for compatibility only add the first byte, not the code point @@ -378,7 +383,7 @@ input_event input_manager::get_input_event() void input_manager::set_timeout( const int delay ) { timeout( delay ); - // Use this to determine when curses should return a CATA_INPUT_TIMEOUT event. + // Use this to determine when curses should return a input_event_t::timeout event. input_timeout = delay; } diff --git a/src/npctrade.cpp b/src/npctrade.cpp index ba266beb71dd..acd043de509a 100644 --- a/src/npctrade.cpp +++ b/src/npctrade.cpp @@ -420,7 +420,7 @@ void trading_window::show_item_data( size_t offset, exit = true; } else if( action == "ANY_INPUT" ) { const input_event evt = ctxt.get_raw_input(); - if( evt.type != CATA_INPUT_KEYBOARD || evt.sequence.empty() ) { + if( evt.type != input_event_t::keyboard || evt.sequence.empty() ) { continue; } size_t help = evt.get_first_input(); @@ -568,7 +568,7 @@ bool trading_window::perform_trade( npc &np, const std::string &deal ) confirm = false; } else if( action == "ANY_INPUT" ) { const input_event evt = ctxt.get_raw_input(); - if( evt.type != CATA_INPUT_KEYBOARD || evt.sequence.empty() ) { + if( evt.type != input_event_t::keyboard || evt.sequence.empty() ) { continue; } size_t ch = evt.get_first_input(); diff --git a/src/omdata.h b/src/omdata.h index 3208e4b52633..9b9df4483b07 100644 --- a/src/omdata.h +++ b/src/omdata.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -18,6 +17,7 @@ #include "color.h" #include "numeric_interval.h" #include "coordinates.h" +#include "enum_bitset.h" #include "int_id.h" #include "om_direction.h" #include "mapgen_parameter.h" @@ -84,7 +84,7 @@ struct overmap_static_spawns : public overmap_spawns { }; //terrain flags enum! this is for tracking the indices of each flag. -enum oter_flags { +enum class oter_flags : int { known_down = 0, known_up, no_rotate, // this tile doesn't have four rotated versions (north, east, south, west) @@ -123,6 +123,11 @@ enum oter_flags { num_oter_flags }; +template<> +struct enum_traits { + static constexpr auto last = oter_flags::num_oter_flags; +}; + struct oter_type_t { public: static const oter_type_t null_type; @@ -155,7 +160,7 @@ struct oter_type_t { } void set_flag( oter_flags flag, bool value = true ) { - flags[flag] = value; + flags.set( flag, value ); } void load( const JsonObject &jo, const std::string &src ); @@ -163,11 +168,11 @@ struct oter_type_t { void finalize(); bool is_rotatable() const { - return !has_flag( no_rotate ) && !has_flag( line_drawing ); + return !has_flag( oter_flags::no_rotate ) && !has_flag( oter_flags::line_drawing ); } bool is_linear() const { - return has_flag( line_drawing ); + return has_flag( oter_flags::line_drawing ); } bool has_connections() const { @@ -179,7 +184,7 @@ struct oter_type_t { } private: - std::bitset flags; + enum_bitset flags; std::vector directional_peers; std::string connect_group; // Group for connection when rendering overmap tiles @@ -277,7 +282,7 @@ struct oter_t { } bool is_river() const { - return type->has_flag( river_tile ); + return type->has_flag( oter_flags::river_tile ); } bool is_wooded() const { @@ -288,11 +293,11 @@ struct oter_t { } bool is_lake() const { - return type->has_flag( lake ); + return type->has_flag( oter_flags::lake ); } bool is_lake_shore() const { - return type->has_flag( lake_shore ); + return type->has_flag( oter_flags::lake_shore ); } private: diff --git a/src/output.cpp b/src/output.cpp index 48f520a6de0f..8c1f345bf47f 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -779,7 +779,7 @@ int popup( const std::string &text, PopupFlags flags ) pop.context( "POPUP_WAIT" ); const auto &res = pop.query(); - if( res.evt.type == CATA_INPUT_KEYBOARD ) { + if( res.evt.type == input_event_t::keyboard ) { return res.evt.get_first_input(); } else { return UNKNOWN_UNICODE; diff --git a/src/overmap.cpp b/src/overmap.cpp index de6a76f4d2c2..27a72bec0c37 100644 --- a/src/overmap.cpp +++ b/src/overmap.cpp @@ -725,8 +725,8 @@ void oter_type_t::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "connect_group", connect_group, string_reader{} ); - if( has_flag( line_drawing ) ) { - if( has_flag( no_rotate ) ) { + if( has_flag( oter_flags::line_drawing ) ) { + if( has_flag( oter_flags::no_rotate ) ) { jo.throw_error( R"(Mutually exclusive flags: "NO_ROTATE" and "LINEAR".)" ); } @@ -766,7 +766,7 @@ void oter_type_t::finalize() for( om_direction::type dir : om_direction::all ) { register_terrain( oter_t( *this, dir ), static_cast( dir ), om_direction::size ); } - } else if( has_flag( line_drawing ) ) { + } else if( has_flag( oter_flags::line_drawing ) ) { for( size_t i = 0; i < om_lines::size; ++i ) { register_terrain( oter_t( *this, i ), i, om_lines::size ); } @@ -810,7 +810,7 @@ oter_id oter_type_t::get_rotated( om_direction::type dir ) const oter_id oter_type_t::get_linear( size_t n ) const { - if( !has_flag( line_drawing ) ) { + if( !has_flag( oter_flags::line_drawing ) ) { debugmsg( "Overmap terrain \"%s \" isn't drawn with lines.", id.c_str() ); return ot_null; } @@ -848,14 +848,14 @@ oter_t::oter_t( const oter_type_t &type, size_t line ) : std::string oter_t::get_mapgen_id() const { - return type->has_flag( line_drawing ) + return type->has_flag( oter_flags::line_drawing ) ? type->id.str() + om_lines::mapgen_suffixes[om_lines::all[line].mapgen] : type->id.str(); } oter_id oter_t::get_rotated( om_direction::type dir ) const { - return type->has_flag( line_drawing ) + return type->has_flag( oter_flags::line_drawing ) ? type->get_linear( om_lines::rotate( this->line, dir ) ) : type->get_rotated( om_direction::add( this->dir, dir ) ); } diff --git a/src/overmap.h b/src/overmap.h index cf305a613408..db144b5ba2b1 100644 --- a/src/overmap.h +++ b/src/overmap.h @@ -114,41 +114,41 @@ struct map_layer { }; static const std::map oter_flags_map = { - { "KNOWN_DOWN", known_down }, - { "KNOWN_UP", known_up }, - { "RIVER", river_tile }, - { "SIDEWALK", has_sidewalk }, - { "NO_ROTATE", no_rotate }, - { "IGNORE_ROTATION_FOR_ADJACENCY", ignore_rotation_for_adjacency }, - { "LINEAR", line_drawing }, - { "SUBWAY", subway_connection }, - { "LAKE", lake }, - { "LAKE_SHORE", lake_shore }, - { "GENERIC_LOOT", generic_loot }, - { "RISK_HIGH", risk_high }, - { "RISK_LOW", risk_low }, - { "SOURCE_AMMO", source_ammo }, - { "SOURCE_ANIMALS", source_animals }, - { "SOURCE_BOOKS", source_books }, - { "SOURCE_CHEMISTRY", source_chemistry }, - { "SOURCE_CLOTHING", source_clothing }, - { "SOURCE_CONSTRUCTION", source_construction }, - { "SOURCE_COOKING", source_cooking }, - { "SOURCE_DRINK", source_drink }, - { "SOURCE_ELECTRONICS", source_electronics }, - { "SOURCE_FABRICATION", source_fabrication }, - { "SOURCE_FARMING", source_farming }, - { "SOURCE_FOOD", source_food }, - { "SOURCE_FORAGE", source_forage }, - { "SOURCE_FUEL", source_fuel }, - { "SOURCE_GUN", source_gun }, - { "SOURCE_LUXURY", source_luxury }, - { "SOURCE_MEDICINE", source_medicine }, - { "SOURCE_PEOPLE", source_people }, - { "SOURCE_SAFETY", source_safety }, - { "SOURCE_TAILORING", source_tailoring }, - { "SOURCE_VEHICLES", source_vehicles }, - { "SOURCE_WEAPON", source_weapon } + { "KNOWN_DOWN", oter_flags::known_down }, + { "KNOWN_UP", oter_flags::known_up }, + { "RIVER", oter_flags::river_tile }, + { "SIDEWALK", oter_flags::has_sidewalk }, + { "NO_ROTATE", oter_flags::no_rotate }, + { "IGNORE_ROTATION_FOR_ADJACENCY", oter_flags::ignore_rotation_for_adjacency }, + { "LINEAR", oter_flags::line_drawing }, + { "SUBWAY", oter_flags::subway_connection }, + { "LAKE", oter_flags::lake }, + { "LAKE_SHORE", oter_flags::lake_shore }, + { "GENERIC_LOOT", oter_flags::generic_loot }, + { "RISK_HIGH", oter_flags::risk_high }, + { "RISK_LOW", oter_flags::risk_low }, + { "SOURCE_AMMO", oter_flags::source_ammo }, + { "SOURCE_ANIMALS", oter_flags::source_animals }, + { "SOURCE_BOOKS", oter_flags::source_books }, + { "SOURCE_CHEMISTRY", oter_flags::source_chemistry }, + { "SOURCE_CLOTHING", oter_flags::source_clothing }, + { "SOURCE_CONSTRUCTION", oter_flags::source_construction }, + { "SOURCE_COOKING", oter_flags::source_cooking }, + { "SOURCE_DRINK", oter_flags::source_drink }, + { "SOURCE_ELECTRONICS", oter_flags::source_electronics }, + { "SOURCE_FABRICATION", oter_flags::source_fabrication }, + { "SOURCE_FARMING", oter_flags::source_farming }, + { "SOURCE_FOOD", oter_flags::source_food }, + { "SOURCE_FORAGE", oter_flags::source_forage }, + { "SOURCE_FUEL", oter_flags::source_fuel }, + { "SOURCE_GUN", oter_flags::source_gun }, + { "SOURCE_LUXURY", oter_flags::source_luxury }, + { "SOURCE_MEDICINE", oter_flags::source_medicine }, + { "SOURCE_PEOPLE", oter_flags::source_people }, + { "SOURCE_SAFETY", oter_flags::source_safety }, + { "SOURCE_TAILORING", oter_flags::source_tailoring }, + { "SOURCE_VEHICLES", oter_flags::source_vehicles }, + { "SOURCE_WEAPON", oter_flags::source_weapon } }; template diff --git a/src/overmap_ui.cpp b/src/overmap_ui.cpp index 2319ae8384a7..5baa41a87e83 100644 --- a/src/overmap_ui.cpp +++ b/src/overmap_ui.cpp @@ -738,7 +738,8 @@ static tripoint_abs_omt show_notes_manager( const tripoint_abs_omt &origin ) return result; } -static void draw_ascii( const catacurses::window &w, +static void draw_ascii( ui_adaptor &ui, + const catacurses::window &w, const tripoint_abs_omt ¢er, const tripoint_abs_omt &/*orig*/, bool blink, @@ -1230,8 +1231,9 @@ static void draw_ascii( const catacurses::window &w, mvwputch( w, point( om_half_width + 1, om_half_height + 1 ), c_light_gray, LINE_XOOX ); } // Done with all drawing! - wmove( w, point( om_half_width, om_half_height ) ); wnoutrefresh( w ); + // Set cursor for screen readers + ui.set_cursor( w, point( om_half_width, om_half_height ) ); } static void draw_om_sidebar( @@ -1428,6 +1430,7 @@ tiles_redraw_info redraw_info; #endif static void draw( + ui_adaptor &ui, const tripoint_abs_omt ¢er, const tripoint_abs_omt &orig, bool blink, @@ -1439,7 +1442,7 @@ static void draw( { draw_om_sidebar( g->w_omlegend, center, orig, blink, fast_scroll, inp_ctxt, data ); if( !use_tiles || !use_tiles_overmap ) { - draw_ascii( g->w_overmap, center, orig, blink, show_explored, fast_scroll, inp_ctxt, data, + draw_ascii( ui, g->w_overmap, center, orig, blink, show_explored, fast_scroll, inp_ctxt, data, grids_data ); } else { #ifdef TILES @@ -1520,6 +1523,7 @@ static void create_note( const tripoint_abs_omt &curs ) } else if( input_popup.confirmed() ) { break; } + ui.invalidate_ui(); } while( true ); disable_ime(); @@ -1994,8 +1998,9 @@ static tripoint_abs_omt display( const tripoint_abs_omt &orig, std::chrono::time_point last_blink = std::chrono::steady_clock::now(); grids_draw_data grids_data; - ui.on_redraw( [&]( const ui_adaptor & ) { - draw( curs, orig, uistate.overmap_show_overlays, + + ui.on_redraw( [&]( ui_adaptor & ui ) { + draw( ui, curs, orig, uistate.overmap_show_overlays, show_explored, fast_scroll, &ictxt, data, grids_data ); } ); diff --git a/src/popup.cpp b/src/popup.cpp index 3944ebbdc40d..b875836a4978 100644 --- a/src/popup.cpp +++ b/src/popup.cpp @@ -303,9 +303,9 @@ query_popup::result query_popup::query_once() res.evt = ctxt.get_raw_input(); } while( // Always ignore mouse movement - ( res.evt.type == CATA_INPUT_MOUSE && res.evt.get_first_input() == MOUSE_MOVE ) || + ( res.evt.type == input_event_t::mouse && res.evt.get_first_input() == MOUSE_MOVE ) || // Ignore window losing focus in SDL - ( res.evt.type == CATA_INPUT_KEYBOARD && res.evt.sequence.empty() ) + ( res.evt.type == input_event_t::keyboard && res.evt.sequence.empty() ) ); if( cancel && res.action == "QUIT" ) { diff --git a/src/ranged.cpp b/src/ranged.cpp index fbb5a6d2e6c8..e4a654062dd4 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -514,18 +514,18 @@ target_handler::trajectory target_handler::mode_shaped( avatar &you, shape_facto return ui.run(); } -static double occupied_tile_fraction( m_size target_size ) +static double occupied_tile_fraction( creature_size target_size ) { switch( target_size ) { - case MS_TINY: + case creature_size::tiny: return 0.1; - case MS_SMALL: + case creature_size::small: return 0.25; - case MS_MEDIUM: + case creature_size::medium: return 0.5; - case MS_LARGE: + case creature_size::large: return 0.75; - case MS_HUGE: + case creature_size::huge: return 1.0; default: break; @@ -538,15 +538,15 @@ double Creature::ranged_target_size() const { if( has_flag( MF_HARDTOSHOOT ) ) { switch( get_size() ) { - case MS_TINY: - case MS_SMALL: - return occupied_tile_fraction( MS_TINY ); - case MS_MEDIUM: - return occupied_tile_fraction( MS_SMALL ); - case MS_LARGE: - return occupied_tile_fraction( MS_MEDIUM ); - case MS_HUGE: - return occupied_tile_fraction( MS_LARGE ); + case creature_size::tiny: + case creature_size::small: + return occupied_tile_fraction( creature_size::tiny ); + case creature_size::medium: + return occupied_tile_fraction( creature_size::small ); + case creature_size::large: + return occupied_tile_fraction( creature_size::medium ); + case creature_size::huge: + return occupied_tile_fraction( creature_size::large ); default: break; } @@ -970,9 +970,10 @@ int ranged::fire_gun( Character &who, const tripoint &target, int max_shots, ite // If user is currently able to fire a mounted gun freely, penalize dispersion // HEAVY_WEAPON_SUPPORT flag has highest penalty, Large mutants lower penalty, no penalty for Huge mutants. if( gun.has_flag( flag_MOUNTED_GUN ) && !can_use_heavy_weapon( shooter, here, shooter.pos() ) ) { - if( who.get_size() == MS_LARGE ) { + if( who.get_size() == creature_size::large ) { gun_recoil = gun_recoil * 2; - } else if( who.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) && ( who.get_size() <= MS_MEDIUM ) ) { + } else if( who.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) && + ( who.get_size() <= creature_size::medium ) ) { gun_recoil = gun_recoil * 3; } } @@ -1975,9 +1976,10 @@ dispersion_sources ranged::get_weapon_dispersion( const Character &who, const it // If user is currently able to fire a mounted gun freely, penalize dispersion // HEAVY_WEAPON_SUPPORT flag has highest penalty, Large mutants lower penalty, no penalty for Huge mutants. if( obj.has_flag( flag_MOUNTED_GUN ) && !can_use_heavy_weapon( who, get_map(), who.pos() ) ) { - if( who.get_size() == MS_LARGE ) { + if( who.get_size() == creature_size::large ) { dispersion.add_range( 500 ); - } else if( who.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) && ( who.get_size() <= MS_MEDIUM ) ) { + } else if( who.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) && + ( who.get_size() <= creature_size::medium ) ) { dispersion.add_range( 1000 ); } } @@ -3683,7 +3685,7 @@ void target_ui::panel_fire_mode_aim( int &text_y ) } const double target_size = dst_critter ? dst_critter->ranged_target_size() : - occupied_tile_fraction( m_size::MS_MEDIUM ); + occupied_tile_fraction( creature_size::medium ); item *load_loc = activity->reload_loc ? &*activity->reload_loc : nullptr; text_y = print_aim( *you, w_target, text_y, ctxt, *relevant->gun_current_mode(), @@ -3808,7 +3810,8 @@ auto ranged::gunmode_checks_weapon( avatar &you, const map &m, std::vectorhas_flag( flag_MOUNTED_GUN ) ) { const Character &shooter = you; - if( !can_use_heavy_weapon( shooter, m, shooter.pos() ) && !( you.get_size() > MS_MEDIUM ) && + if( !can_use_heavy_weapon( shooter, m, shooter.pos() ) && + !( you.get_size() > creature_size::medium ) && !you.worn_with_flag( flag_HEAVY_WEAPON_SUPPORT ) ) { messages.push_back( string_format( _( "You must stand near acceptable terrain or furniture to fire the %s. A table, a mound of dirt, a broken window, etc." ), diff --git a/src/savegame.cpp b/src/savegame.cpp index 006e98fe36b2..fe23def9d406 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -312,7 +312,7 @@ void game::load_shortcuts( std::istream &fin ) for( const JsonMember &member : data.get_object( "quick_shortcuts" ) ) { std::list &qslist = quick_shortcuts_map[member.name()]; for( const int i : member.get_array() ) { - qslist.push_back( input_event( i, CATA_INPUT_KEYBOARD ) ); + qslist.push_back( input_event( i, input_event_t::keyboard ) ); } } } diff --git a/src/sdltiles.cpp b/src/sdltiles.cpp index 561d2e767dab..3afe06f1e080 100644 --- a/src/sdltiles.cpp +++ b/src/sdltiles.cpp @@ -1633,7 +1633,7 @@ static int HandleDPad() return 0; } - last_input = input_event( lc, CATA_INPUT_GAMEPAD ); + last_input = input_event( lc, input_event_t::gamepad ); lastdpad = lc; queued_dpad = ERR; @@ -1653,7 +1653,7 @@ static int HandleDPad() // If we didn't hold it down for a while, just // fire the last registered press. if( queued_dpad != ERR ) { - last_input = input_event( queued_dpad, CATA_INPUT_GAMEPAD ); + last_input = input_event( queued_dpad, input_event_t::gamepad ); queued_dpad = ERR; return 1; } @@ -2139,7 +2139,7 @@ int choose_best_key_for_action( const std::string &action, const std::string &ca const std::vector &events = inp_mngr.get_input_for_action( action, category ); int best_key = -1; for( const auto &events_event : events ) { - if( events_event.type == CATA_INPUT_KEYBOARD && events_event.sequence.size() == 1 ) { + if( events_event.type == input_event_t::keyboard && events_event.sequence.size() == 1 ) { bool is_ascii_char = isprint( events_event.sequence.front() ) && events_event.sequence.front() < 0xFF; bool is_best_ascii_char = best_key >= 0 && isprint( best_key ) && best_key < 0xFF; @@ -2155,7 +2155,7 @@ bool add_key_to_quick_shortcuts( int key, const std::string &category, bool back { if( key > 0 ) { quick_shortcuts_t &qsl = quick_shortcuts_map[get_quick_shortcut_name( category )]; - input_event event = input_event( key, CATA_INPUT_KEYBOARD ); + input_event event = input_event( key, input_event_t::keyboard ); quick_shortcuts_t::iterator it = std::find( qsl.begin(), qsl.end(), event ); if( it != qsl.end() ) { // already exists ( *it ).shortcut_last_used_action_counter = @@ -2328,7 +2328,7 @@ void draw_quick_shortcuts() std::vector ®istered_manual_keys = touch_input_context.get_registered_manual_keys(); for( const auto &manual_key : registered_manual_keys ) { - input_event event( manual_key.key, CATA_INPUT_KEYBOARD ); + input_event event( manual_key.key, input_event_t::keyboard ); add_quick_shortcut( qsl, event, !shortcut_right, true ); } } @@ -2573,56 +2573,56 @@ void handle_finger_input( uint32_t ticks ) WindowHeight ) ) ) { if( !handle_diagonals ) { if( delta_x >= 0 && delta_y >= 0 ) { - last_input = input_event( delta_x > delta_y ? KEY_RIGHT : KEY_DOWN, CATA_INPUT_KEYBOARD ); + last_input = input_event( delta_x > delta_y ? KEY_RIGHT : KEY_DOWN, input_event_t::keyboard ); } else if( delta_x < 0 && delta_y >= 0 ) { - last_input = input_event( -delta_x > delta_y ? KEY_LEFT : KEY_DOWN, CATA_INPUT_KEYBOARD ); + last_input = input_event( -delta_x > delta_y ? KEY_LEFT : KEY_DOWN, input_event_t::keyboard ); } else if( delta_x >= 0 && delta_y < 0 ) { - last_input = input_event( delta_x > -delta_y ? KEY_RIGHT : KEY_UP, CATA_INPUT_KEYBOARD ); + last_input = input_event( delta_x > -delta_y ? KEY_RIGHT : KEY_UP, input_event_t::keyboard ); } else if( delta_x < 0 && delta_y < 0 ) { - last_input = input_event( -delta_x > -delta_y ? KEY_LEFT : KEY_UP, CATA_INPUT_KEYBOARD ); + last_input = input_event( -delta_x > -delta_y ? KEY_LEFT : KEY_UP, input_event_t::keyboard ); } } else { if( delta_x > 0 ) { if( std::abs( delta_y ) < delta_x * 0.5f ) { // swipe right - last_input = input_event( KEY_RIGHT, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_RIGHT, input_event_t::keyboard ); } else if( std::abs( delta_y ) < delta_x * 2.0f ) { if( delta_y < 0 ) { // swipe up-right - last_input = input_event( JOY_RIGHTUP, CATA_INPUT_GAMEPAD ); + last_input = input_event( JOY_RIGHTUP, input_event_t::gamepad ); } else { // swipe down-right - last_input = input_event( JOY_RIGHTDOWN, CATA_INPUT_GAMEPAD ); + last_input = input_event( JOY_RIGHTDOWN, input_event_t::gamepad ); } } else { if( delta_y < 0 ) { // swipe up - last_input = input_event( KEY_UP, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_UP, input_event_t::keyboard ); } else { // swipe down - last_input = input_event( KEY_DOWN, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_DOWN, input_event_t::keyboard ); } } } else { if( std::abs( delta_y ) < -delta_x * 0.5f ) { // swipe left - last_input = input_event( KEY_LEFT, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_LEFT, input_event_t::keyboard ); } else if( std::abs( delta_y ) < -delta_x * 2.0f ) { if( delta_y < 0 ) { // swipe up-left - last_input = input_event( JOY_LEFTUP, CATA_INPUT_GAMEPAD ); + last_input = input_event( JOY_LEFTUP, input_event_t::gamepad ); } else { // swipe down-left - last_input = input_event( JOY_LEFTDOWN, CATA_INPUT_GAMEPAD ); + last_input = input_event( JOY_LEFTDOWN, input_event_t::gamepad ); } } else { if( delta_y < 0 ) { // swipe up - last_input = input_event( KEY_UP, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_UP, input_event_t::keyboard ); } else { // swipe down - last_input = input_event( KEY_DOWN, CATA_INPUT_KEYBOARD ); + last_input = input_event( KEY_DOWN, input_event_t::keyboard ); } } } @@ -2634,13 +2634,13 @@ void handle_finger_input( uint32_t ticks ) // We only allow repeats for waiting, not confirming in menus as that's a bit silly if( is_default_mode ) { last_input = input_event( get_key_event_from_string( get_option( "ANDROID_TAP_KEY" ) ), - CATA_INPUT_KEYBOARD ); + input_event_t::keyboard ); } } else { if( last_tap_time > 0 && ticks - last_tap_time < static_cast( get_option( "ANDROID_INITIAL_DELAY" ) ) ) { // Double tap - last_input = input_event( is_default_mode ? KEY_ESCAPE : KEY_ESCAPE, CATA_INPUT_KEYBOARD ); + last_input = input_event( is_default_mode ? KEY_ESCAPE : KEY_ESCAPE, input_event_t::keyboard ); last_tap_time = 0; } else { // First tap detected, waiting to decide whether it's a single or a double tap input @@ -2970,7 +2970,7 @@ static void CheckMessages() // Single tap last_tap_time = ticks; last_input = input_event( is_default_mode ? get_key_event_from_string( - get_option( "ANDROID_TAP_KEY" ) ) : '\n', CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_TAP_KEY" ) ) : '\n', input_event_t::keyboard ); last_tap_time = 0; return; } @@ -3064,7 +3064,7 @@ static void CheckMessages() } else if( add_alt_code( lc ) ) { // key was handled } else { - last_input = input_event( lc, CATA_INPUT_KEYBOARD ); + last_input = input_event( lc, input_event_t::keyboard ); #if defined(__ANDROID__) if( !android_is_hardware_keyboard_available() ) { if( !is_string_input( touch_input_context ) && !touch_input_context.allow_text_entry ) { @@ -3073,7 +3073,7 @@ static void CheckMessages() } // add a quick shortcut - if( !last_input.text.empty() || !inp_mngr.get_keyname( lc, CATA_INPUT_KEYBOARD ).empty() ) { + if( !last_input.text.empty() || !inp_mngr.get_keyname( lc, input_event_t::keyboard ).empty() ) { qsl.remove( last_input ); add_quick_shortcut( qsl, last_input, false, true ); refresh_display(); @@ -3107,7 +3107,7 @@ static void CheckMessages() if( ev.key.keysym.sym == SDLK_LALT || ev.key.keysym.sym == SDLK_RALT ) { int code = end_alt_code(); if( code ) { - last_input = input_event( code, CATA_INPUT_KEYBOARD ); + last_input = input_event( code, input_event_t::keyboard ); last_input.text = utf32_to_utf8( code ); } } @@ -3117,7 +3117,7 @@ static void CheckMessages() if( !add_alt_code( *ev.text.text ) ) { if( strlen( ev.text.text ) > 0 ) { const unsigned lc = UTF8_getch( ev.text.text ); - last_input = input_event( lc, CATA_INPUT_KEYBOARD ); + last_input = input_event( lc, input_event_t::keyboard ); #if defined(__ANDROID__) if( !android_is_hardware_keyboard_available() ) { if( !is_string_input( touch_input_context ) && !touch_input_context.allow_text_entry ) { @@ -3140,7 +3140,7 @@ static void CheckMessages() } else { // no key pressed in this event last_input = input_event(); - last_input.type = CATA_INPUT_KEYBOARD; + last_input.type = input_event_t::keyboard; } last_input.text = ev.text.text; text_refresh = true; @@ -3149,11 +3149,11 @@ static void CheckMessages() case SDL_TEXTEDITING: { if( strlen( ev.edit.text ) > 0 ) { const unsigned lc = UTF8_getch( ev.edit.text ); - last_input = input_event( lc, CATA_INPUT_KEYBOARD ); + last_input = input_event( lc, input_event_t::keyboard ); } else { // no key pressed in this event last_input = input_event(); - last_input.type = CATA_INPUT_KEYBOARD; + last_input.type = input_event_t::keyboard; } last_input.edit = ev.edit.text; last_input.edit_refresh = true; @@ -3161,7 +3161,7 @@ static void CheckMessages() } break; case SDL_JOYBUTTONDOWN: - last_input = input_event( ev.jbutton.button, CATA_INPUT_KEYBOARD ); + last_input = input_event( ev.jbutton.button, input_event_t::keyboard ); break; case SDL_JOYAXISMOTION: // on gamepads, the axes are the analog sticks @@ -3175,26 +3175,26 @@ static void CheckMessages() } // Only monitor motion when cursor is visible - last_input = input_event( MOUSE_MOVE, CATA_INPUT_MOUSE ); + last_input = input_event( MOUSE_MOVE, input_event_t::mouse ); } break; case SDL_MOUSEBUTTONUP: switch( ev.button.button ) { case SDL_BUTTON_LEFT: - last_input = input_event( MOUSE_BUTTON_LEFT, CATA_INPUT_MOUSE ); + last_input = input_event( MOUSE_BUTTON_LEFT, input_event_t::mouse ); break; case SDL_BUTTON_RIGHT: - last_input = input_event( MOUSE_BUTTON_RIGHT, CATA_INPUT_MOUSE ); + last_input = input_event( MOUSE_BUTTON_RIGHT, input_event_t::mouse ); break; } break; case SDL_MOUSEWHEEL: if( ev.wheel.y > 0 ) { - last_input = input_event( SCROLLWHEEL_UP, CATA_INPUT_MOUSE ); + last_input = input_event( SCROLLWHEEL_UP, input_event_t::mouse ); } else if( ev.wheel.y < 0 ) { - last_input = input_event( SCROLLWHEEL_DOWN, CATA_INPUT_MOUSE ); + last_input = input_event( SCROLLWHEEL_DOWN, input_event_t::mouse ); } break; @@ -3290,7 +3290,7 @@ static void CheckMessages() if( std::max( d1, d2 ) < get_option( "ANDROID_DEADZONE_RANGE" ) * longest_window_edge ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_TAP_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_TAP_KEY" ) ), input_event_t::keyboard ); } else { float dot = ( x1 * x2 + y1 * y2 ) / ( d1 * d2 ); // dot product of two finger vectors, -1 to +1 if( dot > 0.0f ) { // both fingers mostly heading in same direction, check for double-finger swipe gesture @@ -3303,16 +3303,16 @@ static void CheckMessages() float yavg = 0.5f * ( y1 + y2 ); if( xavg > 0 && xavg > std::abs( yavg ) ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_SWIPE_LEFT_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_SWIPE_LEFT_KEY" ) ), input_event_t::keyboard ); } else if( xavg < 0 && -xavg > std::abs( yavg ) ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_SWIPE_RIGHT_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_SWIPE_RIGHT_KEY" ) ), input_event_t::keyboard ); } else if( yavg > 0 && yavg > std::abs( xavg ) ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_SWIPE_DOWN_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_SWIPE_DOWN_KEY" ) ), input_event_t::keyboard ); } else { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_2_SWIPE_UP_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_2_SWIPE_UP_KEY" ) ), input_event_t::keyboard ); } } } else { @@ -3328,10 +3328,10 @@ static void CheckMessages() const float zoom_ratio = 0.9f; if( curr_dist < down_dist * zoom_ratio ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_PINCH_IN_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_PINCH_IN_KEY" ) ), input_event_t::keyboard ); } else if( curr_dist > down_dist / zoom_ratio ) { last_input = input_event( get_key_event_from_string( - get_option( "ANDROID_PINCH_OUT_KEY" ) ), CATA_INPUT_KEYBOARD ); + get_option( "ANDROID_PINCH_OUT_KEY" ) ), input_event_t::keyboard ); } } } @@ -3659,11 +3659,11 @@ input_event input_manager::get_input_event() if( inputdelay < 0 ) { do { CheckMessages(); - if( last_input.type != CATA_INPUT_ERROR ) { + if( last_input.type != input_event_t::error ) { break; } SDL_Delay( 1 ); - } while( last_input.type == CATA_INPUT_ERROR ); + } while( last_input.type == input_event_t::error ); } else if( inputdelay > 0 ) { uint32_t starttime = SDL_GetTicks(); uint32_t endtime = 0; @@ -3671,29 +3671,29 @@ input_event input_manager::get_input_event() do { CheckMessages(); endtime = SDL_GetTicks(); - if( last_input.type != CATA_INPUT_ERROR ) { + if( last_input.type != input_event_t::error ) { break; } SDL_Delay( 1 ); timedout = endtime >= starttime + inputdelay; if( timedout ) { - last_input.type = CATA_INPUT_TIMEOUT; + last_input.type = input_event_t::timeout; } } while( !timedout ); } else { CheckMessages(); } - if( last_input.type == CATA_INPUT_MOUSE ) { + if( last_input.type == input_event_t::mouse ) { SDL_GetMouseState( &last_input.mouse_pos.x, &last_input.mouse_pos.y ); - } else if( last_input.type == CATA_INPUT_KEYBOARD ) { + } else if( last_input.type == input_event_t::keyboard ) { previously_pressed_key = last_input.get_first_input(); #if defined(__ANDROID__) android_vibrate(); #endif } #if defined(__ANDROID__) - else if( last_input.type == CATA_INPUT_GAMEPAD ) { + else if( last_input.type == input_event_t::gamepad ) { android_vibrate(); } #endif diff --git a/src/string_editor_window.cpp b/src/string_editor_window.cpp index 4d395e432161..cc920f1101b2 100644 --- a/src/string_editor_window.cpp +++ b/src/string_editor_window.cpp @@ -11,6 +11,7 @@ #endif #include "wcwidth.h" +#include "ui_manager.h" static bool is_linebreak( const uint32_t uc ) { @@ -193,7 +194,7 @@ point string_editor_window::get_line_and_position( const int position, const boo return _folded->codepoint_coordinates( position, zero_x ); } -void string_editor_window::print_editor() +void string_editor_window::print_editor( ui_adaptor &ui ) { const point focus = _ime_preview_range ? _ime_preview_range->display_last : _cursor_display; const int ftsize = _folded->get_lines().size(); @@ -213,6 +214,7 @@ void string_editor_window::print_editor() } } + std::optional cursor_pos; for( int i = topoflist; i < bottomoflist; i++ ) { const int y = i - topoflist; const folded_line &line = _folded->get_lines()[i]; @@ -233,7 +235,9 @@ void string_editor_window::print_editor() || is_linebreak( c_cursor ) || mk_wcwidth( c_cursor ) < 1 ) { c_cursor = ' '; } - mvwprintz( _win, point( _cursor_display.x + 1, y ), h_white, "%s", utf32_to_utf8( c_cursor ) ); + const point cursor_pos( _cursor_display.x + 1, y ); + mvwprintz( _win, cursor_pos, h_white, "%s", utf32_to_utf8( c_cursor ) ); + ui.set_cursor( _win, cursor_pos ); } if( _ime_preview_range && i >= _ime_preview_range->display_first.y && i <= _ime_preview_range->display_last.y ) { @@ -246,6 +250,14 @@ void string_editor_window::print_editor() mvwprintz( _win, disp, c_dark_gray_white, "%s", preview.str() ); } } + if( _ime_preview_range ) { + cursor_pos = _ime_preview_range->display_last + point( 1, -topoflist ); + } + + if( _ime_preview_range ) { + const point cursor_pos = _ime_preview_range->display_last + point( 1, -topoflist ); + ui.set_cursor( _win, cursor_pos ); + } if( ftsize > _max.y ) { scrollbar() @@ -254,11 +266,34 @@ void string_editor_window::print_editor() .viewport_size( _max.y ) .apply( _win ); } + + if( cursor_pos ) { + wmove( _win, cursor_pos.value() ); + wnoutrefresh( _win ); + } } void string_editor_window::create_context() { - ctxt = std::make_unique(); + ctxt = std::make_unique( "STRING_EDITOR" ); + ctxt->register_action( "TEXT.QUIT" ); + ctxt->register_action( "TEXT.CONFIRM" ); + ctxt->register_action( "TEXT.LEFT" ); + ctxt->register_action( "TEXT.RIGHT" ); + ctxt->register_action( "TEXT.UP" ); + ctxt->register_action( "TEXT.DOWN" ); + ctxt->register_action( "TEXT.CLEAR" ); + ctxt->register_action( "TEXT.BACKSPACE" ); + ctxt->register_action( "TEXT.HOME" ); + ctxt->register_action( "TEXT.END" ); + ctxt->register_action( "TEXT.PAGE_UP" ); + ctxt->register_action( "TEXT.PAGE_DOWN" ); + ctxt->register_action( "TEXT.DELETE" ); +#if defined(TILES) + ctxt->register_action( "TEXT.PASTE" ); +#endif + ctxt->register_action( "TEXT.INPUT_FROM_FILE" ); + ctxt->register_action( "HELP_KEYBINDINGS" ); ctxt->register_action( "ANY_INPUT" ); } @@ -330,7 +365,7 @@ std::pair string_editor_window::query_string() ui.position_from_window( _win ); } ); ui.mark_resize(); - ui.on_redraw( [&]( const ui_adaptor & ) { + ui.on_redraw( [&]( ui_adaptor & ui ) { if( refold ) { utf8_wrapper text = _utext; if( !edit.empty() ) { @@ -356,7 +391,7 @@ std::pair string_editor_window::query_string() reposition = false; } werase( _win ); - print_editor(); + print_editor( ui ); wnoutrefresh( _win ); } ); @@ -374,54 +409,50 @@ std::pair string_editor_window::query_string() const std::string action = ctxt->handle_input(); - if( action != "ANY_INPUT" ) { - continue; - } - const input_event ev = ctxt->get_raw_input(); - ch = ev.type == input_event_t::CATA_INPUT_KEYBOARD ? ev.get_first_input() : 0; + ch = ev.get_first_input(); - if( ch == KEY_ESCAPE ) { + if( action == "TEXT.QUIT" ) { return { false, _utext.str() }; - } else if( ch == 0x13 ) { + } else if( action == "TEXT.CONFIRM" ) { // ctrl-s: confirm return { true, _utext.str() }; - } else if( ch == KEY_UP ) { + } else if( action == "TEXT.UP" ) { if( edit.empty() ) { cursor_updown( -1 ); reposition = true; } - } else if( ch == KEY_DOWN ) { + } else if( action == "TEXT.DOWN" ) { if( edit.empty() ) { cursor_updown( 1 ); reposition = true; } - } else if( ch == KEY_RIGHT ) { + } else if( action == "TEXT.RIGHT" ) { if( edit.empty() ) { cursor_leftright( 1 ); _cursor_desired_x = -1; reposition = true; } - } else if( ch == KEY_LEFT ) { + } else if( action == "TEXT.LEFT" ) { if( edit.empty() ) { cursor_leftright( -1 ); _cursor_desired_x = -1; reposition = true; } - } else if( ch == 0x15 ) { + } else if( action == "TEXT.CLEAR" ) { // ctrl-u: delete all the things _position = 0; _cursor_desired_x = -1; _utext.erase( 0 ); refold = true; - } else if( ch == KEY_BACKSPACE ) { + } else if( action == "TEXT.BACKSPACE" ) { if( _position > 0 && _position <= static_cast( _utext.size() ) ) { _position--; _cursor_desired_x = -1; _utext.erase( _position, 1 ); refold = true; } - } else if( ch == KEY_HOME ) { + } else if( action == "TEXT.HOME" ) { if( edit.empty() && static_cast( _cursor_display.y ) < _folded->get_lines().size() ) { _position = _folded->get_lines()[_cursor_display.y].cpts_start; @@ -429,7 +460,7 @@ std::pair string_editor_window::query_string() _cursor_desired_x = 0; reposition = true; } - } else if( ch == KEY_END ) { + } else if( action == "TEXT.END" ) { if( edit.empty() && static_cast( _cursor_display.y ) < _folded->get_lines().size() ) { _position = _folded->get_lines()[_cursor_display.y].cpts_end; @@ -440,26 +471,27 @@ std::pair string_editor_window::query_string() _cursor_desired_x = -1; reposition = true; } - } else if( ch == KEY_PPAGE ) { + } else if( action == "TEXT.PAGE_UP" ) { if( edit.empty() ) { cursor_updown( -_max.y ); reposition = true; } - } else if( ch == KEY_NPAGE ) { + } else if( action == "TEXT.PAGE_DOWN" ) { if( edit.empty() ) { cursor_updown( _max.y ); reposition = true; } - } else if( ch == KEY_DC ) { + } else if( action == "TEXT.DELETE" ) { if( _position < static_cast( _utext.size() ) ) { _cursor_desired_x = -1; _utext.erase( _position, 1 ); refold = true; } - } else if( ch == 0x16 || ch == KEY_F( 2 ) || !ev.text.empty() || ch == KEY_ENTER || ch == '\n' ) { - // ctrl-v, f2, or _utext input + } else if( action == "TEXT.PASTE" || action == "TEXT.INPUT_FROM_FILE" + || !ev.text.empty() || ch == '\n' ) { + // paste, input from file, or text input std::string entered; - if( ch == 0x16 ) { + if( action == "TEXT.PASTE" ) { #if defined(TILES) if( edit.empty() ) { char *const clip = SDL_GetClipboardText(); @@ -469,11 +501,11 @@ std::pair string_editor_window::query_string() } } #endif - } else if( ch == KEY_F( 2 ) ) { + } else if( action == "TEXT.INPUT_FROM_FILE" ) { if( edit.empty() ) { entered = get_input_string_from_file(); } - } else if( ch == KEY_ENTER || ch == '\n' ) { + } else if( ch == '\n' ) { if( edit.empty() ) { entered = "\n"; } diff --git a/src/string_editor_window.h b/src/string_editor_window.h index 9a7b66d701d7..02baacab462a 100644 --- a/src/string_editor_window.h +++ b/src/string_editor_window.h @@ -9,12 +9,13 @@ #include "input.h" #include "output.h" #include "ui.h" -#include "ui_manager.h" class folded_text; struct ime_preview_range; +class ui_adaptor; + /// /// editor, to let the player edit text. /// @@ -59,7 +60,7 @@ class string_editor_window private: /*print the editor*/ - void print_editor(); + void print_editor( ui_adaptor &ui ); void create_context(); diff --git a/src/string_input_popup.cpp b/src/string_input_popup.cpp index 015dd96e1218..144b8d1e16c6 100644 --- a/src/string_input_popup.cpp +++ b/src/string_input_popup.cpp @@ -12,6 +12,11 @@ #include "ui.h" #include "ui_manager.h" #include "uistate.h" +#include "wcwidth.h" + +#if defined(TILES) +#include "sdl_wrappers.h" +#endif #if defined(__ANDROID__) #include @@ -58,17 +63,21 @@ void string_input_popup::create_window() break; } } - w_height += static_cast( title_split.size() ) - 1; + title_height = static_cast( title_split.size() ) - 1; + w_height += title_height; } - descformatted.clear(); if( !_description.empty() ) { const int twidth = std::min( utf8_width( remove_color_tags( _description ) ), w_width - 4 ); - descformatted = foldstring( _description, twidth ); - w_height += descformatted.size(); + description_height = foldstring( _description, twidth ).size(); + w_height += description_height; + if( w_height > TERMY ) { + description_height = TERMY - 2 - title_height - 1; + w_height = TERMY; + } } // length of title + border (left) + space - _startx = titlesize + 2; + _startx = titlesize + 1; if( _max_length <= 0 ) { _max_length = 1024; @@ -77,9 +86,16 @@ void string_input_popup::create_window() const int w_y = ( TERMY - w_height ) / 2; const int w_x = std::max( ( TERMX - w_width ) / 2, 0 ); - w = catacurses::newwin( w_height, w_width, point( w_x, w_y ) ); - - _starty = w_height - 2; // The ____ looks better at the bottom right when the title folds + _starty = title_height; + w_full = catacurses::newwin( w_height, w_width, point( w_x, w_y ) ); + if( !_description.empty() ) { + w_description = catacurses::newwin( description_height, w_width - 1, point( w_x, + w_y + 1 ) ); + desc_view_ptr = std::make_unique( w_description ); + desc_view_ptr->set_text( _description ); + } + w_title_and_entry = catacurses::newwin( w_height - description_height - 2, w_width - 2, + point( w_x + 1, w_y + 1 + description_height ) ); custom_window = false; } @@ -88,6 +104,28 @@ void string_input_popup::create_context() { ctxt_ptr = std::make_unique( "STRING_INPUT" ); ctxt = ctxt_ptr.get(); + ctxt->register_action( "TEXT.QUIT" ); + ctxt->register_action( "TEXT.CONFIRM" ); + if( !_identifier.empty() ) { + ctxt->register_action( "HISTORY_UP" ); + ctxt->register_action( "HISTORY_DOWN" ); + } + ctxt->register_action( "TEXT.LEFT" ); + ctxt->register_action( "TEXT.RIGHT" ); + ctxt->register_action( "TEXT.CLEAR" ); + ctxt->register_action( "TEXT.BACKSPACE" ); + ctxt->register_action( "TEXT.HOME" ); + ctxt->register_action( "TEXT.END" ); + ctxt->register_action( "TEXT.DELETE" ); +#if defined(TILES) + ctxt->register_action( "TEXT.PASTE" ); +#endif + ctxt->register_action( "TEXT.INPUT_FROM_FILE" ); + ctxt->register_action( "HELP_KEYBINDINGS" ); + ctxt->register_action( "PAGE_UP" ); + ctxt->register_action( "PAGE_DOWN" ); + ctxt->register_action( "SCROLL_UP" ); + ctxt->register_action( "SCROLL_DOWN" ); ctxt->register_action( "ANY_INPUT" ); } @@ -114,17 +152,17 @@ void string_input_popup::show_history( utf8_wrapper &ret ) hmenu.w_height_setup = [&]() -> int { // number of lines that make up the menu window: 2*border+entries int height = 2 + hmenu.entries.size(); - if( getbegy( w ) < height ) + if( getbegy( w_full ) < height ) { - height = std::max( getbegy( w ), 4 ); + height = std::max( getbegy( w_full ), 4 ); } return height; }; hmenu.w_x_setup = [&]( int ) -> int { - return getbegx( w ); + return getbegx( w_full ); }; hmenu.w_y_setup = [&]( const int height ) -> int { - return std::max( getbegy( w ) - height, 0 ); + return std::max( getbegy( w_full ) - height, 0 ); }; bool finished = false; @@ -202,20 +240,19 @@ void string_input_popup::update_input_history( utf8_wrapper &ret, bool up ) _position = ret.length(); } -void string_input_popup::draw( const utf8_wrapper &ret, const utf8_wrapper &edit, - const int shift ) const +void string_input_popup::draw( ui_adaptor *const ui, const utf8_wrapper &ret, + const utf8_wrapper &edit ) const { if( !custom_window ) { - draw_border( w ); + werase( w_full ); + draw_border( w_full ); + wnoutrefresh( w_full ); - for( size_t i = 0; i < descformatted.size(); ++i ) { - trim_and_print( w, point( 1, 1 + i ), w_width - 2, _desc_color, descformatted[i] ); - } - int pos_y = descformatted.size() + 1; + int pos_y = 0; for( int i = 0; i < static_cast( title_split.size() ) - 1; i++ ) { - mvwprintz( w, point( i + 1, pos_y++ ), _title_color, title_split[i] ); + mvwprintz( w_title_and_entry, point( i, pos_y++ ), _title_color, title_split[i] ); } - right_print( w, pos_y, w_width - titlesize - 1, _title_color, title_split.back() ); + trim_and_print( w_title_and_entry, point( 0, pos_y ), titlesize, _title_color, title_split.back() ); } const int scrmax = _endx - _startx; @@ -223,11 +260,13 @@ void string_input_popup::draw( const utf8_wrapper &ret, const utf8_wrapper &edit const utf8_wrapper ds( ret.substr_display( shift, scrmax ) ); int start_x_edit = _startx; // Clear the line - mvwprintw( w, point( _startx, _starty ), std::string( std::max( 0, scrmax ), ' ' ) ); + mvwprintw( w_title_and_entry, point( _startx, _starty ), std::string( std::max( 0, scrmax ), + ' ' ) ); // Print the whole input string in default color - mvwprintz( w, point( _startx, _starty ), _string_color, "%s", ds.c_str() ); + mvwprintz( w_title_and_entry, point( _startx, _starty ), _string_color, "%s", ds.c_str() ); size_t sx = ds.display_width(); // Print the cursor in its own color + point cursor_pos; if( _position >= 0 && static_cast( _position ) < ret.length() ) { utf8_wrapper cursor = ret.substr( _position, 1 ); size_t a = _position; @@ -238,14 +277,18 @@ void string_input_popup::draw( const utf8_wrapper &ret, const utf8_wrapper &edit cursor = ret.substr( a, _position - a + 1 ); } const size_t left_over = ret.substr( 0, a ).display_width() - shift; - mvwprintz( w, point( _startx + left_over, _starty ), _cursor_color, "%s", cursor.c_str() ); + cursor_pos = point( _startx + left_over, _starty ); + mvwprintz( w_title_and_entry, cursor_pos, _cursor_color, "%s", cursor.c_str() ); start_x_edit += left_over; - } else if( _position == _max_length && _max_length > 0 ) { - mvwprintz( w, point( _startx + sx, _starty ), _cursor_color, " " ); + } else if( _max_length > 0 + && ret.display_width() >= static_cast( _max_length ) ) { + cursor_pos = point( _startx + sx, _starty ); + mvwprintz( w_title_and_entry, cursor_pos, _cursor_color, " " ); start_x_edit += sx; sx++; // don't override trailing ' ' } else { - mvwprintz( w, point( _startx + sx, _starty ), _cursor_color, "_" ); + cursor_pos = point( _startx + sx, _starty ); + mvwprintz( w_title_and_entry, cursor_pos, _cursor_color, "_" ); start_x_edit += sx; sx++; // don't override trailing '_' } @@ -253,24 +296,42 @@ void string_input_popup::draw( const utf8_wrapper &ret, const utf8_wrapper &edit // could be scrolled out of view when the cursor is at the start of the input size_t l = scrmax - sx; if( _max_length > 0 ) { - if( static_cast( ret.length() ) >= _max_length ) { + if( ret.display_width() >= static_cast( _max_length ) ) { l = 0; // no more input possible! } else if( _position == static_cast( ret.length() ) ) { // one '_' is already printed, formatted as cursor - l = std::min( l, _max_length - ret.length() - 1 ); + l = std::min( l, _max_length - ret.display_width() - 1 ); } else { - l = std::min( l, _max_length - ret.length() ); + l = std::min( l, _max_length - ret.display_width() ); } } if( l > 0 ) { - mvwprintz( w, point( _startx + sx, _starty ), _underscore_color, std::string( l, '_' ) ); + mvwprintz( w_title_and_entry, point( _startx + sx, _starty ), _underscore_color, std::string( l, + '_' ) ); } } if( !edit.empty() ) { - mvwprintz( w, point( start_x_edit, _starty ), _cursor_color, "%s", edit.c_str() ); + mvwprintz( w_title_and_entry, point( start_x_edit, _starty ), _cursor_color, "%s", edit.c_str() ); + } + wnoutrefresh( w_title_and_entry ); + + std::unique_ptr move_cursor_and_refresh; + if( ui ) { + ui->set_cursor( w_title_and_entry, cursor_pos ); + } else { + // This ensures the cursor is set last for calling UIs to record and set + // for screen readers and IME preview + move_cursor_and_refresh = std::make_unique( [&]() { + wmove( w_title_and_entry, cursor_pos ); + wnoutrefresh( w_title_and_entry ); + } ); } - wnoutrefresh( w ); + //Draw scrolling description + if( !custom_window && desc_view_ptr ) { + desc_view_ptr->draw( _desc_color ); + wnoutrefresh( w_description ); + } } void string_input_popup::query( const bool loop, const bool draw_only ) @@ -290,7 +351,7 @@ int64_t string_input_popup::query_int64_t( const bool loop, const bool draw_only const std::string &string_input_popup::query_string( const bool loop, const bool draw_only ) { - if( !custom_window && !w ) { + if( !custom_window && !w_full ) { create_window(); _position = -1; } @@ -307,20 +368,18 @@ const std::string &string_input_popup::query_string( const bool loop, const bool if( _position == -1 ) { _position = ret.length(); } - const int scrmax = _endx - _startx; - // in output (console) cells, not characters of the string! - int shift = 0; + const int scrmax = std::max( 0, _endx - _startx ); std::unique_ptr ui; if( !draw_only && !custom_window ) { ui = std::make_unique(); - ui->position_from_window( w ); + ui->position_from_window( w_full ); ui->on_screen_resize( [this]( ui_adaptor & ui ) { create_window(); - ui.position_from_window( w ); + ui.position_from_window( w_full ); } ); - ui->on_redraw( [&]( const ui_adaptor & ) { - draw( ret, edit, shift ); + ui->on_redraw( [&]( ui_adaptor & ui ) { + draw( &ui, ret, edit ); } ); } @@ -329,39 +388,42 @@ const std::string &string_input_popup::query_string( const bool loop, const bool _canceled = false; _confirmed = false; do { - if( _position < 0 ) { _position = 0; } - - const size_t left_shift = ret.substr( 0, _position ).display_width(); - if( static_cast( left_shift ) < shift ) { + if( shift < 0 ) { shift = 0; - } else if( _position < static_cast( ret.length() ) && - static_cast( left_shift ) + 1 >= shift + scrmax ) { - // if the cursor is inside the input string, keep one cell right of - // the cursor visible, because the cursor might be on a multi-cell - // character. - shift = left_shift - scrmax + 2; - } else if( _position == static_cast( ret.length() ) && - static_cast( left_shift ) >= shift + scrmax ) { - // cursor is behind the end of the input string, keep the - // trailing '_' visible (always a single cell character) - shift = left_shift - scrmax + 1; - } else if( shift < 0 ) { + } + + const size_t width_to_cursor_start = ret.substr( 0, _position ).display_width(); + size_t width_to_cursor_end = width_to_cursor_start; + if( static_cast( _position ) < ret.length() ) { + width_to_cursor_end += ret.substr( _position, 1 ).display_width(); + } else { + width_to_cursor_end += 1; + } + // starts scrolling when the cursor is this far from the start or end + const size_t scroll_width = std::min( 10, scrmax / 5 ); + if( ret.display_width() < static_cast( scrmax ) ) { shift = 0; + } else if( width_to_cursor_start < static_cast( shift ) + scroll_width ) { + shift = std::max( width_to_cursor_start, scroll_width ) - scroll_width; + } else if( width_to_cursor_end > static_cast( shift + scrmax ) - scroll_width ) { + shift = std::min( width_to_cursor_end + scroll_width, ret.display_width() ) - scrmax; } - const size_t xleft_shift = ret.substr_display( 0, shift ).display_width(); - if( static_cast( xleft_shift ) != shift ) { + const utf8_wrapper text_before_start = ret.substr_display( 0, shift ); + const size_t width_before_start = text_before_start.display_width(); + if( width_before_start != static_cast( shift ) ) { // This prevents a multi-cell character from been split, which is not possible // instead scroll a cell further to make that character disappear completely - shift++; + const size_t width_at_start = ret.substr( text_before_start.length(), 1 ).display_width(); + shift = width_before_start + width_at_start; } if( ui ) { ui_manager::redraw(); } else { - draw( ret, edit, shift ); + draw( nullptr, ret, edit ); } if( draw_only ) { @@ -370,7 +432,7 @@ const std::string &string_input_popup::query_string( const bool loop, const bool const std::string action = ctxt->handle_input(); const input_event ev = ctxt->get_raw_input(); - ch = ev.type == CATA_INPUT_KEYBOARD ? ev.get_first_input() : 0; + ch = ev.type == input_event_t::keyboard ? ev.get_first_input() : 0; _handled = true; if( callbacks[ch] ) { @@ -379,12 +441,7 @@ const std::string &string_input_popup::query_string( const bool loop, const bool } } - if( _ignore_custom_actions && action != "ANY_INPUT" ) { - _handled = false; - continue; - } - - if( ch == KEY_ESCAPE ) { + if( action == "TEXT.QUIT" ) { #if defined(__ANDROID__) if( get_option( "ANDROID_AUTO_KEYBOARD" ) ) { SDL_StopTextInput(); @@ -394,7 +451,7 @@ const std::string &string_input_popup::query_string( const bool loop, const bool _position = -1; _canceled = true; return _text; - } else if( ch == '\n' ) { + } else if( action == "TEXT.CONFIRM" ) { add_to_history( ret.str() ); _confirmed = true; _text = ret.str(); @@ -403,58 +460,119 @@ const std::string &string_input_popup::query_string( const bool loop, const bool _session_str_entered.erase( 0 ); } return _text; - } else if( ch == KEY_UP ) { + } else if( action == "HISTORY_UP" ) { if( !_identifier.empty() ) { - if( _hist_use_uilist ) { - show_history( ret ); - } else { - update_input_history( ret, true ); + if( edit.empty() ) { + if( _hist_use_uilist ) { + show_history( ret ); + } else { + update_input_history( ret, true ); + } } } else { _handled = false; } - } else if( ch == KEY_DOWN ) { + } else if( action == "HISTORY_DOWN" ) { if( !_identifier.empty() ) { - if( !_hist_use_uilist ) { + if( edit.empty() && !_hist_use_uilist ) { update_input_history( ret, false ); } } else { _handled = false; } - } else if( ch == KEY_DOWN || ch == KEY_NPAGE || ch == KEY_PPAGE || ch == KEY_BTAB || ch == 9 ) { - _handled = false; - } else if( ch == KEY_RIGHT ) { - if( _position + 1 <= static_cast( ret.size() ) ) { + + } else if( action == "TEXT.RIGHT" ) { + if( edit.empty() && _position + 1 <= static_cast( ret.size() ) ) { _position++; } - } else if( ch == KEY_LEFT ) { - if( _position > 0 ) { + } else if( action == "TEXT.LEFT" ) { + if( edit.empty() && _position > 0 ) { _position--; } - } else if( ch == 0x15 ) { // ctrl-u: delete all the things + } else if( action == "TEXT.CLEAR" ) { _position = 0; ret.erase( 0 ); - // Move the cursor back and re-draw it - } else if( ch == KEY_BACKSPACE ) { - // but silently drop input if we're at 0, instead of adding '^' + } else if( action == "TEXT.BACKSPACE" ) { if( _position > 0 && _position <= static_cast( ret.size() ) ) { - // TODO: it is safe now since you only input ASCII chars _position--; ret.erase( _position, 1 ); } - } else if( ch == KEY_HOME ) { - _position = 0; - } else if( ch == KEY_END ) { - _position = ret.size(); - } else if( ch == KEY_DC ) { + } else if( action == "TEXT.HOME" ) { + if( edit.empty() ) { + _position = 0; + } + } else if( action == "TEXT.END" ) { + if( edit.empty() ) { + _position = ret.size(); + } + } else if( action == "TEXT.DELETE" ) { if( _position < static_cast( ret.size() ) ) { ret.erase( _position, 1 ); } - } else if( ch == KEY_F( 2 ) ) { - std::string tmp = get_input_string_from_file(); - int tmplen = utf8_width( tmp ); - if( tmplen > 0 && ( tmplen + utf8_width( ret ) <= _max_length || _max_length == 0 ) ) { - ret.append( tmp ); + /*Note: SCROLL_UP/SCROLL_DOWN should by default only trigger on mousewheel, + * since up/down arrow were handled above */ + } else if( action == "SCROLL_UP" ) { + if( desc_view_ptr ) { + desc_view_ptr->scroll_up(); + } + } else if( action == "SCROLL_DOWN" ) { + if( desc_view_ptr ) { + desc_view_ptr->scroll_down(); + } + } else if( action == "PAGE_UP" ) { + if( desc_view_ptr ) { + desc_view_ptr->page_up(); + } + } else if( action == "PAGE_DOWN" ) { + if( desc_view_ptr ) { + desc_view_ptr->page_down(); + } + } else if( action == "TEXT.PASTE" || action == "TEXT.INPUT_FROM_FILE" + || ( action == "ANY_INPUT" && !ev.text.empty() ) ) { + // paste, input from file, or text input + // bail out early if already at length limit + if( _max_length <= 0 || ret.display_width() < static_cast( _max_length ) ) { + std::string entered; + if( action == "TEXT.PASTE" ) { +#if defined(TILES) + if( edit.empty() ) { + char *const clip = SDL_GetClipboardText(); + if( clip ) { + entered = clip; + SDL_free( clip ); + } + } +#endif + } else if( action == "TEXT.INPUT_FROM_FILE" ) { + if( edit.empty() ) { + entered = get_input_string_from_file(); + } + } else { + entered = ev.text; + } + if( !entered.empty() ) { + utf8_wrapper insertion; + const char *str = entered.c_str(); + int len = entered.length(); + int width = ret.display_width(); + while( len > 0 ) { + const uint32_t ch = UTF8_getch( &str, &len ); + // Use mk_wcwidth to filter out control characters + if( _only_digits ? ch == '-' || isdigit( ch ) : mk_wcwidth( ch ) >= 0 ) { + const int newwidth = mk_wcwidth( ch ); + if( _max_length <= 0 || width + newwidth <= _max_length ) { + insertion.append( utf8_wrapper( utf32_to_utf8( ch ) ) ); + width += newwidth; + } else { + break; + } + } + } + ret.insert( _position, insertion ); + _position += insertion.length(); + edit = utf8_wrapper(); + ctxt->set_edittext( std::string() ); + } } } else if( !ev.text.empty() && _only_digits && !( isdigit( ev.text[0] ) || ev.text[0] == '-' ) ) { // ignore non-digit (and '-' is a digit as well) @@ -464,16 +582,14 @@ const std::string &string_input_popup::query_string( const bool loop, const bool const utf8_wrapper t( ev.text ); ret.insert( _position, t ); _position += t.length(); - edit.erase( 0 ); - ctxt->set_edittext( edit.c_str() ); + edit = utf8_wrapper(); + ctxt->set_edittext( std::string() ); } else if( ev.edit_refresh ) { - const utf8_wrapper t( ev.edit ); - edit.erase( 0 ); - edit.insert( 0, t ); - ctxt->set_edittext( edit.c_str() ); + edit = utf8_wrapper( ev.edit ); + ctxt->set_edittext( ev.edit ); } else if( ev.edit.empty() ) { - edit.erase( 0 ); - ctxt->set_edittext( edit.c_str() ); + edit = utf8_wrapper(); + ctxt->set_edittext( std::string() ); } else { _handled = false; } @@ -485,11 +601,11 @@ const std::string &string_input_popup::query_string( const bool loop, const bool string_input_popup &string_input_popup::window( const catacurses::window &w, point start, int endx ) { - if( !custom_window && this->w ) { + if( !custom_window && this->w_full ) { // default window already created return *this; } - this->w = w; + this->w_title_and_entry = w; _startx = start.x; _starty = start.y; _endx = endx; diff --git a/src/string_input_popup.h b/src/string_input_popup.h index d3e153766ba0..ce6a22f71c7c 100644 --- a/src/string_input_popup.h +++ b/src/string_input_popup.h @@ -15,6 +15,8 @@ #include "cursesdef.h" class input_context; +class scrolling_text_view; +class ui_adaptor; class utf8_wrapper; struct point; @@ -61,11 +63,12 @@ class string_input_popup // NOLINT(cata-xy) int _max_length = -1; bool _only_digits = false; bool _hist_use_uilist = true; - bool _ignore_custom_actions = true; int _startx = 0; int _starty = 0; int _endx = 0; int _position = -1; + // in output (console) cells, not characters of the string! + int shift = 0; int _hist_str_ind = 0; //Counts only when @_hist_use_uilist is false const size_t _hist_max_size = 100; @@ -73,12 +76,16 @@ class string_input_popup // NOLINT(cata-xy) // Cache when using the default window int w_width = 0; int w_height = 0; - std::vector descformatted; + int title_height = 0; + int description_height = 0; std::vector title_split; int titlesize = 0; bool custom_window = false; - catacurses::window w; + catacurses::window w_full; + catacurses::window w_description; + catacurses::window w_title_and_entry; + std::unique_ptr desc_view_ptr; std::unique_ptr ctxt_ptr; input_context *ctxt = nullptr; @@ -93,7 +100,7 @@ class string_input_popup // NOLINT(cata-xy) void show_history( utf8_wrapper &ret ); void add_to_history( const std::string &value ) const; void update_input_history( utf8_wrapper &ret, bool up ); - void draw( const utf8_wrapper &ret, const utf8_wrapper &edit, int shift ) const; + void draw( ui_adaptor *ui, const utf8_wrapper &ret, const utf8_wrapper &edit ) const; public: string_input_popup(); @@ -168,16 +175,6 @@ class string_input_popup // NOLINT(cata-xy) _hist_use_uilist = value; return *this; } - /** - * If true and the custom input context returns an input action, the - * action is not handled at all and left to be handled by the caller. - * Otherwise the action is always handled as an input event to the popup. - * The caller can use @ref handled to check whether the last input is handled. - */ - string_input_popup &ignore_custom_actions( const bool value ) { - _ignore_custom_actions = value; - return *this; - } /** * Set the window area where to display the input text. If this is set, * the class will not create a separate window and *only* the editable diff --git a/src/trapfunc.cpp b/src/trapfunc.cpp index 2f6729f9550a..6a3b47d6838d 100644 --- a/src/trapfunc.cpp +++ b/src/trapfunc.cpp @@ -85,7 +85,7 @@ bool trapfunc::none( const tripoint &, Creature *, item * ) bool trapfunc::bubble( const tripoint &p, Creature *c, item * ) { // tiny animals don't trigger bubble wrap - if( c != nullptr && c->get_size() == MS_TINY ) { + if( c != nullptr && c->get_size() == creature_size::tiny ) { return false; } if( c != nullptr ) { @@ -104,7 +104,7 @@ bool trapfunc::glass( const tripoint &p, Creature *c, item * ) { if( c != nullptr ) { // tiny animals and hallucinations don't trigger glass trap - if( c->get_size() == MS_TINY || c->is_hallucination() ) { + if( c->get_size() == creature_size::tiny || c->is_hallucination() ) { return false; } c->add_msg_player_or_npc( m_warning, _( "You step on some glass!" ), @@ -143,7 +143,7 @@ bool trapfunc::cot( const tripoint &p, Creature *c, item * ) bool trapfunc::beartrap( const tripoint &p, Creature *c, item * ) { // tiny animals don't trigger bear traps - if( c != nullptr && c->get_size() == MS_TINY ) { + if( c != nullptr && c->get_size() == creature_size::tiny ) { return false; } sounds::sound( p, 8, sounds::sound_t::combat, _( "SNAP!" ), false, "trap", "bear_trap" ); @@ -178,7 +178,7 @@ bool trapfunc::board( const tripoint &p, Creature *c, item * ) return false; } // tiny animals don't trigger spiked boards, they can squeeze between the nails - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You step on a spiked board!" ), @@ -208,7 +208,7 @@ bool trapfunc::caltrops( const tripoint &p, Creature *c, item * ) return false; } // tiny animals don't trigger caltrops, they can squeeze between them - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You step on a sharp metal caltrop!" ), @@ -238,7 +238,7 @@ bool trapfunc::caltrops_glass( const tripoint &p, Creature *c, item * ) return false; } // tiny animals don't trigger caltrops, they can squeeze between them - if( c->get_size() == MS_TINY || c->is_hallucination() ) { + if( c->get_size() == creature_size::tiny || c->is_hallucination() ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You step on a sharp glass caltrop!" ), @@ -268,7 +268,7 @@ bool trapfunc::tripwire( const tripoint &p, Creature *c, item * ) return false; } // tiny animals don't trigger tripwires, they just squeeze under it - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You trip over a tripwire!" ), @@ -378,19 +378,19 @@ bool trapfunc::crossbow( const tripoint &p, Creature *c, item * ) int chance = 0; // chance of getting hit depends on size switch( z->type->size ) { - case MS_TINY: + case creature_size::tiny: chance = 50; break; - case MS_SMALL: + case creature_size::small: chance = 8; break; - case MS_MEDIUM: + case creature_size::medium: chance = 6; break; - case MS_LARGE: + case creature_size::large: chance = 4; break; - case MS_HUGE: + case creature_size::huge: chance = 1; break; default: @@ -528,7 +528,7 @@ bool trapfunc::snare_light( const tripoint &p, Creature *c, item * ) // Actual effects c->add_effect( effect_lightsnare, 1_turns, hit->token ); monster *z = dynamic_cast( c ); - if( z != nullptr && z->type->size == MS_TINY ) { + if( z != nullptr && z->type->size == creature_size::tiny ) { z->deal_damage( nullptr, hit, damage_instance( DT_BASH, 10 ) ); } c->check_dead_state(); @@ -563,11 +563,11 @@ bool trapfunc::snare_heavy( const tripoint &p, Creature *c, item * ) } else if( z != nullptr ) { int damage; switch( z->type->size ) { - case MS_TINY: - case MS_SMALL: + case creature_size::tiny: + case creature_size::small: damage = 20; break; - case MS_MEDIUM: + case creature_size::medium: damage = 10; break; default: @@ -598,7 +598,7 @@ static explosion_data get_basic_explosion_data() bool trapfunc::landmine( const tripoint &p, Creature *c, item * ) { // tiny animals are too light to trigger land mines - if( c != nullptr && c->get_size() == MS_TINY ) { + if( c != nullptr && c->get_size() == creature_size::tiny ) { return false; } if( c != nullptr ) { @@ -748,7 +748,7 @@ bool trapfunc::pit( const tripoint &p, Creature *c, item * ) return false; } // tiny animals aren't hurt by falling into pits - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } const float eff = pit_effectiveness( p ); @@ -799,7 +799,7 @@ bool trapfunc::pit_spikes( const tripoint &p, Creature *c, item * ) return false; } // tiny animals aren't hurt by falling into spiked pits - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You fall in a spiked pit!" ), @@ -880,7 +880,7 @@ bool trapfunc::pit_glass( const tripoint &p, Creature *c, item * ) return false; } // tiny animals aren't hurt by falling into glass pits - if( c->get_size() == MS_TINY ) { + if( c->get_size() == creature_size::tiny ) { return false; } c->add_msg_player_or_npc( m_bad, _( "You fall in a pit filled with glass shards!" ), @@ -1068,7 +1068,7 @@ static bool sinkhole_safety_roll( player *p, const itype_id &itemname, const int bool trapfunc::sinkhole( const tripoint &p, Creature *c, item *i ) { // tiny creatures don't trigger the sinkhole to collapse - if( c == nullptr || c->get_size() == MS_TINY ) { + if( c == nullptr || c->get_size() == creature_size::tiny ) { return false; } monster *z = dynamic_cast( c ); diff --git a/src/ui.cpp b/src/ui.cpp index 0e18873bc737..d2b6d0f212aa 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -199,7 +199,7 @@ void uilist::init() callback = nullptr; // * uilist_callback filter.clear(); // filter string. If "", show everything fentries.clear(); // fentries is the actual display after filtering, and maps displayed entry number to actual entry number - fselected = 0; // fentries[selected] + fselected = 0; // selected = fentries[fselected] filtering = true; // enable list display filtering via '/' or '.' filtering_igncase = true; // ignore case when filtering max_entry_len = 0; @@ -211,6 +211,60 @@ void uilist::init() additional_actions.clear(); } +input_context uilist::create_main_input_context() const +{ + input_context ctxt( input_category ); + ctxt.register_updown(); + ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) ); + ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) ); + ctxt.register_action( "HOME", to_translation( "Go to first entry" ) ); + ctxt.register_action( "END", to_translation( "Go to last entry" ) ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); + if( allow_cancel ) { + ctxt.register_action( "QUIT" ); + } + ctxt.register_action( "SELECT" ); + ctxt.register_action( "CONFIRM" ); + ctxt.register_action( "FILTER" ); + ctxt.register_action( "ANY_INPUT" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + for( const auto &additional_action : additional_actions ) { + ctxt.register_action( additional_action.first, additional_action.second ); + } + return ctxt; +} + +input_context uilist::create_filter_input_context() const +{ + input_context ctxt( input_category ); + // string input popup actions + ctxt.register_action( "TEXT.LEFT" ); + ctxt.register_action( "TEXT.RIGHT" ); + ctxt.register_action( "TEXT.QUIT" ); + ctxt.register_action( "TEXT.CONFIRM" ); + ctxt.register_action( "TEXT.CLEAR" ); + ctxt.register_action( "TEXT.BACKSPACE" ); + ctxt.register_action( "TEXT.HOME" ); + ctxt.register_action( "TEXT.END" ); + ctxt.register_action( "TEXT.DELETE" ); +#if defined( TILES ) + ctxt.register_action( "TEXT.PASTE" ); +#endif + ctxt.register_action( "TEXT.INPUT_FROM_FILE" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "ANY_INPUT" ); + // uilist actions + ctxt.register_updown(); + ctxt.register_action( "PAGE_UP", to_translation( "Fast scroll up" ) ); + ctxt.register_action( "PAGE_DOWN", to_translation( "Fast scroll down" ) ); + ctxt.register_action( "HOME", to_translation( "Go to first entry" ) ); + ctxt.register_action( "END", to_translation( "Go to last entry" ) ); + ctxt.register_action( "SCROLL_UP" ); + ctxt.register_action( "SCROLL_DOWN" ); + return ctxt; +} + void uilist::filterlist() { bool filtering = ( this->filtering && !filter.empty() ); @@ -288,16 +342,9 @@ void uilist::set_filter( const std::string &fstr ) void uilist::inputfilter() { - input_context ctxt( input_category ); - ctxt.register_updown(); - ctxt.register_action( "PAGE_UP" ); - ctxt.register_action( "PAGE_DOWN" ); - ctxt.register_action( "SCROLL_UP" ); - ctxt.register_action( "SCROLL_DOWN" ); - ctxt.register_action( "ANY_INPUT" ); + input_context ctxt = create_filter_input_context(); filter_popup = std::make_unique(); filter_popup->context( ctxt ).text( filter ) - .ignore_custom_actions( false ) .max_length( 256 ) .window( window, point( 4, w_height - 1 ), w_width - 4 ); ime_sentry sentry; @@ -607,7 +654,7 @@ void uilist::apply_scrollbar() /** * Generate and refresh output */ -void uilist::show() +void uilist::show( ui_adaptor &ui ) { if( !started ) { setup(); @@ -643,6 +690,10 @@ void uilist::show() const int pad_size = std::max( 0, w_width - 2 - pad_left - pad_right ); const std::string padspaces = std::string( pad_size, ' ' ); + for( uilist_entry &entry : entries ) { + entry.drawn_rect = std::nullopt; + } + for( int fei = vshift, si = 0; si < vmax; fei++, si++ ) { if( fei < static_cast( fentries.size() ) ) { int ei = fentries [ fei ]; @@ -721,10 +772,13 @@ void uilist::show() } } + std::optional cursor_pos; if( filter_popup ) { mvwprintz( window, point( 2, w_height - 1 ), border_color, "< " ); mvwprintz( window, point( w_width - 3, w_height - 1 ), border_color, " >" ); filter_popup->query( /*loop=*/false, /*draw_only=*/true ); + // Record cursor immediately after filter_popup drawing + ui.record_term_cursor(); } else { if( !filter.empty() ) { mvwprintz( window, point( 2, w_height - 1 ), border_color, "< %s >", filter ); @@ -737,6 +791,11 @@ void uilist::show() if( callback != nullptr ) { callback->refresh( this ); } + + if( cursor_pos ) { + wmove( window, cursor_pos.value() ); + wnoutrefresh( window ); + } } int uilist::scroll_amount_from_action( const std::string &action ) @@ -820,8 +879,8 @@ shared_ptr_fast uilist::create_or_get_ui_adaptor() shared_ptr_fast current_ui = ui.lock(); if( !current_ui ) { ui = current_ui = make_shared_fast(); - current_ui->on_redraw( [this]( const ui_adaptor & ) { - show(); + current_ui->on_redraw( [this]( ui_adaptor & ui ) { + show( ui ); } ); current_ui->on_screen_resize( [this]( ui_adaptor & ui ) { reposition( ui ); @@ -849,23 +908,7 @@ void uilist::query( bool loop, int timeout ) } ret = UILIST_WAIT_INPUT; - input_context ctxt( input_category ); - ctxt.register_updown(); - ctxt.register_action( "PAGE_UP" ); - ctxt.register_action( "PAGE_DOWN" ); - ctxt.register_action( "SCROLL_UP" ); - ctxt.register_action( "SCROLL_DOWN" ); - if( allow_cancel ) { - ctxt.register_action( "QUIT" ); - } - ctxt.register_action( "CONFIRM" ); - ctxt.register_action( "FILTER" ); - ctxt.register_action( "ANY_INPUT" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - for( const auto &additional_action : additional_actions ) { - ctxt.register_action( additional_action.first, additional_action.second ); - } - hotkeys = ctxt.get_available_single_char_hotkeys( hotkeys ); + input_context ctxt = create_main_input_context(); shared_ptr_fast ui = create_or_get_ui_adaptor(); @@ -889,15 +932,25 @@ void uilist::query( bool loop, int timeout ) /* nothing */ } else if( filtering && ret_act == "FILTER" ) { inputfilter(); - } else if( iter != keymap.end() ) { - selected = iter->second; - if( entries[ selected ].enabled ) { - ret = entries[ selected ].retval; // valid - } else if( allow_disabled ) { - ret = entries[selected].retval; // disabled - } - if( callback != nullptr ) { - callback->select( this ); + } else if( ret_act == "ANY_INPUT" && iter != keymap.end() ) { + // only handle "ANY_INPUT" since "HELP_KEYBINDINGS" is already + // handled by the input context and the caller might want to handle + // its custom actions + const auto it = std::find( fentries.begin(), fentries.end(), iter->second ); + if( it != fentries.end() ) { + const bool enabled = entries[*it].enabled; + if( enabled || allow_disabled || hilight_disabled ) { + // Change the selection to display correctly when this function + // is called again. + fselected = std::distance( fentries.begin(), it ); + selected = *it; + if( enabled || allow_disabled ) { + ret = entries[selected].retval; + } + if( callback != nullptr ) { + callback->select( this ); + } + } } } else if( !fentries.empty() && ret_act == "CONFIRM" ) { if( entries[ selected ].enabled ) { diff --git a/src/ui.h b/src/ui.h index 7658ca69cd96..7abee2417407 100644 --- a/src/ui.h +++ b/src/ui.h @@ -10,6 +10,7 @@ #include #include "color.h" +#include "cuboid_rectangle.h" #include "cursesdef.h" #include "memory_fast.h" #include "pimpl.h" @@ -123,6 +124,8 @@ struct uilist_entry { text_color = c; return *this; } + + std::optional> drawn_rect; }; /** @@ -236,7 +239,7 @@ class uilist // NOLINT(cata-xy) void setup(); // initialize the window or reposition it after screen size change. void reposition( ui_adaptor &ui ); - void show(); + void show( ui_adaptor &ui ); bool scrollby( int scrollby ); void query( bool loop = true, int timeout = -1 ); @@ -368,6 +371,8 @@ class uilist // NOLINT(cata-xy) private: std::string hotkeys; report_color_error _color_error = report_color_error::yes; + input_context create_main_input_context() const; + input_context create_filter_input_context() const; public: // Iternal states @@ -411,7 +416,6 @@ class uilist // NOLINT(cata-xy) std::string ret_act; int ret; int keypress; - int selected; }; diff --git a/src/ui_manager.cpp b/src/ui_manager.cpp index 20590600cd2a..797a0a6c5f6c 100644 --- a/src/ui_manager.cpp +++ b/src/ui_manager.cpp @@ -1,6 +1,7 @@ #include "ui_manager.h" #include +#include #include #include #include @@ -135,6 +136,66 @@ void ui_adaptor::on_screen_resize( const screen_resize_callback_t &fun ) screen_resized_cb = fun; } +void ui_adaptor::set_cursor( const catacurses::window &w, const point &pos ) +{ +#if !defined( TILES ) + cursor_type = cursor::custom; + cursor_pos = point( getbegx( w ), getbegy( w ) ) + pos; +#else + // Unimplemented + cursor_type = cursor::disabled; + static_cast( w ); + static_cast( pos ); +#endif +} + +void ui_adaptor::record_cursor( const catacurses::window &w ) +{ +#if !defined( TILES ) + cursor_type = cursor::custom; + cursor_pos = point( getbegx( w ) + getcurx( w ), getbegy( w ) + getcury( w ) ); +#else + // Unimplemented + cursor_type = cursor::disabled; + static_cast( w ); +#endif +} + +void ui_adaptor::record_term_cursor() +{ +#if !defined( TILES ) + cursor_type = cursor::custom; + cursor_pos = point( getcurx( catacurses::newscr ), getcury( catacurses::newscr ) ); +#else + // Unimplemented + cursor_type = cursor::disabled; +#endif +} + +void ui_adaptor::default_cursor() +{ +#if !defined( TILES ) + cursor_type = cursor::last; +#else + // Unimplemented + cursor_type = cursor::disabled; +#endif +} + +void ui_adaptor::disable_cursor() +{ + cursor_type = cursor::disabled; +} + +static void restore_cursor( const point &p ) +{ +#if !defined( TILES ) + wmove( catacurses::newscr, p ); +#else + static_cast( p ); +#endif +} + void ui_adaptor::mark_resize() const { deferred_resize = true; @@ -343,17 +404,29 @@ void ui_adaptor::redraw_invalidated() first_enabled = ui_stack_copy->begin() + ( first_enabled - ui_stack_orig->begin() ); ui_stack_orig = &*ui_stack_copy; } + std::optional cursor_pos; for( auto it = first_enabled; !restart_redrawing && it != ui_stack_orig->end(); ++it ) { - const ui_adaptor &ui = *it; + ui_adaptor &ui = *it; if( ui.invalidated ) { if( ui.redraw_cb ) { + ui.default_cursor(); ui.redraw_cb( ui ); + if( ui.cursor_type == cursor::last ) { + ui.record_term_cursor(); + assert( ui.cursor_type != cursor::last ); + } + if( ui.cursor_type == cursor::custom ) { + cursor_pos = ui.cursor_pos; + } } if( !restart_redrawing ) { ui.invalidated = false; } } } + if( !restart_redrawing && cursor_pos.has_value() ) { + restore_cursor( cursor_pos.value() ); + } } } while( restart_redrawing ); } diff --git a/src/ui_manager.h b/src/ui_manager.h index dcaa0978ba4c..44d0465bcb1e 100644 --- a/src/ui_manager.h +++ b/src/ui_manager.h @@ -35,11 +35,14 @@ class window; * // Mark the resize callback to be called on the first redraw * ui.mark_resize(); * // Things to do when redrawing the UI - * ui.on_redraw( [&]( const ui_adaptor & ) { + * ui.on_redraw( [&]( ui_adaptor & ui ) { * // Clear UI area * werase( win ); * // Print things * mvwprintw( win, point_zero, "Hello World!" ); + * // Record the cursor position for screen readers and IME preview to + * // correctly function on curses + * ui.record_cursor( win ); * // Write to frame buffer * wnoutrefresh( win ); * } ); @@ -64,7 +67,7 @@ class window; class ui_adaptor { public: - using redraw_callback_t = std::function; + using redraw_callback_t = std::function; using screen_resize_callback_t = std::function; struct disable_uis_below { @@ -133,7 +136,8 @@ class ui_adaptor * - Construct new `ui_adaptor` instances * - Deconstruct old `ui_adaptor` instances * - Call `redraw` or `screen_resized` - * - (Redraw callback) call `position_from_window` + * - (Redraw callback) call `position_from_window`, `position`, or + * `invalidate_ui` * - Call any function that does these things, except for `debugmsg` * * Otherwise, display glitches or even crashes might happen. @@ -142,6 +146,46 @@ class ui_adaptor /* See `on_redraw`. */ void on_screen_resize( const screen_resize_callback_t &fun ); + /** + * Automatically set the termianl cursor to a window position after + * drawing is done. The cursor position of the last drawn UI takes + * effect. This ensures the cursor is always set to the desired position + * even if some subsequent drawing code moves the cursor, so screen + * readers (and in the case of text input, the IME preview) can + * correctly function. This is only supposed to be called from the + * `on_redraw` callback on the `ui_adaptor` argument of the callback. + * Currently only supports curses (on tiles, the windows may have + * different cell sizes, so the current implementation of recording the + * on-screen position does not work, and screen readers do not work with + * the current tiles implementation anyway). + */ + void set_cursor( const catacurses::window &win, const point &pos ); + /** + * Record the current window cursor position and restore it when drawing + * is done. The most recent cursor position is recorded regardless of + * whether the terminal cursor is updated by refreshing the window. See + * also `set_cursor`. + */ + void record_cursor( const catacurses::window &win ); + /** + * Record the current terminal cursor position (where the cursor was + * placed when refreshing the last window) and restore it when drawing + * is done. See also `set_cursor`. + */ + void record_term_cursor(); + /** + * Use the terminal cursor position when the redraw callback returns. + * This is the default. See also `set_cursor`. + */ + void default_cursor(); + /** + * Do not set cursor for this `ui_adaptor`. If any higher or lower UI + * sets the cursor, that cursor position is used instead. If no UI sets + * the cursor, the final cursor position when drawing is done is used. + * See also `set_cursor`. + */ + void disable_cursor(); + /** * Mark this `ui_adaptor` for resizing the next time it is redrawn. * This is normally called alongside `on_screen_resize` to initialize @@ -178,6 +222,16 @@ class ui_adaptor redraw_callback_t redraw_cb; screen_resize_callback_t screen_resized_cb; + // For optimization purposes, these value may or may not be reset or + // modified before, during, or after drawing, so they do not necessarily + // indicate what the current cursor position would be, and therefore + // should not be made public or have a public getter. + enum class cursor { + last, custom, disabled + }; + cursor cursor_type = cursor::last; + point cursor_pos; + bool disabling_uis_below; bool is_debug_message_ui; diff --git a/src/veh_interact.cpp b/src/veh_interact.cpp index e1172f2484e9..b6e5bab5f1a7 100644 --- a/src/veh_interact.cpp +++ b/src/veh_interact.cpp @@ -2656,6 +2656,26 @@ void veh_interact::display_name() wnoutrefresh( w_name ); } +static std::string veh_act_desc( const input_context &ctxt, const std::string &id, + const std::string &desc, const task_reason reason ) +{ + static const translation inline_fmt_enabled = to_translation( + "keybinding", "%1$s%2$s%3$s" ); + static const translation inline_fmt_disabled = to_translation( + "keybinding", "%1$s%2$s%3$s" ); + static const translation separate_fmt_enabled = to_translation( + "keybinding", "%1$s-%2$s" ); + static const translation separate_fmt_disabled = to_translation( + "keybinding", "%1$s-%2$s" ); + if( reason == task_reason::CAN_DO ) { + return ctxt.get_desc( id, desc, input_context::allow_all_keys, + inline_fmt_enabled, separate_fmt_enabled ); + } else { + return ctxt.get_desc( id, desc, input_context::allow_all_keys, + inline_fmt_disabled, separate_fmt_disabled ); + } +} + /** * Prints the list of usable commands, and highlights the hotkeys used to activate them. */ @@ -2668,65 +2688,59 @@ void veh_interact::display_mode() // NOLINTNEXTLINE(cata-use-named-point-constants) print_colored_text( w_mode, point( 1, 0 ), title_col, title_col, title.value() ); } else { - size_t esc_pos = display_esc( w_mode ); - - // broken indentation preserved to avoid breaking git history for large number of lines - const std::array actions = { { - { _( "nstall" ) }, - { _( "epair" ) }, - { _( "end" ) }, - { _( "reill" ) }, - { _( "remve" ) }, - { _( "iphon" ) }, - { _( "unloa" ) }, - { _( "cre" ) }, - { _( "sha

e" ) }, - { _( "rname" ) }, - { _( "lbel" ) }, + constexpr size_t action_cnt = 11; + const std::array actions = { { + veh_act_desc( main_context, "INSTALL", + pgettext( "veh_interact", "install" ), + cant_do( 'i' ) ), + veh_act_desc( main_context, "REPAIR", + pgettext( "veh_interact", "repair" ), + cant_do( 'r' ) ), + veh_act_desc( main_context, "MEND", + pgettext( "veh_interact", "mend" ), + cant_do( 'm' ) ), + veh_act_desc( main_context, "REFILL", + pgettext( "veh_interact", "refill" ), + cant_do( 'f' ) ), + veh_act_desc( main_context, "REMOVE", + pgettext( "veh_interact", "remove" ), + cant_do( 'o' ) ), + veh_act_desc( main_context, "SIPHON", + pgettext( "veh_interact", "siphon" ), + cant_do( 's' ) ), + veh_act_desc( main_context, "UNLOAD", + pgettext( "veh_interact", "unload" ), + cant_do( 'd' ) ), + veh_act_desc( main_context, "ASSIGN_CREW", + pgettext( "veh_interact", "crew" ), + cant_do( 'w' ) ), + veh_act_desc( main_context, "RENAME", + pgettext( "veh_interact", "rename" ), + task_reason::CAN_DO ), + veh_act_desc( main_context, "RELABEL", + pgettext( "veh_interact", "label" ), + cant_do( 'a' ) ), + veh_act_desc( main_context, "QUIT", + pgettext( "veh_interact", "back" ), + task_reason::CAN_DO ), } }; - const std::array::value> enabled = { { - !cant_do( 'i' ), - !cant_do( 'r' ), - !cant_do( 'm' ), - !cant_do( 'f' ), - !cant_do( 'o' ), - !cant_do( 's' ), - !cant_do( 'd' ), - !cant_do( 'w' ), - !cant_do( 'p' ), - true, // 'rename' is always available - !cant_do( 'a' ), - } - }; - - int pos[std::tuple_size::value + 1]; - pos[0] = 1; - for( size_t i = 0; i < actions.size(); i++ ) { - pos[i + 1] = pos[i] + utf8_width( actions[i] ) - 2; + std::array < int, action_cnt + 1 > pos; + pos[0] = 0; + for( size_t i = 0; i < action_cnt; i++ ) { + pos[i + 1] = pos[i] + utf8_width( actions[i], true ); } - int spacing = static_cast( ( esc_pos - 1 - pos[actions.size()] ) / actions.size() ); - int shift = static_cast( ( esc_pos - pos[actions.size()] - spacing * - ( actions.size() - 1 ) ) / 2 ) - 1; - for( size_t i = 0; i < actions.size(); i++ ) { - shortcut_print( w_mode, point( pos[i] + spacing * i + shift, 0 ), - enabled[i] ? c_light_gray : c_dark_gray, enabled[i] ? c_light_green : c_green, - actions[i] ); + const int space = std::max( getmaxx( w_mode ) - pos.back(), action_cnt + 1 ); + for( size_t i = 0; i < action_cnt; i++ ) { + nc_color dummy = c_white; + print_colored_text( w_mode, point( pos[i] + space * ( i + 1 ) / ( action_cnt + 1 ), 0 ), + dummy, c_white, actions[i] ); } } wnoutrefresh( w_mode ); } -size_t veh_interact::display_esc( const catacurses::window &win ) -{ - std::string backstr = _( "-back" ); - // right text align - size_t pos = getmaxx( win ) - utf8_width( backstr ) + 2; - shortcut_print( win, point( pos, 0 ), c_light_gray, c_light_green, backstr ); - return pos; -} - /** * Draws the list of parts that can be mounted in the selected square. Used * when installing new parts. diff --git a/src/veh_interact.h b/src/veh_interact.h index 6bab27f1c97e..d7fc58a9a6af 100644 --- a/src/veh_interact.h +++ b/src/veh_interact.h @@ -163,7 +163,6 @@ class veh_interact void display_mode(); void display_list( size_t pos, const std::vector &list, int header = 0 ); void display_details( const vpart_info *part ); - size_t display_esc( const catacurses::window &win ); struct part_option { part_option( const std::string &key, vehicle_part *part, char hotkey, diff --git a/src/wincurse.cpp b/src/wincurse.cpp index 2bb6140cc642..801bbbed0dfb 100644 --- a/src/wincurse.cpp +++ b/src/wincurse.cpp @@ -712,17 +712,17 @@ input_event input_manager::get_input_event() input_event rval; if( lastchar == ERR ) { if( input_timeout > 0 ) { - rval.type = CATA_INPUT_TIMEOUT; + rval.type = input_event_t::timeout; } else { - rval.type = CATA_INPUT_ERROR; + rval.type = input_event_t::error; } } else { // == Unicode DELETE if( lastchar == 127 ) { previously_pressed_key = KEY_BACKSPACE; - return input_event( KEY_BACKSPACE, CATA_INPUT_KEYBOARD ); + return input_event( KEY_BACKSPACE, input_event_t::keyboard ); } - rval.type = CATA_INPUT_KEYBOARD; + rval.type = input_event_t::keyboard; rval.text = utf32_to_utf8( lastchar ); previously_pressed_key = lastchar; // for compatibility only add the first byte, not the code point diff --git a/src/worldfactory.cpp b/src/worldfactory.cpp index 830f8d13765e..923f0bbc38f8 100644 --- a/src/worldfactory.cpp +++ b/src/worldfactory.cpp @@ -42,6 +42,12 @@ using namespace std::placeholders; // single instance of world generator std::unique_ptr world_generator; +/** + * Max utf-8 character worldname length. + * 0 index is inclusive. + */ +static const int max_worldname_len = 32; + save_t::save_t( const std::string &name ): name( name ) {} std::string save_t::decoded_name() const @@ -1359,6 +1365,32 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL ui_adaptor ui; + string_input_popup spopup; + spopup.max_length( max_worldname_len ); + + const point namebar_pos( 3 + utf8_width( _( "World Name:" ) ), 1 ); + + input_context ctxt( "WORLDGEN_CONFIRM_DIALOG" ); + // dialog actions + ctxt.register_action( "QUIT" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "PICK_RANDOM_WORLDNAME" ); + // string input popup actions + ctxt.register_action( "TEXT.LEFT" ); + ctxt.register_action( "TEXT.RIGHT" ); + ctxt.register_action( "TEXT.CLEAR" ); + ctxt.register_action( "TEXT.BACKSPACE" ); + ctxt.register_action( "TEXT.HOME" ); + ctxt.register_action( "TEXT.END" ); + ctxt.register_action( "TEXT.DELETE" ); +#if defined( TILES ) + ctxt.register_action( "TEXT.PASTE" ); +#endif + ctxt.register_action( "TEXT.INPUT_FROM_FILE" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + ctxt.register_action( "ANY_INPUT" ); + const auto init_windows = [&]( ui_adaptor & ui ) { const int iTooltipHeight = 1; const int iContentHeight = TERMY - 3 - iTooltipHeight; @@ -1368,22 +1400,16 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL w_confirmation = catacurses::newwin( iContentHeight, iMinScreenWidth - 2, point( 1 + iOffsetX, iTooltipHeight + 2 ) ); + // +1 for end-of-text cursor + spopup.window( w_confirmation, namebar_pos, namebar_pos.x + max_worldname_len + 1 ) + .context( ctxt ); + ui.position_from_window( win ); }; init_windows( ui ); ui.on_screen_resize( init_windows ); - int namebar_y = 1; - int namebar_x = 3 + utf8_width( _( "World Name:" ) ); - bool noname = false; - input_context ctxt( "WORLDGEN_CONFIRM_DIALOG" ); - ctxt.register_action( "HELP_KEYBINDINGS" ); - ctxt.register_action( "QUIT" ); - ctxt.register_action( "ANY_INPUT" ); - ctxt.register_action( "NEXT_TAB" ); - ctxt.register_action( "PREV_TAB" ); - ctxt.register_action( "PICK_RANDOM_WORLDNAME" ); std::string worldname = world->world_name; @@ -1392,8 +1418,10 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL ui.on_redraw( [&]( const ui_adaptor & ) { draw_worldgen_tabs( win, 2 ); + wnoutrefresh( win ); - mvwprintz( w_confirmation, point( 2, namebar_y ), c_white, _( "World Name:" ) ); + werase( w_confirmation ); + mvwprintz( w_confirmation, point( 2, namebar_pos.y ), c_white, _( "World Name:" ) ); fold_and_print( w_confirmation, point( 2, 3 ), getmaxx( w_confirmation ) - 2, c_light_gray, _( "Press [%s] to pick a random name for your world." ), ctxt.get_desc( "PICK_RANDOM_WORLDNAME" ) ); @@ -1403,25 +1431,22 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL "to continue, or [%s] to go back and review your world." ), ctxt.get_desc( "NEXT_TAB" ), ctxt.get_desc( "PREV_TAB" ) ); if( noname ) { - mvwprintz( w_confirmation, point( namebar_x, namebar_y ), h_light_gray, + mvwprintz( w_confirmation, namebar_pos, h_light_gray, _( "________NO NAME ENTERED!________" ) ); + wnoutrefresh( w_confirmation ); } else { - mvwprintz( w_confirmation, point( namebar_x, namebar_y ), c_light_gray, worldname ); - wprintz( w_confirmation, h_light_gray, "_" ); - for( int underscores = 31 - utf8_width( worldname ); - underscores > 0; --underscores ) { - wprintz( w_confirmation, c_light_gray, "_" ); - } + // spopup.query_string() will call wnoutrefresh( w_confirmation ), and should + // be called last to position the cursor at the correct place in the curses build. + spopup.text( worldname ); + spopup.query_string( false, true ); } - - wnoutrefresh( win ); - wnoutrefresh( w_confirmation ); } ); do { ui_manager::redraw(); - const std::string action = ctxt.handle_input(); + worldname = spopup.query_string( false ); + const std::string action = ctxt.input_to_action( ctxt.get_raw_input() ); if( action == "NEXT_TAB" ) { if( worldname.empty() ) { noname = true; @@ -1437,13 +1462,9 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL } return 1; } - } else if( query_yn( _( "Are you SURE you're finished?" ) ) ) { - if( valid_worldname( worldname ) ) { - world->world_name = worldname; - return 1; - } else { - continue; - } + } else if( valid_worldname( worldname ) && query_yn( _( "Are you SURE you're finished?" ) ) ) { + world->world_name = worldname; + return 1; } else { continue; } @@ -1455,27 +1476,6 @@ int worldfactory::show_worldgen_tab_confirm( const catacurses::window &win, WORL } else if( action == "QUIT" && ( !on_quit || on_quit() ) ) { world->world_name = worldname; return -999; - } else if( action == "ANY_INPUT" ) { - const input_event ev = ctxt.get_raw_input(); - const int ch = ev.get_first_input(); - utf8_wrapper wrap( worldname ); - utf8_wrapper newtext( ev.text ); - if( ch == KEY_BACKSPACE ) { - if( !wrap.empty() ) { - wrap.erase( wrap.length() - 1, 1 ); - worldname = wrap.str(); - } - } else if( ch == KEY_F( 2 ) ) { - std::string tmp = get_input_string_from_file(); - int tmplen = utf8_width( tmp ); - if( tmplen > 0 && tmplen + utf8_width( worldname ) < 30 ) { - worldname.append( tmp ); - } - } else if( !newtext.empty() && is_char_allowed( newtext.at( 0 ) ) ) { - // No empty string, no slash, no backslash, no control sequence - wrap.append( newtext ); - worldname = wrap.str(); - } } } while( true ); @@ -1583,12 +1583,29 @@ bool worldfactory::valid_worldname( const std::string &name, bool automated ) { std::string msg; - if( name == "save" || name == "TUTORIAL" || name == "DEFENSE" ) { + if( name.empty() ) { + msg = _( "World name cannot be empty!" ); + } else if( name == "save" || name == "TUTORIAL" || name == "DEFENSE" ) { msg = string_format( _( "%s is a reserved name!" ), name ); - } else if( !has_world( name ) ) { - return true; - } else { + } else if( has_world( name ) ) { msg = string_format( _( "A world named %s already exists!" ), name ); + } else { + // just check the raw bytes because unicode characters are always acceptable + bool allowed = true; + for( const char ch : name ) { + if( !is_char_allowed( ch ) ) { + if( std::isprint( ch ) ) { + msg = string_format( _( "World name contains invalid character: '%c'" ), ch ); + } else { + msg = string_format( _( "World name contains invalid character: 0x%x" ), ch ); + } + allowed = false; + break; + } + } + if( allowed ) { + return true; + } } if( !automated ) { popup( msg, PF_GET_KEY ); diff --git a/tests/char_biometrics_test.cpp b/tests/char_biometrics_test.cpp index b702f2ac00fb..38c45e7bd0e4 100644 --- a/tests/char_biometrics_test.cpp +++ b/tests/char_biometrics_test.cpp @@ -58,7 +58,7 @@ TEST_CASE( "character height and body size mutations", "[biometrics][height][mut REQUIRE( dummy.base_height() == init_height ); WHEN( "they are normal-sized (MEDIUM)" ) { - REQUIRE( dummy.get_size() == MS_MEDIUM ); + REQUIRE( dummy.get_size() == creature_size::medium ); THEN( "height is initial height" ) { CHECK( dummy.height() == init_height ); @@ -67,7 +67,7 @@ TEST_CASE( "character height and body size mutations", "[biometrics][height][mut WHEN( "they become SMALL" ) { set_single_trait( dummy, "SMALL" ); - REQUIRE( dummy.get_size() == MS_SMALL ); + REQUIRE( dummy.get_size() == creature_size::small ); THEN( "they are 50cm shorter" ) { CHECK( dummy.height() == init_height - 50 ); @@ -76,7 +76,7 @@ TEST_CASE( "character height and body size mutations", "[biometrics][height][mut WHEN( "they become LARGE" ) { set_single_trait( dummy, "LARGE" ); - REQUIRE( dummy.get_size() == MS_LARGE ); + REQUIRE( dummy.get_size() == creature_size::large ); THEN( "they are 50cm taller" ) { CHECK( dummy.height() == init_height + 50 ); @@ -85,7 +85,7 @@ TEST_CASE( "character height and body size mutations", "[biometrics][height][mut WHEN( "they become HUGE" ) { set_single_trait( dummy, "HUGE" ); - REQUIRE( dummy.get_size() == MS_HUGE ); + REQUIRE( dummy.get_size() == creature_size::huge ); THEN( "they are 100cm taler" ) { CHECK( dummy.height() == init_height + 100 ); diff --git a/tests/crafting_test.cpp b/tests/crafting_test.cpp index 43bc936046a8..22c2d7357678 100644 --- a/tests/crafting_test.cpp +++ b/tests/crafting_test.cpp @@ -742,7 +742,7 @@ TEST_CASE( "tool selection ui", "[crafting][ui]" ) REQUIRE( map_inv.size() == 1 ); CAPTURE( map_inv.find_item( 0 ).display_name() ); CHECK( result.comp.type == tool_component.type ); - CHECK( result.use_from == use_from_map ); + CHECK( result.use_from == usage_from::map ); } } @@ -758,7 +758,7 @@ TEST_CASE( "tool selection ui", "[crafting][ui]" ) DEFAULT_HOTKEYS, cost_adjustment::none ); THEN( "Use from is set to none" ) { - CHECK( result.use_from == use_from_none ); + CHECK( result.use_from == usage_from::none ); } } diff --git a/tests/creature_test.cpp b/tests/creature_test.cpp index 4700b7600003..9487f1387bb3 100644 --- a/tests/creature_test.cpp +++ b/tests/creature_test.cpp @@ -58,8 +58,8 @@ const auto expected_larger = Expected }; -void calculate_bodypart_distribution( const enum m_size attacker_size, - const enum m_size defender_size, +void calculate_bodypart_distribution( const enum creature_size attacker_size, + const enum creature_size defender_size, const int hit_roll, const Weights &expected ) { INFO( "hit roll = " << hit_roll ); @@ -105,25 +105,34 @@ TEST_CASE( "Check distribution of attacks to body parts for same sized opponents { rng_set_engine_seed( 4242424242 ); - calculate_bodypart_distribution( MS_SMALL, MS_SMALL, 0, expected_same.base ); - calculate_bodypart_distribution( MS_SMALL, MS_SMALL, 1, expected_same.base ); - calculate_bodypart_distribution( MS_SMALL, MS_SMALL, 100, expected_same.max ); + calculate_bodypart_distribution( creature_size::small, creature_size::small, 0, + expected_same.base ); + calculate_bodypart_distribution( creature_size::small, creature_size::small, 1, + expected_same.base ); + calculate_bodypart_distribution( creature_size::small, creature_size::small, 100, + expected_same.max ); } TEST_CASE( "Check distribution of attacks to body parts for smaller attacker." ) { rng_set_engine_seed( 4242424242 ); - calculate_bodypart_distribution( MS_SMALL, MS_MEDIUM, 0, expected_smaller.base ); - calculate_bodypart_distribution( MS_SMALL, MS_MEDIUM, 1, expected_smaller.base ); - calculate_bodypart_distribution( MS_SMALL, MS_MEDIUM, 100, expected_smaller.max ); + calculate_bodypart_distribution( creature_size::small, creature_size::medium, 0, + expected_smaller.base ); + calculate_bodypart_distribution( creature_size::small, creature_size::medium, 1, + expected_smaller.base ); + calculate_bodypart_distribution( creature_size::small, creature_size::medium, 100, + expected_smaller.max ); } TEST_CASE( "Check distribution of attacks to body parts for larger attacker." ) { rng_set_engine_seed( 4242424242 ); - calculate_bodypart_distribution( MS_MEDIUM, MS_SMALL, 0, expected_larger.base ); - calculate_bodypart_distribution( MS_MEDIUM, MS_SMALL, 1, expected_larger.base ); - calculate_bodypart_distribution( MS_MEDIUM, MS_SMALL, 100, expected_larger.max ); + calculate_bodypart_distribution( creature_size::medium, creature_size::small, 0, + expected_larger.base ); + calculate_bodypart_distribution( creature_size::medium, creature_size::small, 1, + expected_larger.base ); + calculate_bodypart_distribution( creature_size::medium, creature_size::small, 100, + expected_larger.max ); } From 36ec9bf39d45341b7033517fd117273d6871b179 Mon Sep 17 00:00:00 2001 From: Pavel Vasin Date: Wed, 5 Jun 2024 20:56:05 +0300 Subject: [PATCH 02/10] fix: prevent stairs leading to deep water (#4739) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix for missaligned movement between stairs | issue 4087 * style(autofix.ci): automated formatting * Fix for warning: suggest parentheses around ‘&&’ * style(autofix.ci): automated formatting * fix logic * style(autofix.ci): automated formatting * added second space to fix 'insufficient spaces at this location. 2 required, but only 1 found' in clang-tidy --------- Co-authored-by: jobasha Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/game.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 44afc044e2a4..69ac19427261 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -10519,7 +10519,8 @@ std::optional game::find_or_make_stairs( map &mp, const int z_after, b if( going_down_1 && mp.has_flag( TFLAG_GOES_UP, u.pos() + tripoint_below ) ) { stairs.emplace( u.pos() + tripoint_below ); } - if( going_up_1 && mp.has_flag( TFLAG_GOES_DOWN, u.pos() + tripoint_above ) ) { + if( going_up_1 && mp.has_flag( TFLAG_GOES_DOWN, u.pos() + tripoint_above ) && + !mp.has_flag( TFLAG_DEEP_WATER, u.pos() + tripoint_below ) ) { stairs.emplace( u.pos() + tripoint_above ); } // We did not find stairs directly above or below, so search the map for them @@ -10527,8 +10528,9 @@ std::optional game::find_or_make_stairs( map &mp, const int z_after, b for( const tripoint &dest : m.points_in_rectangle( omtile_align_start, omtile_align_end ) ) { if( rl_dist( u.pos(), dest ) <= best && ( ( going_down_1 && mp.has_flag( TFLAG_GOES_UP, dest ) ) || - ( going_up_1 && ( mp.has_flag( TFLAG_GOES_DOWN, dest ) || - mp.ter( dest ) == t_manhole_cover ) ) || + ( ( going_up_1 && ( mp.has_flag( TFLAG_GOES_DOWN, dest ) && + !mp.has_flag( TFLAG_DEEP_WATER, dest ) ) ) || + mp.ter( dest ) == t_manhole_cover ) || ( ( movez == 2 || movez == -2 ) && mp.ter( dest ) == t_elevator ) ) ) { stairs.emplace( dest ); best = rl_dist( u.pos(), dest ); @@ -10582,7 +10584,12 @@ std::optional game::find_or_make_stairs( map &mp, const int z_after, b } if( movez > 0 ) { - if( !mp.has_flag( "GOES_DOWN", *stairs ) ) { + if( mp.has_flag( "DEEP_WATER", *stairs ) ) { + if( !query_yn( + _( "There is a huge blob of water! You may be unable to return back down these stairs. Continue up?" ) ) ) { + return std::nullopt; + } + } else if( !mp.has_flag( "GOES_DOWN", *stairs ) ) { if( !query_yn( _( "You may be unable to return back down these stairs. Continue up?" ) ) ) { return std::nullopt; } From 0f9461d6c23d9ffe1d494db907f6096fa1632216 Mon Sep 17 00:00:00 2001 From: 0Monet <146018959+0Monet@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:56:29 +0200 Subject: [PATCH 03/10] fix: remove indoor `t_concrete` in speedway (#4743) * Update speedway.json * Update speedway.json --- data/json/mapgen/speedway.json | 2 +- data/json/mapgen_palettes/speedway.json | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data/json/mapgen/speedway.json b/data/json/mapgen/speedway.json index ce17e01f81ed..e64d3d6be034 100644 --- a/data/json/mapgen/speedway.json +++ b/data/json/mapgen/speedway.json @@ -404,7 +404,7 @@ " f -- I-------------------I -- I-------------------I -- I-------------------I -- I-------------------I --.......BNNB.................. ||||||| f", " f -- -- -- -- --........BB.....|cccc|.&&&&&&...AA.DDD. f", " f -- -- -- -- -- ............F|e'''+.&#&&#&......DDD. f", - " f -------------------------------------------------------------------------------------------------------------- ............V|eOOa|.&#&&#&.......... f", + " f -------------------------------------------------------------------------------------------------------------- ............V|eOOa̱|.&#&&#&.......... f", " f -------------------------------------------------------------------------------------------------------------- ............||||||.&#&&#&.........A f", " f ---- . . ...4.....&#&&#&. f" ], diff --git a/data/json/mapgen_palettes/speedway.json b/data/json/mapgen_palettes/speedway.json index 00ac78d2d2a3..66b14bf465de 100644 --- a/data/json/mapgen_palettes/speedway.json +++ b/data/json/mapgen_palettes/speedway.json @@ -49,6 +49,7 @@ "d": "f_desk", "i": "f_filing_cabinet", "a": "f_trashcan", + "a̱": "f_trashcan", "A": "f_trashcan", "D": "f_dumpster", "S": "f_sink", @@ -68,6 +69,7 @@ "d": { "item": "SUS_office_desk", "chance": 33 }, "i": { "item": "SUS_office_filing_cabinet", "chance": 25 }, "a": { "item": "trash", "chance": 66, "repeat": [ 1, 3 ] }, + "a̱": { "item": "trash", "chance": 66, "repeat": [ 1, 3 ] }, "A": { "item": "trash", "chance": 66, "repeat": [ 1, 3 ] }, "D": { "item": "trash", "chance": 80, "repeat": [ 4, 8 ] } }, From acff08ce48aeab4940e3fc7d3d9a03940f9a22e6 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Wed, 5 Jun 2024 19:46:31 -0500 Subject: [PATCH 04/10] fix: mossberg 590A1 no longer has obsoleted gunmods installed on it (#4744) --- data/json/items/gun/shot.json | 1 - 1 file changed, 1 deletion(-) diff --git a/data/json/items/gun/shot.json b/data/json/items/gun/shot.json index d66bcafc6629..9a8a8b860626 100644 --- a/data/json/items/gun/shot.json +++ b/data/json/items/gun/shot.json @@ -210,7 +210,6 @@ "ascii_picture": "mossberg590", "volume": "2548 ml", "looks_like": "mossberg_500", - "default_mods": [ "sights_mount", "underbarrel_mount" ], "price": "700 USD", "barrel_length": "0 ml", "clip_size": 9 From df3c09a8bd236518b02178f696e55fed1791cc85 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Wed, 5 Jun 2024 19:47:13 -0500 Subject: [PATCH 05/10] fix: chitin goes on the outside, not the inside (#4742) --- data/json/harvest.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/data/json/harvest.json b/data/json/harvest.json index bf25178343fa..144311f9d451 100644 --- a/data/json/harvest.json +++ b/data/json/harvest.json @@ -541,7 +541,7 @@ { "drop": "meat_tainted", "type": "flesh", "mass_ratio": 0.33 }, { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0035 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.015 }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.15 } + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.15 } ] }, { @@ -554,7 +554,7 @@ { "drop": "mutant_bug_hydrogen_sacs", "type": "flesh", "mass_ratio": 0.2 }, { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0035 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.015 }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.015 } + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.015 } ] }, { @@ -577,7 +577,7 @@ { "drop": "sinew", "type": "bone", "mass_ratio": 0.01 }, { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0035 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.015 }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.15 } + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.15 } ] }, { @@ -588,7 +588,7 @@ { "drop": "mutant_meat", "type": "flesh", "mass_ratio": 0.33 }, { "drop": "mutant_fat", "type": "flesh", "mass_ratio": 0.04 }, { "drop": "sinew", "type": "bone", "mass_ratio": 0.01 }, - { "drop": "acidchitin_piece", "type": "bone", "mass_ratio": 0.15 } + { "drop": "acidchitin_piece", "type": "skin", "mass_ratio": 0.15 } ] }, { @@ -602,7 +602,7 @@ { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0035 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.015 }, { "drop": "bee_sting", "base_num": [ 0, 1 ], "type": "bone" }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.15 } + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.15 } ] }, { @@ -616,7 +616,7 @@ { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.01 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.03 }, { "drop": "wasp_sting", "base_num": [ 0, 1 ], "type": "bone" }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.15 } + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.15 } ] }, { @@ -630,7 +630,7 @@ { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.01 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.03 }, { "drop": "wasp_sting", "base_num": [ 0, 1 ], "type": "bone" }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.15 }, + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.15 }, { "drop": "egg_wasp", "type": "offal", "base_num": [ 10, 30 ], "scale_num": [ 5, 5 ] } ] }, @@ -644,7 +644,7 @@ { "drop": "sinew", "type": "bone", "mass_ratio": 0.005 }, { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0045 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.025 }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.015 }, + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.015 }, { "drop": "egg_dragonfly", "type": "offal", "base_num": [ 5, 35 ], "scale_num": [ 0.3, 0.5 ] } ] }, @@ -659,7 +659,7 @@ { "drop": "mutant_bug_hydrogen_sacs", "type": "flesh", "mass_ratio": 0.2 }, { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0035 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.015 }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.015 }, + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.015 }, { "drop": "egg_firefly", "type": "offal", "base_num": [ 0, 3 ] } ] }, @@ -674,7 +674,7 @@ { "drop": "sinew", "type": "bone", "mass_ratio": 0.05 }, { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0035 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.01 }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.012 } + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.012 } ] }, { @@ -687,7 +687,7 @@ { "drop": "sinew", "type": "bone", "mass_ratio": 0.05 }, { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0035 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.01 }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.012 }, + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.012 }, { "drop": "egg_centipede", "type": "offal", "base_num": [ 3, 10 ], "scale_num": [ 0.5, 1 ], "max": 30 } ] }, @@ -702,7 +702,7 @@ { "drop": "sinew", "type": "bone", "mass_ratio": 0.05 }, { "drop": "mutant_bug_lungs", "type": "flesh", "mass_ratio": 0.0035 }, { "drop": "mutant_bug_organs", "type": "offal", "mass_ratio": 0.01 }, - { "drop": "chitin_piece", "type": "bone", "mass_ratio": 0.3 }, + { "drop": "chitin_piece", "type": "skin", "mass_ratio": 0.3 }, { "drop": "egg_centipede", "type": "offal", "base_num": [ 0, 3 ], "scale_num": [ 0.5, 1 ], "max": 10 } ] }, From 82425035d0e8ad39ce81df9568f43ad979f3c692 Mon Sep 17 00:00:00 2001 From: Chorus System Date: Wed, 5 Jun 2024 23:17:23 -0400 Subject: [PATCH 06/10] feat: Primitive tool conditional names (#4745) * Primitive tools Made stone axe, adze, tomahawk, knife, and shovel be named 'primitive' instead and have conditional names based on their ingredients. * style(autofix.ci): automated formatting --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- data/json/items/tool/knives.json | 12 ++++++++++-- data/json/items/tool/landscaping.json | 8 ++++++-- data/json/items/tool/woodworking.json | 21 +++++++++++++++++---- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/data/json/items/tool/knives.json b/data/json/items/tool/knives.json index 80c0ba965745..af2f9e5433d7 100644 --- a/data/json/items/tool/knives.json +++ b/data/json/items/tool/knives.json @@ -81,8 +81,16 @@ "type": "GENERIC", "category": "tools", "weapon_category": [ "KNIVES" ], - "name": { "str": "stone knife", "str_pl": "stone knives" }, - "description": "This is a sharpened stone set into a hollowed handle. Not nearly as usable as a proper knife, but it's better than nothing.", + "name": { "str": "primitive knife", "str_pl": "primitive knives" }, + "conditional_names": [ + { "type": "COMPONENT_ID", "condition": "rock", "name": { "str": "stone knife", "str_pl": "stone knives" } }, + { + "type": "COMPONENT_ID", + "condition": "ceramic", + "name": { "str": "ceramic knife", "str_pl": "ceramic knives" } + } + ], + "description": "This is a sharpened stone or shard of ceramic set into a hollowed handle. Not nearly as usable as a proper knife, but it's better than nothing.", "weight": "453 g", "volume": "250 ml", "price": "0 cent", diff --git a/data/json/items/tool/landscaping.json b/data/json/items/tool/landscaping.json index a8b9046f1f53..092e2e309fa3 100644 --- a/data/json/items/tool/landscaping.json +++ b/data/json/items/tool/landscaping.json @@ -62,8 +62,12 @@ "type": "GENERIC", "category": "tools", "weapon_category": [ "CLUBS" ], - "name": { "str": "stone shovel" }, - "description": "This is a flattened stone affixed to a stick. It works passably well as a shovel but really can't compare to a real shovel.", + "name": { "str": "primitive shovel" }, + "conditional_names": [ + { "type": "COMPONENT_ID", "condition": "rock", "name": "stone shovel" }, + { "type": "COMPONENT_ID", "condition": "ceramic", "name": "ceramic shovel" } + ], + "description": "This is a flattened stone or ceramic shard affixed to a stick. It works passably well as a shovel but really can't compare to a real shovel.", "weight": "1581 g", "volume": "4 L", "price": "0 cent", diff --git a/data/json/items/tool/woodworking.json b/data/json/items/tool/woodworking.json index a83e6c0a9a3b..94e490dd84c0 100644 --- a/data/json/items/tool/woodworking.json +++ b/data/json/items/tool/woodworking.json @@ -209,8 +209,12 @@ "type": "GENERIC", "category": "tools", "weapon_category": [ "1H_HOOKED" ], - "name": { "str": "stone adze" }, - "description": "This is a stone adze, somewhat useful for smoothing wood objects.", + "name": { "str": "primitive adze" }, + "conditional_names": [ + { "type": "COMPONENT_ID", "condition": "rock", "name": "stone adze" }, + { "type": "COMPONENT_ID", "condition": "ceramic", "name": "ceramic adze" } + ], + "description": "This is a stone or ceramic adze, somewhat useful for smoothing wood objects.", "weight": "1300 g", "volume": "1 L", "price": "10 USD", @@ -229,7 +233,11 @@ "type": "GENERIC", "category": "tools", "weapon_category": [ "2H_AXES" ], - "name": { "str": "stone axe" }, + "name": { "str": "primtitive axe" }, + "conditional_names": [ + { "type": "COMPONENT_ID", "condition": "rock", "name": "stone axe" }, + { "type": "COMPONENT_ID", "condition": "ceramic", "name": "ceramic axe" } + ], "description": "This is a stone with a narrow edge affixed to a stick. It can chop wood, but requires much more effort than a modern axe.", "weight": "3154 g", "volume": "3500 ml", @@ -286,7 +294,12 @@ "weapon_category": [ "1H_AXES" ], "symbol": ";", "color": "light_gray", - "name": { "str": "stone tomahawk" }, + "name": { "str": "primitive tomahawk" }, + "conditional_names": [ + { "type": "COMPONENT_ID", "condition": "rock", "name": "stone tomahawk" }, + { "type": "COMPONENT_ID", "condition": "ceramic", "name": "ceramic tomahawk" }, + { "type": "COMPONENT_ID", "condition": "bone", "name": "bone tomahawk" } + ], "description": "A lightweight one-handed combat hatchet typically made of stone or bone, originally used by the Algonquian people. It makes for a half decent tool and weapon.", "price_postapoc": 100, "material": [ "wood", "stone" ], From c9a1b6e352215db7b46f506616c18e6a3ca1039f Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Thu, 6 Jun 2024 12:39:32 -0500 Subject: [PATCH 07/10] fix: update JSON of pet carrier items with correct property (#4749) --- data/json/items/armor/storage.json | 2 +- data/json/items/tool/pets.json | 4 ++-- data/json/items/vehicle/cargo.json | 2 +- data/mods/Aftershock/items/vehicle_items.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/json/items/armor/storage.json b/data/json/items/armor/storage.json index 9d1463cbd653..3fef127619d5 100644 --- a/data/json/items/armor/storage.json +++ b/data/json/items/armor/storage.json @@ -654,7 +654,7 @@ "storage": "4 L", "warmth": 8, "material_thickness": 2, - "properties": [ [ "monster_size_capacity", "SMALL" ] ], + "properties": [ [ "creature_size_capacity", "SMALL" ] ], "use_action": "CAPTURE_MONSTER_ACT", "valid_mods": [ "resized_large" ], "flags": [ "BELTED", "WATERPROOF" ] diff --git a/data/json/items/tool/pets.json b/data/json/items/tool/pets.json index 16eeb23155bc..dae965ba7ace 100644 --- a/data/json/items/tool/pets.json +++ b/data/json/items/tool/pets.json @@ -14,7 +14,7 @@ "material": [ "steel" ], "symbol": "#", "color": "light_gray", - "properties": [ [ "monster_size_capacity", "TINY" ] ], + "properties": [ [ "creature_size_capacity", "TINY" ] ], "use_action": "CAPTURE_MONSTER_ACT", "flags": [ "TRADER_AVOID" ] }, @@ -104,7 +104,7 @@ "material": [ "steel", "plastic" ], "symbol": "#", "color": "light_gray", - "properties": [ [ "monster_size_capacity", "SMALL" ] ], + "properties": [ [ "creature_size_capacity", "SMALL" ] ], "use_action": "CAPTURE_MONSTER_ACT", "flags": [ "TRADER_AVOID" ] }, diff --git a/data/json/items/vehicle/cargo.json b/data/json/items/vehicle/cargo.json index 2f2f316c5daa..03ca4c2649ca 100644 --- a/data/json/items/vehicle/cargo.json +++ b/data/json/items/vehicle/cargo.json @@ -111,7 +111,7 @@ "symbol": "]", "color": "light_gray", "looks_like": "cargo_rack", - "properties": [ [ "monster_size_capacity", "HUGE" ] ], + "properties": [ [ "creature_size_capacity", "HUGE" ] ], "use_action": "CAPTURE_MONSTER_VEH", "flags": [ "TRADER_AVOID" ] }, diff --git a/data/mods/Aftershock/items/vehicle_items.json b/data/mods/Aftershock/items/vehicle_items.json index d4e563d71adc..7a66dcc1474d 100644 --- a/data/mods/Aftershock/items/vehicle_items.json +++ b/data/mods/Aftershock/items/vehicle_items.json @@ -12,7 +12,7 @@ "symbol": "]", "color": "light_gray", "looks_like": "cargo_rack", - "properties": [ [ "monster_size_capacity", "LARGE" ] ], + "properties": [ [ "creature_size_capacity", "LARGE" ] ], "use_action": "CAPTURE_MONSTER_VEH", "flags": [ "TRADER_AVOID" ] } From 25ea154be9bb5636355d2e9fd19a0f2ab3076d34 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Thu, 6 Jun 2024 12:57:26 -0500 Subject: [PATCH 08/10] feat(balance): increase variety of potential damage states for spawned vehicles (#4746) * feat(balance): increase variety of potential damage states for spawned vehicles * Update vehicle.cpp * Update vehicle_power_test.cpp --- src/vehicle.cpp | 29 ++++++++++++++++++++++------- tests/vehicle_power_test.cpp | 1 + 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/vehicle.cpp b/src/vehicle.cpp index b5c52d6f5a91..6d33aad563a5 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -620,11 +620,15 @@ void vehicle::init_state( int init_veh_fuel, int init_veh_status ) } for( const vpart_reference &vp : get_parts_including_carried( "FRIDGE" ) ) { - vp.part().enabled = true; + if( one_in( 2 ) ) { + vp.part().enabled = true; + } } for( const vpart_reference &vp : get_parts_including_carried( "FREEZER" ) ) { - vp.part().enabled = true; + if( one_in( 2 ) ) { + vp.part().enabled = true; + } } for( const vpart_reference &vp : get_parts_including_carried( "WATER_PURIFIER" ) ) { @@ -637,8 +641,8 @@ void vehicle::init_state( int init_veh_fuel, int init_veh_status ) const size_t p = vp.part_index(); vehicle_part &pt = vp.part(); - if( vp.has_feature( VPFLAG_REACTOR ) ) { - // De-hardcoded reactors. Should always start active + if( vp.has_feature( VPFLAG_REACTOR ) && one_in( 4 ) ) { + // De-hardcoded reactors, may or may not start active pt.enabled = true; } @@ -733,6 +737,11 @@ void vehicle::init_state( int init_veh_fuel, int init_veh_status ) set_hp( pt, 0 ); } + // An added 5% chance to bust each windshield + if( vp.has_feature( "WINDSHIELD" ) && one_in( 20 ) ) { + set_hp( pt, 0 ); + } + /* Bloodsplatter the front-end parts. Assume anything with x > 0 is * the "front" of the vehicle (since the driver's seat is at (0, 0). * We'll be generous with the blood, since some may disappear before @@ -761,6 +770,11 @@ void vehicle::init_state( int init_veh_fuel, int init_veh_status ) blood_inside_pos.emplace( vp.mount() ); } } + + // Potentially bust a single tire if not already wrecking them + if( !destroyTires && !wheelcache.empty() && one_in( 20 ) ) { + set_hp( parts[random_entry( wheelcache )], 0 ); + } } //sets the vehicle to locked, if there is no key and an alarm part exists if( vp.has_feature( "SECURITY" ) && has_no_key && pt.is_available() ) { @@ -772,11 +786,12 @@ void vehicle::init_state( int init_veh_fuel, int init_veh_status ) } } } - // destroy tires until the vehicle is not drivable + // destroy a random number of tires, vehicles with more wheels are more likely to survive if( destroyTires && !wheelcache.empty() ) { int tries = 0; - while( valid_wheel_config() && tries < 100 ) { - // wheel config is still valid, destroy the tire. + int maxtries = wheelcache.size(); + while( valid_wheel_config() && tries < maxtries ) { + // keep going until either we've ruined all wheels or made one attempt for every wheel set_hp( parts[random_entry( wheelcache )], 0 ); tries++; } diff --git a/tests/vehicle_power_test.cpp b/tests/vehicle_power_test.cpp index dab7fdd026f4..4a2918d8c231 100644 --- a/tests/vehicle_power_test.cpp +++ b/tests/vehicle_power_test.cpp @@ -37,6 +37,7 @@ TEST_CASE( "vehicle power with reactor and solar panels", "[vehicle][power]" ) REQUIRE( !veh_ptr->reactors.empty() ); vehicle_part &reactor = veh_ptr->part( veh_ptr->reactors.front() ); + reactor.enabled = true; GIVEN( "the reactor is empty" ) { reactor.ammo_unset(); From 94efdf2a963e19901d57ba370b5e9c657e465681 Mon Sep 17 00:00:00 2001 From: univeous <40604180+univeous@users.noreply.github.com> Date: Fri, 7 Jun 2024 09:14:41 +0900 Subject: [PATCH 09/10] ci: add Apple silicon build (#4747) --- .github/workflows/manual-release.yml | 17 +++++++++++++++++ .github/workflows/release.yml | 17 +++++++++++++++++ tools/copy_mac_libs.py | 2 ++ 3 files changed, 36 insertions(+) diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 67bd4938486b..33d23419815f 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -129,6 +129,20 @@ jobs: artifact: osx-tiles-x64 ext: dmg content: application/x-apple-diskimage + - name: osx-curses-arm + os: macos-14 + mxe: none + tiles: 0 + artifact: osx-curses-arm + ext: dmg + content: application/x-apple-diskimage + - name: osx-tiles-arm + os: macos-14 + mxe: none + tiles: 1 + artifact: osx-tiles-arm + ext: dmg + content: application/x-apple-diskimage - name: Android x64 os: ubuntu-22.04 mxe: none @@ -224,6 +238,8 @@ jobs: if: runner.os == 'macOS' run: | HOMEBREW_NO_AUTO_UPDATE=yes HOMEBREW_NO_INSTALL_CLEANUP=yes brew install sdl2 sdl2_image sdl2_ttf sdl2_mixer gettext ccache parallel + python3 -m venv ./venv + source ./venv/bin/activate pip3 install mac_alias==2.2.0 dmgbuild==1.6.1 biplist - name: Build CBN (linux) @@ -249,6 +265,7 @@ jobs: - name: Build CBN (osx) if: runner.os == 'macOS' run: | + source ./venv/bin/activate make -j3 TILES=${{ matrix.tiles }} SOUND=${{ matrix.tiles }} LUA=1 RELEASE=1 LANGUAGES=all USE_HOME_DIR=1 OSX_MIN=11 PCH=0 dmgdist COMPILER=clang++ mv CataclysmBN-${{ env.VERSION }}.dmg cbn-${{ matrix.artifact }}-${{ env.VERSION }}.dmg - name: Set up JDK 11 (android) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1bb04d1cfc8..c571c5b77c48 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -152,6 +152,20 @@ jobs: artifact: osx-tiles-x64 ext: dmg content: application/x-apple-diskimage + - name: osx-curses-arm + os: macos-14 + mxe: none + tiles: 0 + artifact: osx-curses-arm + ext: dmg + content: application/x-apple-diskimage + - name: osx-tiles-arm + os: macos-14 + mxe: none + tiles: 1 + artifact: osx-tiles-arm + ext: dmg + content: application/x-apple-diskimage - name: Android x64 os: ubuntu-22.04 mxe: none @@ -250,6 +264,8 @@ jobs: if: runner.os == 'macOS' run: | HOMEBREW_NO_AUTO_UPDATE=yes HOMEBREW_NO_INSTALL_CLEANUP=yes brew install sdl2 sdl2_image sdl2_ttf sdl2_mixer gettext ccache parallel + python3 -m venv ./venv + source ./venv/bin/activate pip3 install mac_alias==2.2.0 dmgbuild==1.6.1 biplist - name: Build CBN (linux) @@ -278,6 +294,7 @@ jobs: - name: Build CBN (osx) if: runner.os == 'macOS' run: | + source ./venv/bin/activate make -j3 TILES=${{ matrix.tiles }} SOUND=${{ matrix.tiles }} LUA=1 RELEASE=1 LANGUAGES=all USE_HOME_DIR=1 OSX_MIN=11 PCH=0 dmgdist COMPILER=clang++ mv CataclysmBN-unstable.dmg cbn-${{ matrix.artifact }}-${{ needs.metadata.outputs.tag_name }}.dmg diff --git a/tools/copy_mac_libs.py b/tools/copy_mac_libs.py index e10871f3f4fc..3db7ff131d8d 100644 --- a/tools/copy_mac_libs.py +++ b/tools/copy_mac_libs.py @@ -49,6 +49,8 @@ def copy_and_rewrite(file): if not os.path.isfile(file): # raise Exception("{} is not a file.".format(executable)) return [] + if os.path.exists(executable_dir + "/" + os.path.basename(file)): + return [] otool_ret = subprocess.run(["otool", "-L", file], capture_output=True) if otool_ret.returncode != 0: raise Exception("An error occured in calling otool -L:\n {}" From de7b8a093985f9fba40954c3b1459264a71e6481 Mon Sep 17 00:00:00 2001 From: 0Monet <146018959+0Monet@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:10:04 +0200 Subject: [PATCH 10/10] fix: use regional terrain and `roof_palette` for the arcade (#4752) * Update zombies.json * Update arcade.json * style(autofix.ci): automated formatting --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- data/json/mapgen/arcade.json | 92 ++++++++++------------------ data/json/monstergroups/zombies.json | 6 ++ 2 files changed, 40 insertions(+), 58 deletions(-) diff --git a/data/json/mapgen/arcade.json b/data/json/mapgen/arcade.json index ac1eb7509710..5ad82d0931de 100644 --- a/data/json/mapgen/arcade.json +++ b/data/json/mapgen/arcade.json @@ -1,15 +1,8 @@ [ - { - "name": "GROUP_ARCADE", - "type": "monstergroup", - "default": "mon_null", - "monsters": [ { "monster": "mon_zombie_child", "freq": 600, "cost_multiplier": 1, "pack_size": [ 2, 5 ] } ] - }, { "type": "mapgen", "method": "json", "om_terrain": [ "s_arcade" ], - "weight": 250, "object": { "fill_ter": "t_floor", "rows": [ @@ -21,10 +14,10 @@ ".ssssssssssssssssssssss.", ".ssssssssssssssssssssss.", ".ssssssssssssssssssssss.", - ".ssssssssssssssssssllss.", - ".|---++-OOO-++----|---|.", + ".sssssssssssssssssslLss.", + ".||||++|OOO|++|||||||||.", ".|F |S %|.", - ".|F |-+-|.", + ".|F ||+||.", ".|F FF FF |.", ".|F FF F FF ## |.", ".|F FF F FF # T|.", @@ -32,24 +25,20 @@ ".|F # T|.", ".|F x x x # T|.", ".|FFFFFx x xFFFF #B T|.", - ".|--------------------|.", + ".||||||||||||||||||||||.", ".....................4..", "........................", "........................", "........................" ], "terrain": { - " ": "t_floor", "+": "t_door_c", - "-": "t_wall_r", - ".": [ [ "t_dirt", 5 ], [ "t_grass", 16 ], [ "t_grass_long", 5 ], [ "t_underbrush", 10 ] ], + ".": "t_region_groundcover_urban", "O": "t_window", - "S": "t_floor", - "B": "t_floor", - "%": "t_floor", "_": "t_pavement", - "'": "t_dirt", + "'": "t_region_groundcover_urban", "l": "t_sidewalk", + "L": "t_sidewalk", "s": "t_sidewalk", "4": "t_gutter_downspout", "|": "t_wall_r" @@ -61,15 +50,11 @@ "S": "f_sink", "B": "f_stool", "T": "f_locker", - "l": "f_vending_c", "x": "f_pinball_machine" }, + "vendingmachines": { "l": { "item_group": "vending_drink" }, "L": { "item_group": "vending_food" } }, "toilets": { "%": { } }, - "place_items": [ - { "item": "vending_drink", "x": 19, "y": 8, "chance": 75 }, - { "item": "vending_food", "x": 20, "y": 8, "chance": 75 }, - { "item": "arcade_prizes", "x": 21, "y": [ 14, 18 ], "chance": 95 } - ], + "place_items": [ { "item": "arcade_prizes", "x": 21, "y": [ 14, 18 ], "chance": 95 } ], "place_monsters": [ { "monster": "GROUP_ARCADE", "x": [ 3, 17 ], "y": [ 13, 15 ] } ] } }, @@ -77,44 +62,35 @@ "type": "mapgen", "method": "json", "om_terrain": "s_arcade_roof", - "weight": 100, "object": { "fill_ter": "t_flat_roof", "rows": [ - "........................", - "........................", - "........................", - "........................", - "........................", - "........................", - "........................", - "........................", - "........................", - ".| 3.", - ".| : A 3.", - ".| 3.", - ".| 3.", - ".| 3.", - ".| & 3.", - ".| 3.", - ".| 3.", - ".| X 3.", - ".| 3.", - ".|222222222222222222253.", - "........................", - "........................", - "........................", - "........................" + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " -....................- ", + " -..:.N...............- ", + " -....................- ", + " -....................- ", + " -....................- ", + " -............&.......- ", + " -....................- ", + " -....................- ", + " -...............X....- ", + " -....................- ", + " --------------------5- ", + " ", + " ", + " ", + " " ], - "terrain": { - " ": "t_flat_roof", - ".": "t_open_air", - "|": "t_gutter_west", - "3": "t_gutter_east", - "2": "t_gutter_south", - "5": "t_gutter_drop" - }, - "furniture": { ":": "f_cellphone_booster", "X": "f_small_satelitte_dish", "&": "f_roof_turbine_vent", "A": "f_TV_antenna" }, + "palettes": [ "roof_palette" ], "place_items": [ { "item": "roof_trash", "x": [ 3, 20 ], "y": [ 11, 18 ], "chance": 50, "repeat": [ 1, 3 ] }, { "item": "child_items", "x": [ 3, 20 ], "y": [ 11, 18 ], "chance": 50, "repeat": [ 1, 3 ] } diff --git a/data/json/monstergroups/zombies.json b/data/json/monstergroups/zombies.json index 326d1fc1f4e4..cb50d29b0282 100644 --- a/data/json/monstergroups/zombies.json +++ b/data/json/monstergroups/zombies.json @@ -758,5 +758,11 @@ { "monster": "mon_zombie_technician", "freq": 250, "cost_multiplier": 3 }, { "monster": "mon_zombie_electric", "freq": 150, "cost_multiplier": 5 } ] + }, + { + "name": "GROUP_ARCADE", + "type": "monstergroup", + "default": "mon_null", + "monsters": [ { "monster": "mon_zombie_child", "freq": 600, "cost_multiplier": 1, "pack_size": [ 2, 5 ] } ] } ]