Skip to content

Commit

Permalink
JSONize flag-based modifiers on comestible enjoyability (CleverRaven#…
Browse files Browse the repository at this point in the history
…35813)

* JSONize flag taste modifiers
There was only a single flag that affected the "fun" value of
comestibles (BAD_TASTE) but I want to add more for a water sanitation
rework, so rather than hardcoding more, I JSONized the process.

Adds a new member variable to json_flag objects, "taste_mod," which
additively modifies the enjoyability of comestibles.
This is done via a new function, "item::get_comestible_fun". The MUSHY
flag check was moved there from Character::fun_for as well.
"item::get_comestible_fun" is of course called in "Character::fun_for",
so this commit shouldn't have any functional changes.

* Make "islot_comestible::fun" private
Anyone trying to query a comestible's "fun" should be using the new
"item::get_comestible_fun" method. This commit is to enforce that.

Of course, that had to be reconciled with the current usage of "fun".
I added "item" and "Item_factory" as friends of "islot_comestible" so
that "item::get_comestible_fun" and the function that sets "fun" still
have access to it.
All other queries of "fun" were instead changed to the new method.
This has the side-effect of applying BAD_TASTE and MUSHY effects in
places where they didn't before, including but not limited to:
- Interaction with taste blocker bionic.
- NPCs making decisions about faction food stocks.
- NPCs making decisions about whether they personally want food.
- Scavenging monsters deciding whether to eat food off the ground.

* Apply suggestions from code review

Co-Authored-By: BevapDin <[email protected]>

* Subjugate rogue tab (i.e. astyle)

* Fix merge mistake
  • Loading branch information
Davi-DeGanne authored and ZhilkinSerg committed Dec 6, 2019
1 parent 81be329 commit b1cff02
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 71 deletions.
1 change: 1 addition & 0 deletions data/json/flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,7 @@
"type": "json_flag",
"context": [ "COMESTIBLE" ],
"craft_inherit": true,
"taste_mod": -5,
"//": "Has a bad taste or texture that can't be covered up through cooking.",
"info": "This food is <bad>unappetizing</bad> in a way that <bad>can't be covered up by most cooking</bad>."
},
Expand Down
16 changes: 4 additions & 12 deletions src/consumption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,23 +176,15 @@ std::pair<int, int> Character::fun_for( const item &comest ) const
static const std::string flag_EATEN_COLD( "EATEN_COLD" );
static const std::string flag_COLD( "COLD" );
static const std::string flag_FROZEN( "FROZEN" );
static const std::string flag_MUSHY( "MUSHY" );
static const std::string flag_MELTS( "MELTS" );
static const std::string flag_LUPINE( "LUPINE" );
static const std::string flag_FELINE( "FELINE" );
static const std::string flag_BAD_TASTE( "BAD_TASTE" );
if( !comest.is_comestible() ) {
return std::pair<int, int>( 0, 0 );
}

// As float to avoid rounding too many times
float fun = comest.get_comestible()->fun;
if( comest.has_flag( flag_BAD_TASTE ) ) {
fun -= 5; // BAD_TASTE is just a morale debuff that persists through crafting
}
if( comest.has_flag( flag_MUSHY ) && fun > -5.0f ) {
fun = -5.0f; // defrosted MUSHY food is practicaly tastless or tastes off
}
float fun = comest.get_comestible_fun();
if( ( has_effect( effect_common_cold ) || has_effect( effect_flu ) ) && fun > 0 ) {
fun /= 3; // food doesn't taste as good when you're sick
}
Expand Down Expand Up @@ -250,7 +242,7 @@ std::pair<int, int> Character::fun_for( const item &comest ) const
}

if( has_active_bionic( bio_taste_blocker ) &&
get_power_level() > units::from_kilojoule( abs( comest.get_comestible()->fun ) ) &&
get_power_level() > units::from_kilojoule( abs( comest.get_comestible_fun() ) ) &&
fun < 0 ) {
fun = 0;
}
Expand Down Expand Up @@ -819,7 +811,7 @@ bool player::eat( item &food, bool force )
}

if( has_active_bionic( bio_taste_blocker ) ) {
mod_power_level( units::from_kilojoule( -abs( food.get_comestible()->fun ) ) );
mod_power_level( units::from_kilojoule( -abs( food.get_comestible_fun() ) ) );
}

if( food.has_flag( "CANNIBALISM" ) ) {
Expand Down Expand Up @@ -869,7 +861,7 @@ bool player::eat( item &food, bool force )
add_msg_if_player( m_bad,
_( "You try to ignore its mushy texture, but it leaves you with an awful aftertaste." ) );
}
if( food.type->comestible->fun > 0 ) {
if( food.get_comestible_fun() > 0 ) {
if( has_effect( effect_common_cold ) ) {
add_msg_if_player( m_bad, _( "You can't taste much of anything with this cold." ) );
}
Expand Down
2 changes: 1 addition & 1 deletion src/faction_camp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3689,7 +3689,7 @@ bool basecamp::distribute_food()
g->m.add_item_or_charges( litter_spread, i, false );
i = comest;
}
if( i.is_comestible() && ( i.rotten() || i.get_comestible()->fun < -6 ) ) {
if( i.is_comestible() && ( i.rotten() || i.get_comestible_fun() < -6 ) ) {
keep_me.push_back( i );
} else if( i.is_food() ) {
double rot_multip;
Expand Down
1 change: 1 addition & 0 deletions src/flag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ void json_flag::load( JsonObject &jo )
jo.read( "conflicts", f.conflicts_ );
jo.read( "inherit", f.inherit_ );
jo.read( "craft_inherit", f.craft_inherit_ );
jo.read( "taste_mod", f.taste_mod_ );

// FIXME: most flags have a "context" field that isn't used for anything
// Test for it here to avoid errors about unvisited members
Expand Down
6 changes: 6 additions & 0 deletions src/flag.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class json_flag
return craft_inherit_;
}

/** The flag's modifier on the fun value of comestibles */
int taste_mod() const {
return taste_mod_;
}

/** Is this a valid (non-null) flag */
operator bool() const {
return !id_.empty();
Expand All @@ -46,6 +51,7 @@ class json_flag
std::set<std::string> conflicts_;
bool inherit_ = true;
bool craft_inherit_ = false;
int taste_mod_ = 0;

json_flag( const std::string &id = std::string() ) : id_( id ) {}

Expand Down
22 changes: 21 additions & 1 deletion src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,7 @@ void item::med_info( const item *med_item, std::vector<iteminfo> &info, const it
info.push_back( iteminfo( "MED", _( "Quench: " ), med_com->quench ) );
}

if( med_com->fun != 0 && parts->test( iteminfo_parts::MED_JOY ) ) {
if( med_item->get_comestible_fun() != 0 && parts->test( iteminfo_parts::MED_JOY ) ) {
info.push_back( iteminfo( "MED", _( "Enjoyability: " ),
g->u.fun_for( *med_item ).first ) );
}
Expand Down Expand Up @@ -4580,6 +4580,26 @@ std::set<matec_id> item::get_techniques() const
return result;
}

int item::get_comestible_fun() const
{
if( !is_comestible() ) {
return 0;
}
int fun = get_comestible()->fun;
for( const std::string &flag : item_tags ) {
fun += json_flag::get( flag ).taste_mod();
}
for( const std::string &flag : type->item_tags ) {
fun += json_flag::get( flag ).taste_mod();
}

if( has_flag( "MUSHY" ) ) {
return std::min( -5, fun ); // defrosted MUSHY food is practicaly tastless or tastes off
}

return fun;
}

bool item::goes_bad() const
{
if( item_internal::goes_bad_cache_is_set() ) {
Expand Down
2 changes: 2 additions & 0 deletions src/item.h
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,8 @@ class item : public visitable<item>
/** reset the last_temp_check used when crafting new items and the like */
void reset_temp_check();

int get_comestible_fun() const;

/** whether an item is perishable (can rot) */
bool goes_bad() const;

Expand Down
103 changes: 53 additions & 50 deletions src/itype.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,77 +107,80 @@ struct islot_tool {
};

struct islot_comestible {
/** subtype, e.g. FOOD, DRINK, MED */
std::string comesttype;
friend Item_factory;
friend item;
/** subtype, e.g. FOOD, DRINK, MED */
std::string comesttype;

/** tool needed to consume (e.g. lighter for cigarettes) */
std::string tool = "null";
/** tool needed to consume (e.g. lighter for cigarettes) */
std::string tool = "null";

/** Defaults # of charges (drugs, loaf of bread? etc) */
int def_charges = 1;
/** Defaults # of charges (drugs, loaf of bread? etc) */
int def_charges = 1;

/** effect on character thirst (may be negative) */
int quench = 0;
/** effect on character thirst (may be negative) */
int quench = 0;

/** amount of kcal this food has */
unsigned int kcal = 0;
/** amount of kcal this food has */
unsigned int kcal = 0;

/** Time until becomes rotten at standard temperature, or zero if never spoils */
time_duration spoils = 0_turns;
/** Time until becomes rotten at standard temperature, or zero if never spoils */
time_duration spoils = 0_turns;

/** addiction potential */
int addict = 0;
/** addiction potential */
int addict = 0;

/** effects of addiction */
add_type add = ADD_NULL;
/** effects of addiction */
add_type add = ADD_NULL;

/** effect on morale when consuming */
int fun = 0;
/** stimulant effect */
int stim = 0;

/** stimulant effect */
int stim = 0;
/** Reference to other item that replaces this one as a component in recipe results */
itype_id cooks_like;

/** Reference to other item that replaces this one as a component in recipe results */
itype_id cooks_like;
/** Reference to item that will be received after smoking current item */
itype_id smoking_result;

/** Reference to item that will be received after smoking current item */
itype_id smoking_result;
/** TODO: add documentation */
int healthy = 0;

/** TODO: add documentation */
int healthy = 0;
/** chance (odds) of becoming parasitised when eating (zero if never occurs) */
int parasites = 0;

/** chance (odds) of becoming parasitised when eating (zero if never occurs) */
int parasites = 0;
/** probability [0, 100] to get food poisoning from this comestible */
int contamination = 0;

/** probability [0, 100] to get food poisoning from this comestible */
int contamination = 0;
/** freezing point in degrees Fahrenheit, below this temperature item can freeze */
int freeze_point = temperatures::freezing;

/** freezing point in degrees Fahrenheit, below this temperature item can freeze */
int freeze_point = temperatures::freezing;
//** specific heats in J/(g K) and latent heat in J/g */
float specific_heat_liquid = 4.186;
float specific_heat_solid = 2.108;
float latent_heat = 333;

//** specific heats in J/(g K) and latent heat in J/g */
float specific_heat_liquid = 4.186;
float specific_heat_solid = 2.108;
float latent_heat = 333;
/** vitamins potentially provided by this comestible (if any) */
std::map<vitamin_id, int> vitamins;

/** vitamins potentially provided by this comestible (if any) */
std::map<vitamin_id, int> vitamins;
/** 1 nutr ~= 8.7kcal (1 nutr/5min = 288 nutr/day at 2500kcal/day) */
static constexpr float kcal_per_nutr = 2500.0f / ( 12 * 24 );

/** 1 nutr ~= 8.7kcal (1 nutr/5min = 288 nutr/day at 2500kcal/day) */
static constexpr float kcal_per_nutr = 2500.0f / ( 12 * 24 );
int get_calories() const {
return kcal;
}

int get_calories() const {
return kcal;
}
int get_nutr() const {
return kcal / kcal_per_nutr;
}
/** The monster group that is drawn from when the item rots away */
mongroup_id rot_spawn = mongroup_id::NULL_ID();

int get_nutr() const {
return kcal / kcal_per_nutr;
}
/** The monster group that is drawn from when the item rots away */
mongroup_id rot_spawn = mongroup_id::NULL_ID();
/** Chance the above monster group spawns*/
int rot_spawn_chance = 10;

/** Chance the above monster group spawns*/
int rot_spawn_chance = 10;
private:
/** effect on morale when consuming */
int fun = 0;
};

struct islot_brewable {
Expand Down
2 changes: 1 addition & 1 deletion src/iuse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ int iuse::blech( player *p, item *it, bool, const tripoint & )
p->stomach.mod_quench( 20 ); //acidproof people can drink acids like diluted water.
p->mod_healthy_mod( it->get_comestible()->healthy * multiplier,
it->get_comestible()->healthy * multiplier );
p->add_morale( MORALE_FOOD_BAD, it->get_comestible()->fun * multiplier, 60, 1_hours, 30_minutes,
p->add_morale( MORALE_FOOD_BAD, it->get_comestible_fun() * multiplier, 60, 1_hours, 30_minutes,
false, it->type );
} else {
p->add_msg_if_player( m_bad, _( "Blech, that burns your throat!" ) );
Expand Down
2 changes: 1 addition & 1 deletion src/monattack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ bool mattack::eat_food( monster *z )
auto items = g->m.i_at( p );
for( auto &item : items ) {
//Fun limit prevents scavengers from eating feces
if( !item.is_food() || item.get_comestible()->fun < -20 ) {
if( !item.is_food() || item.get_comestible_fun() < -20 ) {
continue;
}
//Don't eat own eggs
Expand Down
6 changes: 3 additions & 3 deletions src/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2973,9 +2973,9 @@ bool npc::will_accept_from_player( const item &it ) const
return false;
}

if( const auto &comest = it.is_container() ? it.get_contained().get_comestible() :
it.get_comestible() ) {
if( comest->fun < 0 || it.poison > 0 ) {
const auto &comest = it.is_container() ? it.get_contained() : it;
if( comest.is_comestible() ) {
if( it.get_comestible_fun() < 0 || it.poison > 0 ) {
return false;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/npcmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3621,9 +3621,9 @@ static float rate_food( const item &it, int want_nutr, int want_quench )
}

float weight = std::max( 1.0, 10.0 * relative_rot );
if( food->fun < 0 ) {
if( it.get_comestible_fun() < 0 ) {
// This helps to avoid eating stuff like flour
weight /= ( -food->fun ) + 1;
weight /= ( -it.get_comestible_fun() ) + 1;
}

if( food->healthy < 0 ) {
Expand Down

0 comments on commit b1cff02

Please sign in to comment.