Skip to content

Commit

Permalink
Merge pull request #76804 from ShnitzelX2/dialog-hidden-conditions
Browse files Browse the repository at this point in the history
Dialogue: JSON fields for displaying hidden responses
  • Loading branch information
Maleclypse authored Oct 5, 2024
2 parents 34aab02 + 61701a0 commit 6815c0c
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 14 deletions.
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

0 comments on commit 6815c0c

Please sign in to comment.