Skip to content

Commit

Permalink
math_parser: evaluate variables in diag. functions (#65467)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrei8l authored May 19, 2023
1 parent 7451321 commit aa3b581
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 70 deletions.
8 changes: 8 additions & 0 deletions data/mods/TEST_DATA/EOC.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@
"global": true,
"effect": { "u_message": "test recurrence" }
},
{
"type": "effect_on_condition",
"id": "EOC_math_diag_w_vars",
"effect": [
{ "set_string_var": "survival", "target_var": { "global_val": "myskill" } },
{ "math": [ "myskill_math", "=", "u_skill(myskill)" ] }
]
},
{
"type": "effect_on_condition",
"id": "EOC_jmath_test",
Expand Down
12 changes: 7 additions & 5 deletions doc/NPCs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1429,13 +1429,15 @@ Dialogue functions take single-quoted strings as arguments and can return or man

This section is a work in progress as functions are ported from `arithmetic` to `math`.

_function arguments are either `s`trings or `v`[ariables](#variables)_

| Function | Eval | Assign |Scopes | Description |
|----------|------|--------|-------|-------------|
| armor ||| u, n | Return the numerical value for a characters armor on a body part, for a damage type.<br/> Example:<br/>`"condition": { "math": [ "u_armor('bash', 'torso')", ">=", "5"] }`|
| game_option ||| N/A<br/>(global) | Return the numerical value of a game option<br/> Example:<br/>`"condition": { "math": [ "game_option('NPC_SPAWNTIME')", ">=", "5"] }`|
| pain ||| u, n | Return or set pain<br/> Example:<br/>`{ "math": [ "n_pain()", "=", "u_pain() + 9000" ] }`|
| skill ||| u, n | Return or set skill level<br/> Example:<br/>`"condition": { "math": [ "u_skill('driving')", ">=", "5"] }`|
| weather ||| N/A<br/>(global) | Return or set a weather aspect<br/><br/>Aspect must be one of:<br/>`temperature` (in Kelvin),<br/>`humidity` (as percentage),<br/>`pressure` (in millibar),<br/>`windpower` (in mph).<br/><br/>Temperature conversion functions are available: `celsius()`, `fahrenheit()`, `from_celsius()`, and `from_fahrenheit()`.<br/><br/>Examples:<br/>`{ "math": [ "weather('temperature')", "<", "from_fahrenheit( 33 )" ] }`<br/>`{ "math": [ "fahrenheit( weather('temperature') )", "==", "21" ] }`|
| armor(`s`/`v`,`s`/`v`) ||| u, n | Return the numerical value for a characters armor on a body part, for a damage type.<br/> Example:<br/>`"condition": { "math": [ "u_armor('bash', 'torso')", ">=", "5"] }`<br/>`"condition": { "math": [ "u_armor(u_dmgtype, u_bp)", ">=", "5"] }`|
| game_option(`s`) ||| N/A<br/>(global) | Return the numerical value of a game option<br/> Example:<br/>`"condition": { "math": [ "game_option('NPC_SPAWNTIME')", ">=", "5"] }`|
| pain() ||| u, n | Return or set pain<br/> Example:<br/>`{ "math": [ "n_pain()", "=", "u_pain() + 9000" ] }`|
| skill(`s`/`v`) ||| u, n | Return or set skill level<br/> Example:<br/>`"condition": { "math": [ "u_skill('driving')", ">=", "5"] }`<br/>`"condition": { "math": [ "u_skill(someskill)", ">=", "5"] }`|
| weather(`s`) ||| N/A<br/>(global) | Return or set a weather aspect<br/><br/>Aspect must be one of:<br/>`temperature` (in Kelvin),<br/>`humidity` (as percentage),<br/>`pressure` (in millibar),<br/>`windpower` (in mph).<br/><br/>Temperature conversion functions are available: `celsius()`, `fahrenheit()`, `from_celsius()`, and `from_fahrenheit()`.<br/><br/>Examples:<br/>`{ "math": [ "weather('temperature')", "<", "from_fahrenheit( 33 )" ] }`<br/>`{ "math": [ "fahrenheit( weather('temperature') )", "==", "21" ] }`|

##### u_val shim
There is a `val()` shim available that can cover the missing arithmetic functions from `u_val` and `npc_val`:
Expand Down
8 changes: 8 additions & 0 deletions src/cata_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,4 +667,12 @@ T aggregate( const std::vector<T> &values, aggregate_type agg_func )
}
}

// overload pattern for std::variant from https://en.cppreference.com/w/cpp/utility/variant/visit
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
explicit overloaded( Ts... ) -> overloaded<Ts...>;

#endif // CATA_SRC_CATA_UTILITY_H
44 changes: 27 additions & 17 deletions src/math_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ class math_exp::math_exp_impl
void maybe_first_argument();
void error( std::string_view str, std::string_view what );
void validate_string( std::string_view str, std::string_view label, std::string_view badlist );
std::vector<std::string> _get_strings( std::vector<thingie> const &params,
size_t nparams ) const;
std::vector<diag_value> _get_diag_vals( std::vector<thingie> &params,
size_t nparams ) const;
};

void math_exp::math_exp_impl::maybe_first_argument()
Expand Down Expand Up @@ -474,12 +474,12 @@ void math_exp::math_exp_impl::new_func()
std::visit( overloaded{
[&params, nparams, this]( scoped_diag_eval const & v )
{
std::vector<std::string> const strings = _get_strings( params, nparams );
std::vector<diag_value> const strings = _get_diag_vals( params, nparams );
output.emplace( std::in_place_type_t<func_diag_eval>(), v.df->f( v.scope, strings ) );
},
[&params, nparams, this]( scoped_diag_ass const & v )
{
std::vector<std::string> const strings = _get_strings( params, nparams );
std::vector<diag_value> const strings = _get_diag_vals( params, nparams );
output.emplace( std::in_place_type_t<func_diag_ass>(), v.df->f( v.scope, strings ) );
},
[&params, this]( pmath_func v )
Expand All @@ -500,20 +500,30 @@ void math_exp::math_exp_impl::new_func()
}
}

std::vector<std::string> math_exp::math_exp_impl::_get_strings( std::vector<thingie> const
&params, size_t nparams ) const
std::vector<diag_value> math_exp::math_exp_impl::_get_diag_vals( std::vector<thingie> &params,
size_t nparams ) const
{
std::vector<std::string> strings( nparams );
std::transform( params.begin(), params.end(), strings.begin(), [this]( thingie const & e ) {
if( std::holds_alternative<std::string>( e.data ) ) {
return std::get<std::string>( e.data );
}
throw std::invalid_argument( string_format(
"Parameters for %s() must be strings contained in single quotes",
arity.top().sym.data() ) );
return std::string{};
} );
return strings;
std::vector<diag_value> vals( nparams );
for( decltype( vals )::size_type i = 0; i < params.size(); i++ ) {
std::visit( overloaded{
[&vals, i]( std::string & v )
{
vals[i].data.emplace<std::string>( std::move( v ) );
},
[&vals, i]( var & v )
{
vals[i].data.emplace<var_info>( std::move( v.varinfo ) );
},
[this]( auto const &/* v */ )
{
throw std::invalid_argument(
string_format( "Parameters for %s() must be variables or strings contained in single quotes",
arity.top().sym ) );
},
},
params[i].data );
}
return vals;
}

void math_exp::math_exp_impl::new_oper()
Expand Down
75 changes: 55 additions & 20 deletions src/math_parser_diag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
#include <string>
#include <vector>

#include "cata_utility.h"
#include "condition.h"
#include "dialogue.h"
#include "math_parser_shim.h"
#include "mission.h"
#include "options.h"
#include "units.h"
#include "weather.h"
Expand All @@ -26,8 +26,43 @@ bool is_beta( char scope )
}
} // namespace

std::string diag_value::str() const
{
return std::string{ sv() };
}

std::string_view diag_value::sv() const
{
return std::visit( overloaded{
[]( std::string const & v ) -> std::string const &
{
return v;
},
[]( var_info const & /* v */ ) -> std::string const &
{
throw std::invalid_argument( "Variables are not supported in this context" );
},
},
data );
}

std::string diag_value::eval( dialogue const &d ) const
{
return std::visit( overloaded{
[]( std::string const & v )
{
return v;
},
[&d]( var_info const & v )
{
return read_var_value( v, d );
},
},
data );
}

std::function<double( dialogue & )> u_val( char scope,
std::vector<std::string> const &params )
std::vector<diag_value> const &params )
{
kwargs_shim const shim( params, scope );
try {
Expand All @@ -41,7 +76,7 @@ std::function<double( dialogue & )> u_val( char scope,
}

std::function<void( dialogue &, double )> u_val_ass( char scope,
std::vector<std::string> const &params )
std::vector<diag_value> const &params )
{
kwargs_shim const shim( params, scope );
try {
Expand All @@ -53,57 +88,57 @@ std::function<void( dialogue &, double )> u_val_ass( char scope,
}

std::function<double( dialogue & )> option_eval( char /* scope */,
std::vector<std::string> const &params )
std::vector<diag_value> const &params )
{
return[option = params[0]]( dialogue const & ) {
return[option = params[0].str()]( dialogue const & ) {
return get_option<float>( option );
};
}

std::function<double( dialogue & )> armor_eval( char scope,
std::vector<std::string> const &params )
std::vector<diag_value> const &params )
{
return[type = params[0], bpid = params[1], beta = is_beta( scope )]( dialogue const & d ) {
damage_type_id dt( type );
bodypart_id bp( bpid );
damage_type_id dt( type.eval( d ) );
bodypart_id bp( bpid.eval( d ) );
return d.actor( beta )->armor_at( dt, bp );
};
}

std::function<double( dialogue & )> pain_eval( char scope,
std::vector<std::string> const &/* params */ )
std::vector<diag_value> const &/* params */ )
{
return [beta = is_beta( scope )]( dialogue const & d ) {
return d.actor( beta )->pain_cur();
};
}

std::function<void( dialogue &, double )> pain_ass( char scope,
std::vector<std::string> const &/* params */ )
std::vector<diag_value> const &/* params */ )
{
return [beta = is_beta( scope )]( dialogue const & d, double val ) {
d.actor( beta )->set_pain( val );
};
}

std::function<double( dialogue & )> skill_eval( char scope,
std::vector<std::string> const &params )
std::vector<diag_value> const &params )
{
return [beta = is_beta( scope ), sid = skill_id( params[0] )]( dialogue const & d ) {
return d.actor( beta )->get_skill_level( sid );
return [beta = is_beta( scope ), sid = params[0] ]( dialogue const & d ) {
return d.actor( beta )->get_skill_level( skill_id( sid.eval( d ) ) );
};
}

std::function<void( dialogue &, double )> skill_ass( char scope,
std::vector<std::string> const &params )
std::vector<diag_value> const &params )
{
return [beta = is_beta( scope ), sid = skill_id( params[0] )]( dialogue const & d, double val ) {
return d.actor( beta )->set_skill_level( sid, val );
return [beta = is_beta( scope ), sid = params[0] ]( dialogue const & d, double val ) {
return d.actor( beta )->set_skill_level( skill_id( sid.eval( d ) ), val );
};
}

std::function<double( dialogue & )> weather_eval( char /* scope */,
std::vector<std::string> const &params )
std::vector<diag_value> const &params )
{
if( params[0] == "temperature" ) {
return []( dialogue const & ) {
Expand All @@ -125,11 +160,11 @@ std::function<double( dialogue & )> weather_eval( char /* scope */,
return get_weather().weather_precise->pressure;
};
}
throw std::invalid_argument( "Unknown weather aspect " + params[0] );
throw std::invalid_argument( "Unknown weather aspect " + params[0].str() );
}

std::function<void( dialogue &, double )> weather_ass( char /* scope */,
std::vector<std::string> const &params )
std::vector<diag_value> const &params )
{
if( params[0] == "temperature" ) {
return []( dialogue const &, double val ) {
Expand All @@ -156,5 +191,5 @@ std::function<void( dialogue &, double )> weather_ass( char /* scope */,
get_weather().clear_temp_cache();
};
}
throw std::invalid_argument( "Unknown weather aspect " + params[0] );
throw std::invalid_argument( "Unknown weather aspect " + params[0].str() );
}
41 changes: 29 additions & 12 deletions src/math_parser_diag.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
#include <functional>
#include <string>
#include <string_view>
#include <variant>
#include <vector>

#include "dialogue_helpers.h"

struct dialogue;
struct dialogue_func {
dialogue_func( std::string_view s_, std::string_view sc_, int n_ ) : symbol( s_ ),
Expand All @@ -16,9 +19,23 @@ struct dialogue_func {
int num_params{};
};

struct diag_value {
std::string_view sv() const;
std::string str() const;
std::string eval( dialogue const &d ) const;

using impl_t = std::variant<std::string, var_info>;
impl_t data;
};

constexpr bool operator==( diag_value const &lhs, std::string_view rhs )
{
return std::holds_alternative<std::string>( lhs.data ) && std::get<std::string>( lhs.data ) == rhs;
}

struct dialogue_func_eval : dialogue_func {
using f_t = std::function<double( dialogue & )> ( * )( char scope,
std::vector<std::string> const & );
std::vector<diag_value> const & );

dialogue_func_eval( std::string_view s_, std::string_view sc_, int n_, f_t f_ )
: dialogue_func( s_, sc_, n_ ), f( f_ ) {}
Expand All @@ -28,7 +45,7 @@ struct dialogue_func_eval : dialogue_func {

struct dialogue_func_ass : dialogue_func {
using f_t = std::function<void( dialogue &, double )> ( * )( char scope,
std::vector<std::string> const & );
std::vector<diag_value> const & );

dialogue_func_ass( std::string_view s_, std::string_view sc_, int n_, f_t f_ )
: dialogue_func( s_, sc_, n_ ), f( f_ ) {}
Expand All @@ -40,31 +57,31 @@ using pdiag_func_eval = dialogue_func_eval const *;
using pdiag_func_ass = dialogue_func_ass const *;

std::function<double( dialogue & )> u_val( char scope,
std::vector<std::string> const &params );
std::vector<diag_value> const &params );
std::function<void( dialogue &, double )> u_val_ass( char scope,
std::vector<std::string> const &params );
std::vector<diag_value> const &params );

std::function<double( dialogue & )> option_eval( char scope,
std::vector<std::string> const &params );
std::vector<diag_value> const &params );

std::function<double( dialogue & )> armor_eval( char scope,
std::vector<std::string> const &params );
std::vector<diag_value> const &params );

std::function<double( dialogue & )> pain_eval( char scope,
std::vector<std::string> const &/* params */ );
std::vector<diag_value> const &/* params */ );

std::function<void( dialogue &, double )> pain_ass( char scope,
std::vector<std::string> const &/* params */ );
std::vector<diag_value> const &/* params */ );

std::function<double( dialogue & )> skill_eval( char scope,
std::vector<std::string> const &params );
std::vector<diag_value> const &params );
std::function<void( dialogue &, double )> skill_ass( char scope,
std::vector<std::string> const &params );
std::vector<diag_value> const &params );

std::function<double( dialogue & )> weather_eval( char /* scope */,
std::vector<std::string> const &params );
std::vector<diag_value> const &params );
std::function<void( dialogue &, double )> weather_ass( char /* scope */,
std::vector<std::string> const &params );
std::vector<diag_value> const &params );

inline std::array<dialogue_func_eval, 6> const dialogue_eval_f{
dialogue_func_eval{ "val", "un", -1, u_val },
Expand Down
8 changes: 1 addition & 7 deletions src/math_parser_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <variant>
#include <vector>

#include "cata_utility.h"
#include "debug.h"
#include "dialogue_helpers.h"
#include "math_parser_diag.h"
Expand Down Expand Up @@ -131,13 +132,6 @@ struct thingie {
impl_t data;
};

// overload pattern from https://en.cppreference.com/w/cpp/utility/variant/visit
template <class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template <class... Ts>
explicit overloaded( Ts... ) -> overloaded<Ts...>;
constexpr double thingie::eval( dialogue &d ) const
{
return std::visit( overloaded{
Expand Down
Loading

0 comments on commit aa3b581

Please sign in to comment.