Skip to content

Commit

Permalink
npctalk: add support for generic CONDITION trials
Browse files Browse the repository at this point in the history
Add a new type of trial, "CONDITION" which always succeeds or fails based
on the value of a condition object.
  • Loading branch information
mlangsdorf committed Jan 20, 2019
1 parent 0fcdef4 commit e9566ac
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 116 deletions.
22 changes: 22 additions & 0 deletions data/json/npcs/TALK_TEST.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,28 @@
}
]
},
{
"type": "talk_topic",
"id": "TALK_TEST_TRUE_FALSE_CONDITIONAL",
"dynamic_line": "This is a test conversation that shouldn't appear in the game.",
"responses": [
{ "text": "This is a basic test response.", "topic": "TALK_DONE" },
{
"truefalsetext": {
"condition": { "u_has_cash": 500 },
"true": "This is a true/false true response.",
"false": "This is a true/false false response."
},
"topic": "TALK_DONE"
},
{
"text": "This is a conditional trial response.",
"trial": { "type": "CONDITION", "condition": { "u_has_cash": 500 } },
"success": { "topic": "TALK_TEST_TRUE_CONDITION_NEXT" },
"failure": { "topic": "TALK_TEST_FALSE_CONDITION_NEXT" }
}
]
},
{
"type": "talk_topic",
"id": "TALK_TEST_EFFECTS",
Expand Down
13 changes: 7 additions & 6 deletions doc/NPCs.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,10 +396,13 @@ The player will have one response text if a condition is true, and another if it
Will be shown to the user, no further meaning.

### trial
Optional, if not defined, "NONE" is used. Otherwise one of "NONE", "LIE", "PERSUADE" or "INTIMIDATE". If "NONE" is used, the `failure` object is not read, otherwise it's mandatory.
The `difficulty` is only required if type is not "NONE" and specifies the success chance in percent (it is however modified by various things like mutations).
Optional, if not defined, "NONE" is used. Otherwise one of "NONE", "LIE", "PERSUADE" "INTIMIDATE", or "CONDITION". If "NONE" is used, the `failure` object is not read, otherwise it's mandatory.

An optional `mod` array takes any of the following modifiers and increases the difficulty by the NPC's opinion of your character or personality trait for that modifier multiplied by the value: "ANGER", "FEAR", "TRUST", "VALUE", "AGRESSION", "ALTRUISM", "BRAVERY", "COLLECTOR". The special "POS_FEAR" modifier treats NPC's fear of your character below 0 as though it were 0.
The `difficulty` is only required if type is not "NONE" or "CONDITION" and specifies the success chance in percent (it is however modified by various things like mutations). Higher difficulties are easier to pass.

An optional `mod` array takes any of the following modifiers and increases the difficulty by the NPC's opinion of your character or personality trait for that modifier multiplied by the value: "ANGER", "FEAR", "TRUST", "VALUE", "AGRESSION", "ALTRUISM", "BRAVERY", "COLLECTOR". The special "POS_FEAR" modifier treats NPC's fear of your character below 0 as though it were 0. The special "TOTAL" modifier sums all previous modifiers and then multiplies the result by its value and is used when setting the owed value.

"CONDITION" trials take a mandatory `condition` instead of `difficulty`. The `success` object is chosen if the `condition` is true and the `failure` is chosen otherwise.

### success and failure
Both objects have the same structure. `topic` defines which topic the dialogue will switch to. `opinion` is optional, if given it defines how the opinion of the NPC will change. The given values are *added* to the opinion of the NPC, they are all optional and default to 0. `effect` is a function that is executed after choosing the response, see below.
Expand All @@ -418,6 +421,7 @@ The `failure` object is used if the trial fails, the `success` object is used ot
### Sample trials
"trial": { "type": "PERSUADE", "difficulty": 0, "mod": [ [ "TRUST", 3 ], [ "VALUE", 3 ], [ "ANGER", -3 ] ] }
"trial": { "type": "INTIMIDATE", "difficulty": 20, "mod": [ [ "FEAR", 8 ], [ "VALUE", 2 ], [ "TRUST", 2 ], [ "BRAVERY", -2 ] ] }
"trial": { "type": "CONDITION", "condition": { "npc_has_trait": "FARMER" } }

`topic` can also be a single topic object (the `type` member is not required here):
```C++
Expand Down Expand Up @@ -665,7 +669,4 @@ Condition | Type | Description
]
}
}



```
4 changes: 3 additions & 1 deletion src/dialogue.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum talk_trial_type : unsigned char {
TALK_TRIAL_LIE, // Straight up lying
TALK_TRIAL_PERSUADE, // Convince them
TALK_TRIAL_INTIMIDATE, // Physical intimidation
TALK_TRIAL_CONDITION, // Some other condition
NUM_TALK_TRIALS
};

Expand All @@ -52,6 +53,7 @@ using trial_mod = std::pair<std::string, int>;
struct talk_trial {
talk_trial_type type = TALK_TRIAL_NONE;
int difficulty = 0;
std::function<bool( const dialogue & )> condition;

int calc_chance( const dialogue &d ) const;
/**
Expand Down Expand Up @@ -184,7 +186,7 @@ struct talk_response {
talk_data create_option_line( const dialogue &d, char letter );
std::set<dialogue_consequence> get_consequences( const dialogue &d ) const;

talk_response() = default;
talk_response();
talk_response( JsonObject );
};

Expand Down
55 changes: 36 additions & 19 deletions src/npctalk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ static std::map<std::string, json_talk_topic> json_talk_topics;

// Some aliases to help with gen_responses
#define RESPONSE(txt) ret.push_back(talk_response());\
ret.back().text = txt
ret.back().truetext = txt

#define TRIAL(tr, diff) ret.back().trial.type = tr;\
ret.back().trial.difficulty = diff
Expand Down Expand Up @@ -108,7 +108,7 @@ void bulk_trade_accept( npc &, const itype_id &it );
const std::string &talk_trial::name() const
{
static const std::array<std::string, NUM_TALK_TRIALS> texts = { {
"", _( "LIE" ), _( "PERSUADE" ), _( "INTIMIDATE" )
"", _( "LIE" ), _( "PERSUADE" ), _( "INTIMIDATE" ), ""
}
};
if( static_cast<size_t>( type ) >= texts.size() ) {
Expand Down Expand Up @@ -1342,7 +1342,6 @@ int talk_trial::calc_chance( const dialogue &d ) const
npc &p = *d.beta;
int chance = difficulty;
switch( type ) {
case TALK_TRIAL_NONE:
case NUM_TALK_TRIALS:
dbg( D_ERROR ) << "called calc_chance with invalid talk_trial value: " << type;
break;
Expand Down Expand Up @@ -1391,6 +1390,12 @@ int talk_trial::calc_chance( const dialogue &d ) const
chance += 20;
}
break;
case TALK_TRIAL_NONE:
chance = 100;
break;
case TALK_TRIAL_CONDITION:
chance = condition( d ) ? 100 : 0;
break;
}
for( auto this_mod: modifiers ) {
chance += parse_mod( d, this_mod.first, this_mod.second );
Expand Down Expand Up @@ -1681,18 +1686,16 @@ talk_data talk_response::create_option_line( const dialogue &d, const char lette
{
std::string ftext;
text = truefalse_condition( d ) ? truetext : falsetext;
if( trial != TALK_TRIAL_NONE ) { // dialogue w/ a % chance to work
ftext = string_format( pgettext( "talk option", "%1$c: [%2$s %3$d%%] %4$s" ),
letter, // option letter
trial.name(), // trial type
trial.calc_chance( d ), // trial % chance
text // response
);
} else { // regular dialogue
ftext = string_format( pgettext( "talk option", "%1$c: %2$s" ),
letter, // option letter
text // response
);
// dialogue w/ a % chance to work
if( trial.type == TALK_TRIAL_NONE || trial.type == TALK_TRIAL_CONDITION ) {
// regular dialogue
//~ %1$c is an option letter and shouldn't be translated, %2$s is translated response text
ftext = string_format( pgettext( "talk option", "%1$c: %2$s" ), letter, text );
} else {
// dialogue w/ a % chance to work
//~ %1$c is an option letter and shouldn't be translated, %2$s is translated trial type, %3$d is a number, and %4$s is the translated response text
ftext = string_format( pgettext( "talk option", "%1$c: [%2$s %3$d%%] %4$s" ), letter,
trial.name(), trial.calc_chance( d ), text );
}
parse_tags( ftext, *d.alpha, *d.beta );

Expand Down Expand Up @@ -1854,7 +1857,8 @@ talk_trial::talk_trial( JsonObject jo )
WRAP( NONE ),
WRAP( LIE ),
WRAP( PERSUADE ),
WRAP( INTIMIDATE )
WRAP( INTIMIDATE ),
WRAP( CONDITION )
#undef WRAP
}
};
Expand All @@ -1863,9 +1867,12 @@ talk_trial::talk_trial( JsonObject jo )
jo.throw_error( "invalid talk trial type", "type" );
}
type = iter->second;
if( type != TALK_TRIAL_NONE ) {
if( !( type == TALK_TRIAL_NONE || type == TALK_TRIAL_CONDITION ) ) {
difficulty = jo.get_int( "difficulty" );
}

read_dialogue_condition( jo, condition, false );

if( jo.has_array( "mod" ) ) {
JsonArray ja = jo.get_array( "mod" );
while( ja.has_more() ) {
Expand Down Expand Up @@ -2125,7 +2132,7 @@ void talk_effect_t::parse_sub_effect( JsonObject jo )
const std::string dur_string = jo.get_string( "duration" );
if( dur_string == "PERMANENT" ) {
subeffect_fun.set_u_add_permanent_effect( new_effect );
} else {
} else if( !dur_string.empty() && std::stoi( dur_string ) > 0 ) {
int duration = std::stoi( dur_string );
subeffect_fun.set_u_add_effect( new_effect, time_duration::from_turns( duration ) );
}
Expand All @@ -2139,7 +2146,7 @@ void talk_effect_t::parse_sub_effect( JsonObject jo )
const std::string dur_string = jo.get_string( "duration" );
if( dur_string == "PERMANENT" ) {
subeffect_fun.set_npc_add_permanent_effect( new_effect );
} else {
} else if( !dur_string.empty() && std::stoi( dur_string ) > 0 ) {
int duration = std::stoi( dur_string );
subeffect_fun.set_npc_add_effect( new_effect,
time_duration::from_turns( duration ) );
Expand Down Expand Up @@ -2286,6 +2293,16 @@ void talk_effect_t::load_effect( JsonObject &jo )
}
}

talk_response::talk_response()
{
truefalse_condition = []( const dialogue & ) {
return true;
};
mission_selected = nullptr;
skill = skill_id::NULL_ID();
style = matype_id::NULL_ID();
}

talk_response::talk_response( JsonObject jo )
{
if( jo.has_member( "trial" ) ) {
Expand Down
Loading

0 comments on commit e9566ac

Please sign in to comment.