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

Eating same food repeatedly gives less fun #35910

Merged
merged 6 commits into from
Dec 15, 2019

Conversation

Davi-DeGanne
Copy link
Contributor

@Davi-DeGanne Davi-DeGanne commented Dec 6, 2019

Requires #35999.

Summary

SUMMARY: Features "Eating same food repeatedly gives less fun"

Purpose of change

Simulates food getting less exciting when you eat it repeatedly.

Intended to be used for nutrient bars in #35872.

Describe the solution

Introduces a new JSON comestible property, "monotony_penalty", that is subtracted from a food item's fun score for every food like it you've eaten in the last 48 hours. The penalty can't take a food's fun below 0 or reduce fun for foods that already had negative fun, unless that food has the "NEGATIVE_MONOTONY_OK" flag.

Foods are only considered similar for this purpose if their type IDs, and the type IDs of their components, match.

Default "monotony_penalty" is 2, except for junk food where it's 0.

Describe alternatives you've considered

Testing

  1. Spawn several charges of some non-junkfood food, with positive morale. Each time you eat a charge, the enjoyability goes down by 2.
  2. Spawn several charges of some food, with negative morale. The enjoyability is unchanged when you eat a charge.
  3. Spawn several charges of some junkfood, with positive morale. The enjoyability is unchanged when you eat a charge.
  4. Set the time forward by more than 2 days, and note that the morale values have returned to normal.

Additional context

@Davi-DeGanne Davi-DeGanne changed the title Eating same food repeatedly gives less fun [WIP] Eating same food repeatedly gives less fun Dec 7, 2019
@Davi-DeGanne
Copy link
Contributor Author

Needs more work. I realized I can't safely use item pointers, since the items will probably go out of scope (thus the Travis failure). Also, I want to make foods with different ingredients count as different from each other.

@Davi-DeGanne Davi-DeGanne changed the title [WIP] Eating same food repeatedly gives less fun Eating same food repeatedly gives less fun Dec 7, 2019
@Davi-DeGanne
Copy link
Contributor Author

Davi-DeGanne commented Dec 7, 2019

Okay, instead of keeping track of the items themselves, this now keeps track of a history of item IDs and hashes of their associated components.

@ZhilkinSerg ZhilkinSerg added <Bugfix> This is a fix for a bug (or closes open issue) [C++] Changes (can be) made in C++. Previously named `Code` <Enhancement / Feature> New features, or enhancements on existing Items: Food / Vitamins Comestibles and drinks and removed <Bugfix> This is a fix for a bug (or closes open issue) labels Dec 7, 2019
@Qrox
Copy link
Contributor

Qrox commented Dec 9, 2019

The consumption history doesn't seem to be saved?

@Davi-DeGanne Davi-DeGanne changed the title Eating same food repeatedly gives less fun [WIP] Eating same food repeatedly gives less fun Dec 9, 2019
@Davi-DeGanne
Copy link
Contributor Author

Davi-DeGanne commented Dec 9, 2019

I coded up the serialization/deserialization as per Qrox's astute observation, unfortunately, the uint64_t type that I use for the component hash doesn't seem to have serialization/deserialization either. Not sure what the solution is, will take a closer look tomorrow.


consumption_event( const item &food ) {
time = calendar::turn;
type_id = food.typeId();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this could be included in the hash. You don't actually do anything but compare it, just like the hash.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KorGgenT expressed interest in using this consumption history for other things (like a in-game nutrient-tracking device) later, so I figured I'd store the item type separately.

src/character.h Outdated Show resolved Hide resolved
if( fun > 0 || comest.has_flag( "NEGATIVE_MONOTONY_OK" ) ) {
for( const consumption_event &event : consumption_history ) {
if( event.time > calendar::turn - 2_days && event.type_id == comest.typeId() &&
event.component_hash == comest.make_component_hash() ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since comest.make_component_hash() is always calculated here each time and not cached, you might just as well compare the component list directly, which actually might be faster, and you don't have to worry about hash collision and serialization too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had it like that, but it would require storing the entire items in event which would be making a ton of unnecessary item copies.

std::multiset<std::string> id_set;
for( const item &it : components ) {
id_set.insert( it.typeId() );
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this correctly, food with even the slightly different components will not suffer from the monotony penalty, and food with the same components but completely different sub-components will still suffer from the penalty? I think ideally there should be a metric to determine how similar two foods are, but I'm not sure how (or whether) that could be done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just have another idea, though it might actually belong to a separate PR: count the fun bonus and monotony penalty from sub-components and cooking recipes separately, so making different foods from the same materials, and making the same food from different materials will both impose a penalty, while eating the same food made from the same materials gives the most penalty.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bad idea, but again, I don't see a way of doing that without storing copies of the items themselves in the consumption_event struct. Doing this for every NPC as well as the player would be a huge performance overhead for a relatively small feature.

@Davi-DeGanne
Copy link
Contributor Author

@Qrox

I will say that these problems:

food with even the slightly different components will not suffer from the monotony penalty, and food with the same components but completely different sub-components will still suffer from the penalty

Bother me too. I thought about making some exceptions, like not counting water and clean water towards it, but it seemed too cumbersome and finicky in terms of maintainability. Another option is making the component hash recursive (so it hashes components' components and so on) but that would make matching even more strict, making it even easier than it already is to game the system by using slightly different ingredients.

If you (or anyone) can think of a solution that improves on these two problems, while keeping the stored consumption_event objects lightweight, I would love to hear it.

Primarily to expand support for very large integers. Before, precision
was being lost due to conversion to float, and then back to integer.

I also made integer loading more strict. An error will now be thrown if:
 1. An unsigned integer is provided with a negative value.
 2. The supplied integer is too big (we now have int64 support, but even
    that has its limits)
 3. An integer is supplied with a decimal (previously it was truncated)
 4. The above rules hold even when scientific notation is used (yes, we
    have support for using scientific notation in JSON fields)

Number 3 immediately caused a lot of errors in both the base game
and in mods. I went through and fixed these.
@Davi-DeGanne Davi-DeGanne changed the title [WIP] Eating same food repeatedly gives less fun Eating same food repeatedly gives less fun Dec 10, 2019
@Davi-DeGanne
Copy link
Contributor Author

Well, serialization is done. As far as the component comparison issues go, we discussed it on discord, and:

  • Kevin said he's not worried about iterating recursively over all components. I'm inclined to agree -- it's just too much effort and overhead for such a minor feature.
  • Some of us discussed the opposite issue (slightly different components still resulting in effectively different food) and came up with a solution: An item property, defined in JSON, called same_taste or some such, which will specify a different item. For the purpose of constructing an item's component hash, the components will be replaced by their respective same_taste item. That said, this feature is probably best in a separate PR.

I am now satisfied with this (though it does now require #35999).

The way the absolute value of INT_MIN was being determined before may
have given inconsistent results on different platforms.
Also, switched to using std::numeric_limits rather than the
corresponding macro constants.
I wanted to be absolutely sure that these functions never risked
overflow at any point on any compiler, because I know that if at any
point they return an incorrect value, saved games will fail to load due
to incorrectly thinking it's unsafe to read INT_MIN from JSON.
Also, Qrox pointed out that they should be constexpr.
Introduces a new JSON comestible property, "monotony_penalty", that is
subtracted from a food item's fun score for every identical meal you've
eaten in the last 48 hours. Foods are considered identical if the have
the same id, and their components also have the same ids.

The penalty can't take a food's fun below 0
or reduce fun for foods that already had negative fun, unless
that food has the "NEGATIVE_MONOTONY_OK" flag.
Default "monotony_penalty" is 2, except for junk food where it's 0.
The first method was just using bitwise XOR of the string hashes of the
components' type IDs. This could be prone to collisions.
Instead, this method sorts, concatenates, and then hashes the IDs.
So that monotony penalties carry over upon save/load.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[C++] Changes (can be) made in C++. Previously named `Code` <Enhancement / Feature> New features, or enhancements on existing Items: Food / Vitamins Comestibles and drinks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants