Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Safemode UI #17916

Merged
merged 22 commits into from Aug 27, 2016

Conversation

Projects
None yet
6 participants
@OzoneH3
Copy link
Member

commented Aug 4, 2016

Closes #9285
Closes #17635

This PR implements a autopickup like UI for white-/blacklisting safemode monsters.
If no rules are specified, the old safemode checks will be used.

Todo:

  • Check NPCs agains rules (rule name = human)
  • Added whitelistung with ~ (stolen from enable debug mode)
  • Option to blacklist monsters via the V menu monster list

The example below would:

  • Trigger when any hostile monster is withing 30 tiles
  • Exclude hostile zombie (exact word match) from the whitelist
  • Trigger when a neutral moose is within viewdistance

@mugling

This comment has been minimized.

Copy link
Contributor

commented Aug 4, 2016

One possible addition would be to allow ignoring specific monsters from the interruption prompt?

@OzoneH3

This comment has been minimized.

Copy link
Member Author

commented Aug 4, 2016

Ignoring is already implemented in the current safemode.

Or do you mean something different?

@mugling

This comment has been minimized.

Copy link
Contributor

commented Aug 4, 2016

The message that interrupts long activities (especially crafting)

@mugling

This comment has been minimized.

Copy link
Contributor

commented Aug 4, 2016

@OzoneH3 could possible be implemented via the critter tracker?

@OzoneH3 OzoneH3 force-pushed the OzoneH3:safemode_ui branch to db5fc7d Aug 5, 2016

@OzoneH3 OzoneH3 changed the title [WiP][CR] Safemode UI Safemode UI Aug 5, 2016

@OzoneH3

This comment has been minimized.

Copy link
Member Author

commented Aug 5, 2016

I'll try to figure out the long activity interrution in another PR. It's not really related with the ui.
From what I've seen it shouldn't be too hard.

@@ -1710,6 +1862,7 @@
{ "type":"keybinding", "name": "Control Vehicle", "category":"DEFAULTMODE", "id":"control_vehicle", "bindings":[ { "input_method":"keyboard", "key":"^" } ] },
{ "type":"keybinding", "name": "Toggle Safemode", "category":"DEFAULTMODE", "id":"safemode", "bindings":[ { "input_method":"keyboard", "key":"!" } ] },
{ "type":"keybinding", "name": "Ignore Nearby Enemy", "category":"DEFAULTMODE", "id":"ignore_enemy", "bindings":[ { "input_method":"keyboard", "key":"'" } ] },
{ "type":"keybinding", "name": "Whitelist enemy", "category":"DEFAULTMODE", "id":"whitelist_enemy", "bindings":[ { "input_method":"keyboard", "key":"~" } ] },

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

This should replace "ignore" bind.

This comment has been minimized.

Copy link
@OzoneH3

OzoneH3 Aug 5, 2016

Author Member

Why? You can still ignore enemys without whitelisting them forever.

@@ -1542,3 +1542,23 @@ void Creature::check_dead_state() {
die( nullptr );
}
}

std::pair<std::string, nc_color> const &Creature::get_creature_attitude( Attitude att )

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

This name is very misleading. Should be something like attitude_name or attitude_ui_data.

@@ -11556,14 +11623,21 @@ bool game::check_safe_mode_allowed( bool repeat_safe_mode_warnings )
if( new_seen_mon.empty() ) {
// naming consistent with code in game::mon_info
spotted_creature_name = _( "a hostile survivor" );
get_safemode().whitelist = "human";

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

Would be useful if you could also trigger it on neutral survivors. They can become hostile, steal stuff etc.

const int iContentHeight = FULL_SCREEN_HEIGHT - 2 - iHeaderHeight;

const int iOffsetX = ( TERMX > FULL_SCREEN_WIDTH ) ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0;
const int iOffsetY = ( TERMY > FULL_SCREEN_HEIGHT ) ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0;

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

Are you copy+pasting the UI from somewhere?

It would be a good idea to clean it up. For example, make it cover the whole screen instead of just the middle.

This comment has been minimized.

Copy link
@OzoneH3

OzoneH3 Aug 5, 2016

Author Member

It's a copy of the autopickup ui. Shouldn't it stay in line with the other option menus?

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

Both should cover the whole screen eventually. It doesn't need to be in this PR, though.

This comment has been minimized.

Copy link
@OzoneH3

OzoneH3 Aug 5, 2016

Author Member

Well, all 4 of these are copies: options -> autopickup -> color manager -> safmode

Do they really need to fill the whole screen though?

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

They don't need to, but it can be useful when there are many entries. Options and color manager could sure use it, I don't use autopickup except for NPCs so I don't know how big should it be.

( iLine == i && iColumn == col_IE ) ? hilite( cLineColor ) : cLineColor,
"%s", ( ( vRules[iTab][i].bExclude ) ? rm_prefix( _( "Exclude" ) ).c_str() : rm_prefix(
_( "Include" ) ).c_str() )
);

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

This code is hard to read. The non-standard naming scheme (hungarian notation itself, hungarian notation being mixed with non-hungarian notation) doesn't help.

Could be cleaned up by:

  • Extracting a lambda to cover all 3 mvwprintz without a lot of repetition (wouldn't be easy, but doable)
  • Extracting vRules[iTab][i] to a variable like current_tab
  • Extracting some of the inlined data to named variables, to show what is being displayed
tmpx += shortcut_print( w_header, 0, tmpx, c_white, c_ltgreen, _( "<M>ove" ) ) + 2;
tmpx += shortcut_print( w_header, 0, tmpx, c_white, c_ltgreen, _( "<E>nable" ) ) + 2;
tmpx += shortcut_print( w_header, 0, tmpx, c_white, c_ltgreen, _( "<D>isable" ) ) + 2;
shortcut_print( w_header, 0, tmpx, c_white, c_ltgreen, _( "<T>est" ) );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

A lot of nearly identical entries, consider a vector+loop (easier to maintain).

ctxt.register_action( "MOVE_RULE_UP" );
ctxt.register_action( "MOVE_RULE_DOWN" );
ctxt.register_action( "TEST_RULE" );
ctxt.register_action( "HELP_KEYBINDINGS" );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

Side note here:
We should probably have a register_action overload that takes a container argument.

wprintz( w, c_yellow, ">> " );
} else {
wprintz( w, c_yellow, " " );
}

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

Could be wprintz( w, c_yellow, iLine == i ? ">> " : " " );

bool bActive;
bool bExclude;
int attCreature;
int iProxyDist;

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 5, 2016

Contributor

Is this variable supposed to be proximity distance? proxy and proximity aren't highly related words, it would be better to use just proximity or distance.

// display safemode
for( int i = start_pos; i < ( int )creature_list.size(); i++ ) {
if( i >= start_pos &&
i < start_pos + ( ( content_height > ( int )creature_list.size() ) ?

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Looks like this is supposed to get the content_height or creature_list.size(), whatever is smaller. If so, it should be std::min(static_cast<int>(creature_list.size()), content_height) as that's easier to reason about.

int nmatch = creature_list.size();
std::string buf = string_format( ngettext( "%1$d monster matches: %2$s",
"%1$d monsters match: %2$s",
nmatch ), nmatch, temp_rules[row_in].rule.c_str() );

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Let mvprintz do the formatting.

This comment has been minimized.

Copy link
@OzoneH3

OzoneH3 Aug 22, 2016

Author Member

This is needed for utf8_width(...) to work in the next line.

void safemode::test_pattern( const int tab_in, const int row_in )
{
std::vector<std::string> creature_list;
std::string creature_name = "";

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

I'm wondering why people write this explicit initialization for strings, but use the default constructor for the vector (above). The = "" is entirely redundant here.

auto &temp_rules_from = ( tab == GLOBAL_TAB ) ? global_rules : character_rules;
auto &temp_rules_to = ( tab == GLOBAL_TAB ) ? character_rules : global_rules;

temp_rules_to.push_back( rules_class(

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Looks like you're copying the rules_class object. Why not use the copy constructor for that?

temp_rules_to.push_back(temp_rules_from[...]);
int ci_find_substr( const charT &str1, const charT &str2, const std::locale &loc = std::locale() );
// templated version of ci_equal so it could work with both char and wchar_t
template<typename charT>
struct ci_equal {

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

We really don't need the generic templated version of that function. charT will never be anything but char. Same with the function above.

In fact, this will not currently even work with anything else but char. Try to call ci_find_substr<int>(...) (with suitable dummy arguments) from a source file other than output.cpp. The linker will complain that can not find the function ci_find_substr<int>: only the function ci_find_substr<char> is (implicitly) instantiated in output.cpp (as it's called from another functions).

}
} else { //inbetween: vPat[i]
if (*it != "") {
if ((iPos = (int)ci_find_substr(sText, *it)) == -1) {

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

ci_find_substr returns an int - why is there a cast to it?

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 12, 2016

Contributor

Also in the same line: assignment in if is bad practice and saves only one line.

}

for (auto it = vPattern.begin(); it != vPattern.end(); ++it) {
if (it == vPattern.begin() && *it != "") { //beginning: ^vPat[i]

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

In the comment: what's vPat[i]? Makes no sense.


//case insenitive search

/* Possible patterns

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

This kind of comment belongs into the function documentation in the header. None of those functions are currently documented at all.


std::string whitelist = "";
if ( !get_safemode().empty() ) {
whitelist = string_format( " or %s to whitelist the monster", press_x( ACTION_WHITELIST_ENEMY ).c_str() );

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Missing translation macro.

}
};

if( ( int ) att < 0 || ( int ) att > 4 ) {

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Use >= strings.size() and either strings.back() or strings[strings.size() - 1] instead of the magic number.

case ACTION_WHITELIST_ENEMY:
if ( safe_mode == SAFE_MODE_STOP && !get_safemode().empty() ) {
get_safemode().add_rule( get_safemode().whitelist, Creature::A_ANY, 0, RULE_WHITELISTED );
add_msg( m_info, string_format( _( "Creature whitelisted: %s" ), get_safemode().whitelist.c_str() ).c_str() );

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Let add_msg do the formatting.

if (rl_dist( u.pos(), p->pos() ) <= iProxyDist) {
//Safemode NPC check

const int mondist = rl_dist( u.pos(), p->pos() );

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Looks like a repetition of the code above for the monster (only obvious difference is "human" instead of critter.name()), even the variable mondist (should be npcdist, shouldn't it?) - this figuratively screams to be put into a function/lambda.

sText = npc_attitude_name( p->attitude );
color = p->symbol_color();
std::string sSafemode = _("<A>dd do safemode Blacklist");
const std::string monName = (bTypeNPC) ? "human" : m->name();

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

A small helper function (probably in the safemode ui class) that translates a const Creature & into this string (name() for monsters and "human" for NPCs) would make this a bit simpler and safer.

return false;
}
} else { //inbetween: vPat[i]
if (*it != "") {

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Aug 12, 2016

Contributor

!it->empty()

}
} else if( action == "QUIT" ) {
break;
} else if( tab == CHARACTER_TAB && g->u.name.empty() ) {

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Now I'm wondering why this tests for empty(), while other code in this PR uses == "". Have the same behavior, but using empty() is generic and states the intend clearer.

Anyway, this is inconsistent. (Through the problem is not just here.)

This comment has been minimized.

Copy link
@OzoneH3

OzoneH3 Aug 22, 2016

Author Member

Fixed all of those.

}

auto &current_tab = ( tab == GLOBAL_TAB ) ? global_rules : character_rules;
const bool current_tab_non_empty = !current_tab.empty();

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

What's the advantages of using this variable instead of !current_tab.empty() directly? The later is always up to date, while this variable can easily be wrong:

const bool is_non_empty = !foo.empty();
...
foo.clear();
...
if(is_non_empty) {
    foo[0]; // boom
}
for( int i = start_pos; i < ( int )current_tab.size(); i++ ) {
if( i >= start_pos &&
i < start_pos + ( ( content_height > ( int )current_tab.size() ) ?
( int )current_tab.size() : content_height ) ) {

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Looks like another place for using std::min.

attitude = Creature::A_ANY;
break;
case Creature::A_ANY:
default:

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

The default case should be avoided when switching over an enumeration value. That allows the compiler to warn you when an enumeration value has not been handled (e.g. when somebody added a new value to the enumeration without updating the switch). The default case suppresses those warnings.

} else {
//Let the options class handle the validity of the new value
auto temp_option = get_options().get_option( "SAFEMODEPROXIMITY" );
temp_option.setValue( text.c_str() );

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

setValue accepts a std::string, no need to call c_str() here.


wprintz( w_test_rule_content, c_yellow, ( line == i ) ? ">> " : " " );

wprintz( w_test_rule_content, ( line == i ) ? hilite( line_color ) : line_color,

This comment has been minimized.

Copy link
@BevapDin

BevapDin Aug 12, 2016

Contributor

Should be "%s", creature_list[i].c_str()); - just in case the monster name contains a '%'.

@OzoneH3

This comment has been minimized.

Copy link
Member Author

commented Aug 12, 2016

No time to fix this in the forseeable future.

@OzoneH3 OzoneH3 closed this Aug 12, 2016

@OzoneH3 OzoneH3 reopened this Aug 22, 2016

OzoneH3 added some commits Aug 22, 2016

@Rivet-the-Zombie Rivet-the-Zombie merged commit 47575b0 into CleverRaven:master Aug 27, 2016

1 check passed

default This has been rescheduled for testing as the 'master' branch has been updated.

@OzoneH3 OzoneH3 deleted the OzoneH3:safemode_ui branch Sep 6, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.