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

Dialogue: JSON fields for displaying hidden responses #76804

Merged
merged 2 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions data/json/npcs/TALK_TEST.json
Original file line number Diff line number Diff line change
Expand Up @@ -1346,5 +1346,78 @@
"id": [ "TALK_TEST_GUARD" ],
"type": "talk_topic",
"responses": [ { "text": "I have a custom response to a common topic", "topic": "TALK_TEST_FACTION_TRUST" } ]
},
{
"type": "talk_topic",
"id": "TALK_TEST_SHOW_ALWAYS",
"dynamic_line": "This is a test conversation that shouldn't appear in the game.",
"responses": [
{
"condition": { "math": [ "0", ">", "1" ] },
"text": "You should see this response but not be able to select it (except in debug mode).",
"show_always": true,
"topic": "TALK_DONE"
},
{ "text": "All done", "topic": "TALK_DONE" }
]
},
{
"type": "talk_topic",
"id": "TALK_TEST_SHOW_ALWAYS3",
"dynamic_line": "This is a test conversation that shouldn't appear in the game.",
"responses": [
{
"condition": { "math": [ "1", ">", "0" ] },
"text": "You should always see this response and be able to select it.",
"show_always": true,
"topic": "TALK_DONE"
},
{ "text": "All done", "topic": "TALK_DONE" }
]
},
{
"type": "talk_topic",
"id": "TALK_TEST_SHOW_ALWAYS2",
"dynamic_line": "This is a test conversation that shouldn't appear in the game.",
"responses": [
{
"condition": { "math": [ "0", ">", "1" ] },
"text": "You should see this response but not be able to select it (except in debug mode) because",
"show_always": true,
"show_reason": "zero isn't greater than one.",
"topic": "TALK_DONE"
},
{ "text": "All done", "topic": "TALK_DONE" }
]
},
{
"type": "talk_topic",
"id": "TALK_TEST_SHOW_CONDITION",
"dynamic_line": "This is a test conversation that shouldn't appear in the game.",
"responses": [
{
"condition": { "math": [ "0", ">", "1" ] },
"text": "You should see this response but not be able to select it (except in debug mode) because",
"topic": "TALK_DONE",
"show_condition": { "math": [ "1", ">", "0" ] },
"show_reason": "zero isn't greater than one."
},
{ "text": "All done", "topic": "TALK_DONE" }
]
},
{
"type": "talk_topic",
"id": "TALK_TEST_SHOW_CONDITION2",
"dynamic_line": "This is a test conversation that shouldn't appear in the game.",
"responses": [
{
"condition": { "math": [ "0", ">", "1" ] },
"text": "You should not see this response.",
"topic": "TALK_DONE",
"show_condition": { "math": [ "0", ">", "1" ] },
"show_reason": "zero isn't greater than one."
},
{ "text": "All done", "topic": "TALK_DONE" }
]
}
]
9 changes: 9 additions & 0 deletions data/raw/keybindings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2897,6 +2897,15 @@
{ "input_method": "keyboard_code", "mod": [ "ctrl" ], "key": "y" }
]
},
{
"type": "keybinding",
"id": "DEBUG_DIALOGUE_SHOW_ALL_RESPONSE",
"name": "Toggle debug dialogue show hidden responses ",
"bindings": [
{ "input_method": "keyboard_char", "key": "CTRL+R" },
{ "input_method": "keyboard_code", "mod": [ "ctrl" ], "key": "r" }
]
},
{
"type": "keybinding",
"id": "RESET",
Expand Down
13 changes: 11 additions & 2 deletions doc/NPCs.md
Original file line number Diff line number Diff line change
Expand Up @@ -769,12 +769,21 @@ Similar to `opinion`, but adjusts the NPC's opinion of your character according

### Response Availability

#### condition
#### `condition`
This is an optional condition which can be used to prevent the response under certain circumstances. If not defined, it defaults to always `true`. If the condition is not met, the response is not included in the list of possible responses. For possible content, [see Dialogue Conditions below](#dialogue-conditions) for details.

#### switch and default
#### `switch and default`
The optional boolean keys "switch" and "default" are false by default. Only the first response with `"switch": true`, `"default": false`, and a valid condition will be displayed, and no other responses with `"switch": true` will be displayed. If no responses with `"switch": true` and `"default": false` are displayed, then any and all responses with `"switch": true` and `"default": true` will be displayed. In either case, all responses that have `"switch": false` (whether or not they have `"default": true` is set) will be displayed as long their conditions are satisfied.

#### `show_condition`
An optional key that, if defined and evaluates to true, will allow the response to be displayed even if it has a condition evaluating false. The response still will not be selectable if its condition is false, unless debug mode is ON. Cannot be defined if `show_always: true`. Note: do not confuse `show_condition` with `condition`. Empty by default.

#### `show_always`
Shorthand for "show_condition": takes a boolean instead of a condition. False by default.

#### `show_reason`
An optional key that, if defined, will append its contents to the end of a response displayed because of `show_always` or `show_condition`. Empty by default.

Example:
```json
"responses": [
Expand Down
16 changes: 14 additions & 2 deletions src/dialogue.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,16 @@ struct talk_response {
//copy of json_talk_response::condition, optional
std::function<bool( dialogue & )> condition;

//whether to display this response in normal gameplay even if condition is false
bool show_always = false;
//appended to response if condition fails or show_always/show_condition
std::string show_reason;
//show_always, but on show_condition being true
std::function<bool( dialogue & )> show_condition;

//flag to hold result of show_anyways (not read from JSON)
bool ignore_conditionals = false;

mission *mission_selected = nullptr;
skill_id skill = skill_id();
matype_id style = matype_id();
Expand Down Expand Up @@ -256,6 +266,7 @@ struct dialogue {

bool debug_conditionals = true;
bool debug_effects = true;
bool debug_ignore_conditionals = false;

// Methods for setting/getting misc key/value pairs.
void set_value( const std::string &key, const std::string &value );
Expand Down Expand Up @@ -421,10 +432,11 @@ class json_talk_response
return has_condition_;
}
bool test_condition( dialogue &d ) const;
bool show_anyways( dialogue &d ) const;
/**
* Callback from @ref json_talk_topic::gen_responses, see there.
*/
bool gen_responses( dialogue &d, bool switch_done ) const;
bool gen_responses( dialogue &d, bool switch_done );
bool gen_repeat_response( dialogue &d, const itype_id &item_id, bool switch_done ) const;
};

Expand Down Expand Up @@ -490,7 +502,7 @@ class json_talk_topic
* @return true if built in response should excluded (not added). If false, built in
* responses will be added (behind those added here).
*/
bool gen_responses( dialogue &d ) const;
bool gen_responses( dialogue &d );

cata::flat_set<std::string> get_directly_reachable_topics( bool only_unconditional ) const;

Expand Down
6 changes: 3 additions & 3 deletions src/dialogue_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ void dialogue_window::print_header( const std::string &name ) const
int x_debug = 15 + utf8_width( name );
const int ymax = getmaxy( d_win );
const int ybar = ymax - 1 - RESPONSES_LINES - 1;
const int flag_count = 4;
std::array<bool, flag_count> debug_flags = { show_dynamic_line_conditionals, show_response_conditionals, show_dynamic_line_effects, show_response_effects };
std::array<std::string, flag_count> debug_show_toggle = { "DL_COND", "RESP_COND", "DL_EFF", "RESP_EFF" };
const int flag_count = 5;
std::array<bool, flag_count> debug_flags = { show_dynamic_line_conditionals, show_response_conditionals, show_dynamic_line_effects, show_response_effects, show_all_responses };
std::array<std::string, flag_count> debug_show_toggle = { "DL_COND", "RESP_COND", "DL_EFF", "RESP_EFF", "ALL_RESP" };
if( debug_mode ) {
for( int i = 0; i < flag_count; i++ ) {
mvwprintz( d_win, point( x_debug, 1 ), debug_flags[i] ? c_yellow : c_brown, debug_show_toggle[i] );
Expand Down
2 changes: 2 additions & 0 deletions src/dialogue_win.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class dialogue_window
bool show_dynamic_line_effects = true;
bool show_response_conditionals = true;
bool show_response_effects = true;
//copy of dialogue::show_all_responses
bool show_all_responses = false;
int sel_response = 0;
std::string debug_topic_name;
private:
Expand Down
54 changes: 47 additions & 7 deletions src/npctalk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2496,6 +2496,14 @@ talk_data talk_response::create_option_line( dialogue &d, const input_event &hot
ftext = string_format( pgettext( "talk option", "[%1$s %2$d%%] %3$s" ),
trial.name(), trial.calc_chance( d ), text );
}

if( ignore_conditionals ) {
ftext = colorize( ftext, c_dark_gray );
if( !show_reason.empty() ) {
ftext += colorize( " -- [" + show_reason + "]", c_light_red );
}
}

if( d.actor( true )->get_npc() ) {
parse_tags( ftext, *d.actor( false )->get_character(), *d.actor( true )->get_npc(), d,
success.next_topic.item_type );
Expand Down Expand Up @@ -2535,6 +2543,12 @@ std::set<dialogue_consequence> talk_response::get_consequences( dialogue &d ) co
return {{ success.get_consequence( d ), failure.get_consequence( d ) }};
}

bool json_talk_response::show_anyways( dialogue &d ) const
{
return actual_response.show_always || ( actual_response.show_condition &&
actual_response.show_condition( d ) );
}

dialogue_consequence talk_effect_t::get_consequence( dialogue const &d ) const
{
if( d.actor( true )->check_hostile_response( opinion.anger ) ) {
Expand Down Expand Up @@ -2629,6 +2643,7 @@ talk_topic dialogue::opt( dialogue_window &d_win, const talk_topic &topic )
ctxt.register_action( "DEBUG_DIALOGUE_RESP_CONDITIONAL" );
ctxt.register_action( "DEBUG_DIALOGUE_DL_EFFECT" );
ctxt.register_action( "DEBUG_DIALOGUE_RESP_EFFECT" );
ctxt.register_action( "DEBUG_DIALOGUE_SHOW_ALL_RESPONSE" );
ctxt.register_action( "QUIT" );
std::vector<talk_data> response_lines;
std::vector<input_event> response_hotkeys;
Expand Down Expand Up @@ -2680,6 +2695,11 @@ talk_topic dialogue::opt( dialogue_window &d_win, const talk_topic &topic )
generate_response_lines();
} else if( action == "CONFIRM" ) {
response_ind = d_win.sel_response;
//response condition must be reverified since non-selectable responses can be displayed
talk_response &check_valid = responses[response_ind];
if( check_valid.condition && ( !check_valid.condition( *this ) && !debug_mode ) ) {
action = "NONE";
}
} else if( action == "DEBUG_DIALOGUE_DL_CONDITIONAL" ) {
d_win.show_dynamic_line_conditionals = !d_win.show_dynamic_line_conditionals;
} else if( action == "DEBUG_DIALOGUE_RESP_CONDITIONAL" ) {
Expand All @@ -2688,6 +2708,13 @@ talk_topic dialogue::opt( dialogue_window &d_win, const talk_topic &topic )
d_win.show_dynamic_line_effects = !d_win.show_dynamic_line_effects;
} else if( action == "DEBUG_DIALOGUE_RESP_EFFECT" ) {
d_win.show_response_effects = !d_win.show_response_effects;
} else if( action == "DEBUG_DIALOGUE_SHOW_ALL_RESPONSE" ) {
d_win.show_all_responses = !d_win.show_all_responses;
if( debug_mode ) {
this->debug_ignore_conditionals = !this->debug_ignore_conditionals;
gen_responses( topic );
generate_response_lines();
}
} else if( action == "ANY_INPUT" ) {
// Check real hotkeys
const auto hotkey_it = std::find( response_hotkeys.begin(),
Expand Down Expand Up @@ -2815,7 +2842,7 @@ std::vector<std::string> dialogue::build_debug_info( const dialogue_window &d_wi
talk_response &actual_response = responses[do_response];
std::map<std::string, std::string> &debug_info = actual_response.debug_info;
if( d_win.show_response_conditionals ) {
if( debug_info.find( "condition" ) != debug_info.end() && actual_response.condition ) {
if( actual_response.condition && debug_info.find( "condition" ) != debug_info.end() ) {
debug_output.emplace_back( std::string( "Conditional: [" ) + ( actual_response.condition(
*this ) ? colorize( "true", c_light_green ) : colorize( "false",
c_light_red ) ) + std::string( "] - " ) + debug_info["condition"] );
Expand Down Expand Up @@ -7229,7 +7256,18 @@ talk_response::talk_response( const JsonObject &jo, const std::string_view src )
JsonObject failure_obj = jo.get_object( "failure" );
failure = talk_effect_t( failure_obj, "effect", src );
}

if( jo.has_member( "show_always" ) ) {
show_always = jo.get_bool( "show_always" );
}
if( jo.has_member( "show_reason" ) ) {
show_reason = jo.get_string( "show_reason" );
}
if( jo.has_member( "show_condition" ) ) {
if( show_always ) {
jo.throw_error( "show_always and show_condition cannot both be defined" );
}
read_condition( jo, "show_condition", show_condition, false );
}
// TODO: mission_selected
// TODO: skill
// TODO: style
Expand Down Expand Up @@ -7302,10 +7340,12 @@ const talk_response &json_talk_response::get_actual_response() const
return actual_response;
}

bool json_talk_response::gen_responses( dialogue &d, bool switch_done ) const
bool json_talk_response::gen_responses( dialogue &d, bool switch_done )
{
if( !is_switch || !switch_done ) {
if( test_condition( d ) ) {
if( !is_switch || !switch_done || d.debug_ignore_conditionals ) {
if( test_condition( d ) || show_anyways( d ) || d.debug_ignore_conditionals ) {
actual_response.ignore_conditionals = !test_condition( d ) && ( show_anyways( d ) ||
d.debug_ignore_conditionals );
d.responses.emplace_back( actual_response );
return is_switch && !is_default;
} else if( !failure_explanation.empty() || !failure_topic.empty() ) {
Expand Down Expand Up @@ -7623,12 +7663,12 @@ void json_talk_topic::load( const JsonObject &jo, const std::string_view src )
replace_built_in_responses );
}

bool json_talk_topic::gen_responses( dialogue &d ) const
bool json_talk_topic::gen_responses( dialogue &d )
{
d.responses.reserve( responses.size() ); // A wild guess, can actually be more or less

bool switch_done = false;
for( const json_talk_response &r : responses ) {
for( json_talk_response &r : responses ) {
switch_done |= r.gen_responses( d, switch_done );
}
for( const json_talk_repeat_response &repeat : repeat_responses ) {
Expand Down
Loading