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

math_parser: evaluate variables in diag. functions #65467

Merged
merged 1 commit into from
May 19, 2023
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
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