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 19, 2019
1 parent f59ceae commit 7f24d9b
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 114 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
]
}
}



```
2 changes: 2 additions & 0 deletions 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
43 changes: 25 additions & 18 deletions src/npctalk.cpp
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 7f24d9b

Please sign in to comment.