From aae81f0638f2e01e771eea9033fb535f6d50a22f Mon Sep 17 00:00:00 2001 From: eltank <8000047+eltank@users.noreply.github.com> Date: Sun, 5 Sep 2021 16:08:33 -0700 Subject: [PATCH] NPCs keep track of sales value (#51388) Add `sold` to NPC `op_of_u` Add dialog support for reading/writing this value through `"u_val": "sold"` --- doc/NPCs.md | 1 + src/condition.cpp | 8 ++++++++ src/debug_menu.cpp | 16 ++++++++++++---- src/game.cpp | 6 +----- src/npc.h | 7 +++---- src/npctalk.cpp | 28 ++++++++++++++++++---------- src/npctalk_funcs.cpp | 13 +++++++++---- src/npctrade.cpp | 4 ++++ src/npctrade.h | 1 + src/savegame_json.cpp | 2 ++ src/talker.h | 8 ++++++-- src/talker_npc.cpp | 15 ++++++++++++--- src/talker_npc.h | 6 ++++-- 13 files changed, 81 insertions(+), 34 deletions(-) diff --git a/doc/NPCs.md b/doc/NPCs.md index 65079df9cdaa3..3e42431a8874a 100644 --- a/doc/NPCs.md +++ b/doc/NPCs.md @@ -799,6 +799,7 @@ Example | Description `"u_val": "allies"` | Number of allies the character has. Only supported for the player character. Can be read but not written to. `"u_val": "cash"` | Ammount of money the character has. Only supported for the player character. Can be read but not written to. `"u_val": "owed"` | Owed money to the NPC you're talking to. +`"u_val": "sold"` | Amount sold to the NPC you're talking to. `"u_val": "skill_level"` | Level in given skill. `"skill"` must also be specified. `"u_val": "pos_x"` | Player character x coordinate. "pos_y" and "pos_z" also works as expected. `"u_val": "pain"` | Pain level. diff --git a/src/condition.cpp b/src/condition.cpp index 1b2710577fdbe..bb92ef4b520b8 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -1026,6 +1026,14 @@ std::function conditional_t::get_get_int( const JsonObject return d.actor( true )->debt(); }; } + } else if( checked_value == "sold" ) { + if( is_npc ) { + jo.throw_error( "owed ammount not supported for NPCs. In " + jo.str() ); + } else { + return []( const T & d ) { + return d.actor( true )->sold(); + }; + } } else if( checked_value == "skill_level" ) { const skill_id skill( jo.get_string( "skill" ) ); return [is_npc, skill]( const T & d ) { diff --git a/src/debug_menu.cpp b/src/debug_menu.cpp index e08e26eeed3a3..b58e0aa355055 100644 --- a/src/debug_menu.cpp +++ b/src/debug_menu.cpp @@ -1169,8 +1169,9 @@ void character_edit_menu() data << string_format( _( "Trust: %d" ), np->op_of_u.trust ) << " " << string_format( _( "Fear: %d" ), np->op_of_u.fear ) << " " << string_format( _( "Value: %d" ), np->op_of_u.value ) << " " - << string_format( _( "Anger: %d" ), np->op_of_u.anger ) << " " - << string_format( _( "Owed: %d" ), np->op_of_u.owed ) << std::endl; + << string_format( _( "Anger: %d" ), np->op_of_u.anger ) << std::endl; + data << string_format( _( "Owed: %d" ), np->op_of_u.owed ) << " " + << string_format( _( "Sold: %d" ), np->op_of_u.sold ) << std::endl; data << string_format( _( "Aggression: %d" ), static_cast( np->personality.aggression ) ) << " " @@ -1178,10 +1179,11 @@ void character_edit_menu() << string_format( _( "Collector: %d" ), static_cast( np->personality.collector ) ) << " " << string_format( _( "Altruism: %d" ), static_cast( np->personality.altruism ) ) << std::endl; - data << _( "Needs:" ) << std::endl; + data << _( "Needs:" ); for( const auto &need : np->needs ) { - data << need << std::endl; + data << " " << npc::get_need_str_id( need ); } + data << std::endl; data << string_format( _( "Total morale: %d" ), np->get_morale_level() ) << std::endl; nmenu.text = data.str(); @@ -1397,6 +1399,7 @@ void character_edit_menu() smenu.addentry( 2, true, 't', "%s: %d", _( "value" ), np->op_of_u.value ); smenu.addentry( 3, true, 'f', "%s: %d", _( "anger" ), np->op_of_u.anger ); smenu.addentry( 4, true, 'd', "%s: %d", _( "owed" ), np->op_of_u.owed ); + smenu.addentry( 5, true, 'd', "%s: %d", _( "sold" ), np->op_of_u.sold ); smenu.query(); int value; @@ -1429,6 +1432,11 @@ void character_edit_menu() np->op_of_u.owed = value; } break; + case 5: + if( query_int( value, _( "Set sold to? Currently: %d" ), np->op_of_u.sold ) ) { + np->op_of_u.sold = value; + } + break; } } break; diff --git a/src/game.cpp b/src/game.cpp index 0d7cb11b1e1f9..f91997aee327e 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2592,11 +2592,7 @@ void game::reset_npc_dispositions() npc_to_add->chatbin.clear_all(); npc_to_add->mission = NPC_MISSION_NULL; npc_to_add->set_attitude( NPCATT_NULL ); - npc_to_add->op_of_u.anger = 0; - npc_to_add->op_of_u.fear = 0; - npc_to_add->op_of_u.trust = 0; - npc_to_add->op_of_u.value = 0; - npc_to_add->op_of_u.owed = 0; + npc_to_add->op_of_u = npc_opinion(); npc_to_add->set_fac( faction_id( "no_faction" ) ); npc_to_add->add_new_mission( mission::reserve_random( ORIGIN_ANY_NPC, npc_to_add->global_omt_location(), diff --git a/src/npc.h b/src/npc.h index a7735d70eb128..cc2fe0d6557d0 100644 --- a/src/npc.h +++ b/src/npc.h @@ -255,6 +255,7 @@ struct npc_opinion { int value; int anger; int owed; // Positive when the npc owes the player. Negative if player owes them. + int sold; // Total value of goods sold/donated by player to the npc. Cannot be negative. npc_opinion() { trust = 0; @@ -262,10 +263,7 @@ struct npc_opinion { value = 0; anger = 0; owed = 0; - } - - npc_opinion( int T, int F, int V, int A, int O ) : - trust( T ), fear( F ), value( V ), anger( A ), owed( O ) { + sold = 0; } npc_opinion &operator+=( const npc_opinion &rhs ) { @@ -274,6 +272,7 @@ struct npc_opinion { value += rhs.value; anger += rhs.anger; owed += rhs.owed; + sold += rhs.sold; return *this; } diff --git a/src/npctalk.cpp b/src/npctalk.cpp index 0b4bb1c8ade58..044557cc250f4 100644 --- a/src/npctalk.cpp +++ b/src/npctalk.cpp @@ -2507,12 +2507,20 @@ std::function talk_effect_fun_t::get_set_int( con jo.throw_error( "altering cash this way is currently not supported. In " + jo.str() ); } else if( checked_value == "owed" ) { if( is_npc ) { - jo.throw_error( "owed ammount not supported for NPCs. In " + jo.str() ); + jo.throw_error( "owed amount not supported for NPCs. In " + jo.str() ); } else { return []( const dialogue & d, int input ) { d.actor( true )->add_debt( input - d.actor( true )->debt() ); }; } + } else if( checked_value == "sold" ) { + if( is_npc ) { + jo.throw_error( "sold amount not supported for NPCs. In " + jo.str() ); + } else { + return []( const dialogue & d, int input ) { + d.actor( true )->add_sold( input - d.actor( true )->sold() ); + }; + } } else if( checked_value == "skill_level" ) { const skill_id skill( jo.get_string( "skill" ) ); return [is_npc, skill]( const dialogue & d, int input ) { @@ -2923,20 +2931,20 @@ talk_topic talk_effect_t::apply( dialogue &d ) const { if( d.has_beta ) { // Need to get a reference to the mission before effects are applied, because effects can remove the mission - mission *miss = d.actor( true )->selected_mission(); + const mission *miss = d.actor( true )->selected_mission(); for( const talk_effect_fun_t &effect : effects ) { effect( d ); } - d.actor( true )->add_opinion( opinion.trust, opinion.fear, opinion.value, opinion.anger, - opinion.owed ); + d.actor( true )->add_opinion( opinion ); if( miss && ( mission_opinion.trust || mission_opinion.fear || mission_opinion.value || mission_opinion.anger ) ) { - int m_value = d.actor( true )->cash_to_favor( miss->get_value() ); - d.actor( true )->add_opinion( mission_opinion.trust ? m_value / mission_opinion.trust : 0, - mission_opinion.fear ? m_value / mission_opinion.fear : 0, - mission_opinion.value ? m_value / mission_opinion.value : 0, - mission_opinion.anger ? m_value / mission_opinion.anger : 0, - 0 ); + const int m_value = d.actor( true )->cash_to_favor( miss->get_value() ); + npc_opinion op; + op.trust = mission_opinion.trust ? m_value / mission_opinion.trust : 0; + op.fear = mission_opinion.fear ? m_value / mission_opinion.fear : 0; + op.value = mission_opinion.value ? m_value / mission_opinion.value : 0; + op.anger = mission_opinion.anger ? m_value / mission_opinion.anger : 0; + d.actor( true )->add_opinion( op ); } if( d.actor( true )->turned_hostile() ) { d.actor( true )->make_angry(); diff --git a/src/npctalk_funcs.cpp b/src/npctalk_funcs.cpp index e6ab87b07e680..6738e79c116c4 100644 --- a/src/npctalk_funcs.cpp +++ b/src/npctalk_funcs.cpp @@ -123,8 +123,10 @@ void talk_function::mission_success( npc &p ) } int miss_val = npc_trading::cash_to_favor( p, miss->get_value() ); - npc_opinion tmp( 0, 0, 1 + miss_val / 5, -1, 0 ); - p.op_of_u += tmp; + npc_opinion op; + op.value = 1 + miss_val / 5; + op.anger = -1; + p.op_of_u += op; faction *p_fac = p.get_faction(); if( p_fac != nullptr ) { int fac_val = std::min( 1 + miss_val / 10, 10 ); @@ -142,8 +144,11 @@ void talk_function::mission_failure( npc &p ) debugmsg( "mission_failure: mission_selected == nullptr" ); return; } - npc_opinion tmp( -1, 0, -1, 1, 0 ); - p.op_of_u += tmp; + npc_opinion op; + op.trust = -1; + op.value = -1; + op.anger = 1; + p.op_of_u += op; miss->fail(); } diff --git a/src/npctrade.cpp b/src/npctrade.cpp index f51e77034b6f8..9e9e9bf68754d 100644 --- a/src/npctrade.cpp +++ b/src/npctrade.cpp @@ -664,6 +664,9 @@ bool trading_window::perform_trade( npc &np, const std::string &deal ) int delta_price = ip.price * change_amount; if( !np.will_exchange_items_freely() ) { your_balance -= delta_price; + if( ip.selected != focus_them ) { + your_sale_value -= delta_price; + } } if( ip.loc.where() == item_location::type::character ) { volume_left += ip.vol * change_amount; @@ -702,6 +705,7 @@ int trading_window::calc_npc_owes_you( const npc &np ) const void trading_window::update_npc_owed( npc &np ) { np.op_of_u.owed = calc_npc_owes_you( np ); + np.op_of_u.sold += your_sale_value; } // Oh my aching head diff --git a/src/npctrade.h b/src/npctrade.h index 43384251a3a61..36625c3def543 100644 --- a/src/npctrade.h +++ b/src/npctrade.h @@ -53,6 +53,7 @@ class trading_window trading_window() = default; std::vector theirs; std::vector yours; + int your_sale_value = 0; int your_balance = 0; void setup_trade( int cost, npc &np ); diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 1c8641fffe601..6dcba82d1403d 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -1676,6 +1676,7 @@ void npc_opinion::deserialize( JsonIn &jsin ) data.read( "value", value ); data.read( "anger", anger ); data.read( "owed", owed ); + data.read( "sold", sold ); } void npc_opinion::serialize( JsonOut &json ) const @@ -1686,6 +1687,7 @@ void npc_opinion::serialize( JsonOut &json ) const json.member( "value", value ); json.member( "anger", anger ); json.member( "owed", owed ); + json.member( "sold", sold ); json.end_object(); } diff --git a/src/talker.h b/src/talker.h index 40387f517f4e8..1d03e77b804bb 100644 --- a/src/talker.h +++ b/src/talker.h @@ -14,6 +14,7 @@ class item_location; class mission; class monster; class npc; +struct npc_opinion; class Character; class recipe; struct tripoint; @@ -252,6 +253,10 @@ class talker return 0; } virtual void add_debt( int ) {} + virtual int sold() const { + return 0; + } + virtual void add_sold( int ) {} virtual std::vector items_with( const std::function & ) const { return {}; } @@ -377,8 +382,7 @@ class talker virtual std::string opinion_text() const { return ""; } - virtual void add_opinion( int /*trust*/, int /*fear*/, int /*value*/, int /*anger*/, - int /*debt*/ ) {} + virtual void add_opinion( const npc_opinion & ) {} virtual void set_first_topic( const std::string & ) {} virtual bool is_safe() const { return true; diff --git a/src/talker_npc.cpp b/src/talker_npc.cpp index 27109ca7bba39..97e5d328df0d6 100644 --- a/src/talker_npc.cpp +++ b/src/talker_npc.cpp @@ -391,6 +391,16 @@ void talker_npc::add_debt( const int cost ) me_npc->op_of_u.owed += cost; } +int talker_npc::sold() const +{ + return me_npc->op_of_u.sold; +} + +void talker_npc::add_sold( const int value ) +{ + me_npc->op_of_u.sold += value; +} + int talker_npc::cash_to_favor( const int value ) const { return npc_trading::cash_to_favor( *me_npc, value ); @@ -879,10 +889,9 @@ std::string talker_npc::opinion_text() const return me_npc->opinion_text(); } -void talker_npc::add_opinion( const int trust, const int fear, const int value, - const int anger, const int debt ) +void talker_npc::add_opinion( const npc_opinion &op ) { - me_npc->op_of_u += npc_opinion( trust, fear, value, anger, debt ); + me_npc->op_of_u += op; } bool talker_npc::enslave_mind() diff --git a/src/talker_npc.h b/src/talker_npc.h index 9567908843345..522c2da7b5830 100644 --- a/src/talker_npc.h +++ b/src/talker_npc.h @@ -57,8 +57,10 @@ class talker_npc : public talker_character const spell_id &c_spell, const proficiency_id &c_proficiency ) override; // inventory, buying, and selling - void add_debt( int cost ) override; int debt() const override; + void add_debt( int cost ) override; + int sold() const override; + void add_sold( int value ) override; int cash_to_favor( int value ) const override; std::string give_item_to( bool to_use ) override; bool buy_from( int amount ) override; @@ -100,7 +102,7 @@ class talker_npc : public talker_character // miscellaneous std::string opinion_text() const override; - void add_opinion( int trust, int fear, int value, int anger, int debt ) override; + void add_opinion( const npc_opinion &op ) override; bool enslave_mind() override; void set_first_topic( const std::string &chat_topic ) override; bool is_safe() const override;