Skip to content

Commit

Permalink
Streamline has_trait_flag behavior (#2911)
Browse files Browse the repository at this point in the history
* Streamline has_trait_flag behavior

* Start work on it

* Update type_id.h

* Update the thing

* Commit latest fixes

* Update flag_trait.h

* Commit what I have for now

* style(autofix.ci): automated formatting

* Update condition.cpp

* And that should be the basics of the code

* style(autofix.ci): automated formatting

* Start up the JSON work next

* Update flags_mutation.json

* Update JSON_FLAGS.md

* style(autofix.ci): automated formatting

* Update flags_mutation.json

* Update string_id_null_ids.cpp

* Apply suggestions from code review

Co-authored-by: Olanti <[email protected]>

* Do some of the suggested things

* Work on part of the requests

* Add the validation thingy

* refactor: extract `trait_flag_str_id` for `PRED`

* refactor: remove `conflicts` usage

Co-authored-by: olanti-p <[email protected]>

* refactor: extract other trait flag ids

Co-authored-by: olanti-p <[email protected]>

* style(autofix.ci): automated formatting

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olanti <[email protected]>
Co-authored-by: scarf <[email protected]>
  • Loading branch information
4 people authored Sep 26, 2023
1 parent 87a04bc commit dc43e23
Show file tree
Hide file tree
Showing 22 changed files with 430 additions and 55 deletions.
6 changes: 0 additions & 6 deletions data/json/flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -1047,12 +1047,6 @@
"context": [ "SPELL" ],
"//": "pain altering spells can't be resisted (like with the deadened trait)"
},
{
"id": "NON_THRESH",
"type": "json_flag",
"context": [ "mutation" ],
"//": "This mutation does not count toward thresholds at all."
},
{
"id": "EFFECT_FEATHER_FALL",
"context": [ ],
Expand Down
132 changes: 132 additions & 0 deletions data/json/flags_mutation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
[
{
"id": "BG_SURVIVAL_STORY",
"//": "This trait flag is used in dialogue JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "CANNIBAL",
"type": "mutation_flag"
},
{
"id": "hair_black",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_blond",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_brown",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_crewcut",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_fro",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_gray",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_long",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_medium",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_mohawk",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_red",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_short",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "hair_white",
"//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "mycus",
"//": "This trait flag is used in dialogue JSON, with no hardcode usage at present.",
"type": "mutation_flag"
},
{
"id": "MUTATION_THRESHOLD",
"type": "mutation_flag"
},
{
"id": "NEED_ACTIVE_TO_MELEE",
"type": "mutation_flag"
},
{
"id": "NO_THIRST",
"type": "mutation_flag"
},
{
"id": "NO_RADIATION",
"type": "mutation_flag"
},
{
"id": "NON_THRESH",
"type": "mutation_flag"
},
{
"id": "PSYCHOPATH",
"type": "mutation_flag"
},
{
"id": "PRED1",
"type": "mutation_flag"
},
{
"id": "PRED2",
"type": "mutation_flag"
},
{
"id": "PRED3",
"type": "mutation_flag"
},
{
"id": "PRED4",
"type": "mutation_flag"
},
{
"id": "SAPIOVORE",
"type": "mutation_flag"
},
{
"id": "SILENT_SPELL",
"type": "mutation_flag"
},
{
"id": "SUBTLE_SPELL",
"type": "mutation_flag"
},
{
"id": "UNARMED_BONUS",
"type": "mutation_flag"
}
]
1 change: 0 additions & 1 deletion data/json/obsoletion/mutations.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@
"points": 2,
"description": "Your body is simply immune to diseases. You will never catch an ambient disease.",
"prereqs": [ "DISRESISTANT" ],
"flags": [ "NO_DISEASE" ],
"valid": false
},
{
Expand Down
33 changes: 32 additions & 1 deletion doc/src/content/docs/en/mod/json/reference/json_flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -1178,10 +1178,41 @@ example, impale and scratch.

## Mutations

#### Flags
#### Mutation Flags

Mutation flags use a different JSON type from other flags, see json/flags_mutation.json. Primary
difference is that `conflicts` and `requires` are the only additional properties that can be added
to them.

The following show all trait flags that are currently used by the game's code. Trait flags must also
be defined in JSON if they are to be used in NPC dialogue conditions.

- `CANNIBAL` No morale penalty from butchery human corpses, skips warning you about human meat.
NOTE: this only skips the warning, the actual morale effects of eating human flesh still require
one of the relevant traits. Custom traits with this flag will skip the warning and suffer the
morale penalty.
- `NEED_ACTIVE_TO_MELEE` A mutation with this flag will only provide unarmed bonuses if it's been
toggled on.
- `NO_RADIATION` This mutation grants immunity to radiations.
- `NO_THIRST` Your thirst is not modified by food or drinks.
- `NON_THRESH` Mutations with this flag will not count towards the mutation strength (and thus
ability to breach a mutation threshold) of any categories it counts as belonging to.
- `PRED1` Reduces morale impact of enzlaving zombie corpses, reduces morale impact of killing
monsters with the `GUILT` flag.
- `PRED2` Increases EXP gain from combat, negates skill rust of combat skills (if skill rust is
enabled), reduces morale impact of enzlaving zombie corpses, reduces morale impact of killing
monsters with the `GUILT` flag.
- `PRED3` Increases EXP gain from combat, negates skill rust of combat skills (if skill rust is
enabled), increases tolerance for enzlaving zombies while already depressed, negates morale impact
of killing monsters with the `GUILT` flag.
- `PRED4` Increases EXP gain from combat, prevents EXP gain from combat from affecting focus,
negates skill rust of combat skills (if skill rust is enabled), negates morale impact of enzlaving
zombie corpses, increases tolerance for enzlaving zombies while already depressed, negates morale
impact of killing monsters with the `GUILT` flag.
- `PSYCHOPATH` No morale penalty from butchering human corpses.
- `SAPIVORE` No morale penalty from butcheing human corpses.
- `SILENT_SPELL` Negates the negative impact of mouth encumbrance on spells with the `VERBAL` flag.
- `SUBTLE_SPELL` Negates the negative impact of arm encumbrance on spells with the `SOMATIC` flag.
- `UNARMED_BONUS` You get a bonus to unarmed bash and cut damage equal to unarmed_skill/2 up to 4.

### Categories
Expand Down
6 changes: 3 additions & 3 deletions src/activity_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,9 @@ static const quality_id qual_LOCKPICK( "LOCKPICK" );
static const species_id HUMAN( "HUMAN" );
static const species_id ZOMBIE( "ZOMBIE" );

static const std::string trait_flag_CANNIBAL( "CANNIBAL" );
static const std::string trait_flag_PSYCHOPATH( "PSYCHOPATH" );
static const std::string trait_flag_SAPIOVORE( "SAPIOVORE" );
static const trait_flag_str_id trait_flag_CANNIBAL( "CANNIBAL" );
static const trait_flag_str_id trait_flag_PSYCHOPATH( "PSYCHOPATH" );
static const trait_flag_str_id trait_flag_SAPIOVORE( "SAPIOVORE" );

static const bionic_id bio_ears( "bio_ears" );
static const bionic_id bio_painkiller( "bio_painkiller" );
Expand Down
35 changes: 21 additions & 14 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,15 @@ static const flag_str_id flag_BIONIC_ARMOR_INTERFACE( "BIONIC_ARMOR_INTERFACE" )
static const mtype_id mon_player_blob( "mon_player_blob" );
static const mtype_id mon_shadow_snake( "mon_shadow_snake" );

static const trait_flag_str_id trait_flag_PRED1( "PRED1" );
static const trait_flag_str_id trait_flag_PRED2( "PRED2" );
static const trait_flag_str_id trait_flag_PRED3( "PRED3" );
static const trait_flag_str_id trait_flag_PRED4( "PRED4" );

static const trait_flag_str_id flag_NO_THIRST( "NO_THIRST" );
static const trait_flag_str_id flag_NO_RADIATION( "NO_RADIATION" );
static const trait_flag_str_id flag_NON_THRESH( "NON_THRESH" );

namespace io
{

Expand Down Expand Up @@ -3446,16 +3455,16 @@ void Character::practice( const skill_id &id, int amount, int cap, bool suppress
amount = 0;
}
}
if( has_trait_flag( "PRED2" ) && skill.is_combat_skill() ) {
if( has_trait_flag( trait_flag_PRED2 ) && skill.is_combat_skill() ) {
if( one_in( 3 ) ) {
amount *= 2;
}
}
if( has_trait_flag( "PRED3" ) && skill.is_combat_skill() ) {
if( has_trait_flag( trait_flag_PRED3 ) && skill.is_combat_skill() ) {
amount *= 2;
}

if( has_trait_flag( "PRED4" ) && skill.is_combat_skill() ) {
if( has_trait_flag( trait_flag_PRED4 ) && skill.is_combat_skill() ) {
amount *= 3;
}

Expand Down Expand Up @@ -3489,8 +3498,9 @@ void Character::practice( const skill_id &id, int amount, int cap, bool suppress
focus_pool -= chance_to_drop / 100;
// Apex Predators don't think about much other than killing.
// They don't lose Focus when practicing combat skills.
if( ( rng( 1, 100 ) <= ( chance_to_drop % 100 ) ) && ( !( has_trait_flag( "PRED4" ) &&
skill.is_combat_skill() ) ) ) {
if( ( rng( 1, 100 ) <= ( chance_to_drop % 100 ) ) &&
( !( has_trait_flag( trait_flag_PRED4 ) &&
skill.is_combat_skill() ) ) ) {
focus_pool--;
}
}
Expand Down Expand Up @@ -3600,17 +3610,14 @@ void Character::apply_skill_boost()
void Character::do_skill_rust()
{
const int rust_rate_tmp = rust_rate();
static const std::string PRED2( "PRED2" );
static const std::string PRED3( "PRED3" );
static const std::string PRED4( "PRED4" );
for( std::pair<const skill_id, SkillLevel> &pair : *_skills ) {
const Skill &aSkill = *pair.first;
SkillLevel &skill_level_obj = pair.second;

if( aSkill.is_combat_skill() &&
( ( has_trait_flag( PRED2 ) && calendar::once_every( 8_hours ) ) ||
( has_trait_flag( PRED3 ) && calendar::once_every( 4_hours ) ) ||
( has_trait_flag( PRED4 ) && calendar::once_every( 3_hours ) ) ) ) {
( ( has_trait_flag( trait_flag_PRED2 ) && calendar::once_every( 8_hours ) ) ||
( has_trait_flag( trait_flag_PRED3 ) && calendar::once_every( 4_hours ) ) ||
( has_trait_flag( trait_flag_PRED4 ) && calendar::once_every( 3_hours ) ) ) ) {
// Their brain is optimized to remember this
if( one_in( 13 ) ) {
// They've already passed the roll to avoid rust at
Expand Down Expand Up @@ -4438,7 +4445,7 @@ std::pair<std::string, nc_color> Character::get_fatigue_description() const

void Character::mod_thirst( int nthirst )
{
if( has_trait_flag( "NO_THIRST" ) ) {
if( has_trait_flag( flag_NO_THIRST ) ) {
return;
}
set_thirst( std::max( -100, thirst + nthirst ) );
Expand Down Expand Up @@ -7084,7 +7091,7 @@ void Character::set_rad( int new_rad )

void Character::mod_rad( int mod )
{
if( has_trait_flag( "NO_RADIATION" ) ) {
if( has_trait_flag( flag_NO_RADIATION ) ) {
return;
}
set_rad( std::max( 0, get_rad() + mod ) );
Expand Down Expand Up @@ -7810,7 +7817,7 @@ void Character::set_highest_cat_level()
// Then use the map to set the category levels
for( const std::pair<const trait_id, int> &i : dependency_map ) {
const mutation_branch &mdata = i.first.obj();
if( !mdata.flags.count( "NON_THRESH" ) ) {
if( !mdata.flags.count( flag_NON_THRESH ) ) {
for( const std::string &cat : mdata.category ) {
// Decay category strength based on how far it is from the current mutation
mutation_category_level[cat] += 8 / static_cast<int>( std::pow( 2, i.second ) );
Expand Down
2 changes: 1 addition & 1 deletion src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ class Character : public Creature, public visitable<Character>
/** Returns true if the player has the entered starting trait */
bool has_base_trait( const trait_id &b ) const;
/** Returns true if player has a trait with a flag */
bool has_trait_flag( const std::string &b ) const;
bool has_trait_flag( const trait_flag_str_id &b ) const;
/** Returns true if character has a trait which cancels the entered trait. */
bool has_opposite_trait( const trait_id &flag ) const;

Expand Down
13 changes: 9 additions & 4 deletions src/condition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class basecamp;
class recipe;

static const efftype_id effect_currently_busy( "currently_busy" );

static const trait_flag_str_id flag_MUTATION_THRESHOLD( "MUTATION_THRESHOLD" );
// throws an error on failure, so no need to return
std::string get_talk_varname( const JsonObject &jo, const std::string &member, bool check_value )
{
Expand Down Expand Up @@ -126,13 +126,18 @@ template<class T>
void conditional_t<T>::set_has_trait_flag( const JsonObject &jo, const std::string &member,
bool is_npc )
{
const std::string &trait_flag_to_check = jo.get_string( member );
condition = [trait_flag_to_check, is_npc]( const T & d ) {
const std::string &raw = jo.get_string( member );
const trait_flag_str_id trait_flag_to_check( raw );
if( !trait_flag_to_check.is_valid() ) {
jo.show_warning( string_format( "Invalid trait flag %s", raw ), member );
}
const bool check_threshold = trait_flag_to_check == flag_MUTATION_THRESHOLD;
condition = [trait_flag_to_check, check_threshold, is_npc]( const T & d ) {
player *actor = d.alpha;
if( is_npc ) {
actor = dynamic_cast<player *>( d.beta );
}
if( trait_flag_to_check == "MUTATION_THRESHOLD" ) {
if( check_threshold ) {
return actor->crossed_threshold();
}
return actor->has_trait_flag( trait_flag_to_check );
Expand Down
4 changes: 3 additions & 1 deletion src/consumption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ static const trait_id trait_THRESH_URSINE( "THRESH_URSINE" );
static const trait_id trait_VEGETARIAN( "VEGETARIAN" );
static const trait_id trait_WATERSLEEP( "WATERSLEEP" );

static const trait_flag_str_id trait_flag_CANNIBAL( "CANNIBAL" );

static const std::string flag_HIDDEN_HALLU( "HIDDEN_HALLU" );
static const std::string flag_ALLERGEN_EGG( "ALLERGEN_EGG" );
static const std::string flag_ALLERGEN_FRUIT( "ALLERGEN_FRUIT" );
Expand Down Expand Up @@ -755,7 +757,7 @@ ret_val<edible_rating> Character::will_eat( const item &food, bool interactive )
}

const bool carnivore = has_trait( trait_CARNIVORE );
if( food.has_flag( flag_CANNIBALISM ) && !has_trait_flag( "CANNIBAL" ) ) {
if( food.has_flag( flag_CANNIBALISM ) && !has_trait_flag( trait_flag_CANNIBAL ) ) {
add_consequence( _( "The thought of eating human flesh makes you feel sick." ),
edible_rating::cannibalism );
}
Expand Down
Loading

0 comments on commit dc43e23

Please sign in to comment.