summaryrefslogtreecommitdiff
path: root/vampirism.patch
diff options
context:
space:
mode:
Diffstat (limited to 'vampirism.patch')
-rw-r--r--vampirism.patch911
1 files changed, 911 insertions, 0 deletions
diff --git a/vampirism.patch b/vampirism.patch
new file mode 100644
index 0000000..319e1fd
--- /dev/null
+++ b/vampirism.patch
@@ -0,0 +1,911 @@
+--- a/src/activity_handlers.cpp
++++ b/src/activity_handlers.cpp
+@@ -231,6 +231,7 @@
+ static const species_id species_HUMAN( "HUMAN" );
+ static const species_id species_ZOMBIE( "ZOMBIE" );
+
++static const json_character_flag json_flag_VAMPIRE( "VAMPIRE" );
+ static const json_character_flag json_flag_CANNIBAL( "CANNIBAL" );
+ static const json_character_flag json_flag_PSYCHOPATH( "PSYCHOPATH" );
+ static const json_character_flag json_flag_SAPIOVORE( "SAPIOVORE" );
+@@ -616,7 +619,8 @@
+ !corpse.in_species( species_ZOMBIE ) );
+ if( is_human && !( u.has_trait_flag( json_flag_CANNIBAL ) ||
+ u.has_trait_flag( json_flag_PSYCHOPATH ) ||
+- u.has_trait_flag( json_flag_SAPIOVORE ) ) ) {
++ u.has_trait_flag( json_flag_SAPIOVORE ) ||
++ u.has_trait_flag( json_flag_VAMPIRE ) ) ) {
+
+ if( u.is_player() ) {
+ if( query_yn( _( "Would you dare desecrate the mortal remains of a fellow human being?" ) ) ) {
+
+--- a/src/character.cpp
++++ b/src/character.cpp
+@@ -343,6 +343,9 @@
+ static const trait_id trait_NOMAD2( "NOMAD2" );
+ static const trait_id trait_NOMAD3( "NOMAD3" );
+ static const trait_id trait_NOPAIN( "NOPAIN" );
++static const trait_id trait_PAINREC1( "PAINREC1" );
++static const trait_id trait_PAINREC2( "PAINREC2" );
++static const trait_id trait_PAINREC3( "PAINREC3" );
+ static const trait_id trait_PADDED_FEET( "PADDED_FEET" );
+ static const trait_id trait_PAWS( "PAWS" );
+ static const trait_id trait_PAWS_LARGE( "PAWS_LARGE" );
+@@ -370,6 +373,9 @@
+ static const trait_id trait_TOUGH_FEET( "TOUGH_FEET" );
+ static const trait_id trait_TRANSPIRATION( "TRANSPIRATION" );
+ static const trait_id trait_URSINE_EYE( "URSINE_EYE" );
++static const trait_id trait_VAMP_HUNGER( "VAMP_HUNGER" );
++static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" );
++static const trait_id trait_VAMP_VISION( "VAMP_VISION" );
+ static const trait_id trait_VISCOUS( "VISCOUS" );
+ static const trait_id trait_WATERSLEEP( "WATERSLEEP" );
+ static const trait_id trait_WEBBED( "WEBBED" );
+@@ -2095,6 +2096,9 @@
+ if( has_trait( trait_DEBUG_NIGHTVISION ) ) {
+ vision_mode_cache.set( DEBUG_NIGHTVISION );
+ }
++ if( has_active_mutation( trait_VAMP_VISION ) ) {
++ vision_mode_cache.set( VAMP_VISION );
++ }
+ if( has_nv() ) {
+ vision_mode_cache.set( NV_GOGGLES );
+ }
+@@ -2168,8 +2172,10 @@
+ }
+
+ float range = get_per() / 3.0f - eyes_encumb / 10.0f;
+- if( vision_mode_cache[NV_GOGGLES] || vision_mode_cache[NIGHTVISION_3] ||
+- vision_mode_cache[FULL_ELFA_VISION] || vision_mode_cache[CEPH_VISION] ) {
++ if( vision_mode_cache[VAMP_VISION] ) {
++ range += 15;
++ } else if( vision_mode_cache[NV_GOGGLES] || vision_mode_cache[NIGHTVISION_3] ||
++ vision_mode_cache[FULL_ELFA_VISION] || vision_mode_cache[CEPH_VISION] ) {
+ range += 10;
+ } else if( vision_mode_cache[NIGHTVISION_2] || vision_mode_cache[FELINE_VISION] ||
+ vision_mode_cache[URSINE_VISION] || vision_mode_cache[ELFA_VISION] ) {
+@@ -5442,7 +5447,15 @@
+ {
+ int pain_ticks = rate_multiplier;
+ while( get_pain() > 0 && pain_ticks-- > 0 ) {
+- mod_pain( -roll_remainder( 0.2f + get_pain() / 50.0f ) );
++ if( has_trait( trait_PAINREC1 ) ) {
++ mod_pain( -roll_remainder( 0.2f + get_pain() / 45.0f ) );
++ } else if( has_trait( trait_PAINREC2 ) ) {
++ mod_pain( -roll_remainder( 0.2f + get_pain() / 40.0f ) );
++ } else if( has_trait( trait_PAINREC3 ) ) {
++ mod_pain( -roll_remainder( 0.2f + get_pain() / 30.0f ) );
++ } else {
++ mod_pain( -roll_remainder( 0.2f + get_pain() / 50.0f ) );
++ }
+ }
+
+ float rest = rest_quality();
+@@ -5745,6 +5758,7 @@
+ const bool npc_no_food = is_npc() && get_option<bool>( "NO_NPC_FOOD" );
+ const bool foodless = debug_ls || npc_no_food;
+ const bool no_thirst = has_flag( json_flag_NO_THIRST );
++ const bool vamp = has_trait( trait_VAMP_HUNGER );
+ const bool mycus = has_trait( trait_M_DEPENDENT );
+ const float kcal_per_time = get_bmr() / ( 12.0f * 24.0f );
+ const int five_mins = ticks_between( from, to, 5_minutes );
+@@ -5821,7 +5835,7 @@
+ }
+ // Mycus and Metabolic Rehydration makes thirst unnecessary
+ // since water is not limited by intake but by absorption, we can just set thirst to zero
+- if( mycus || no_thirst ) {
++ if( mycus || vamp || no_thirst ) {
+ set_thirst( 0 );
+ }
+
+@@ -6153,19 +6167,21 @@
+ void Character::check_needs_extremes()
+ {
+ // Check if we've overdosed... in any deadly way.
+- if( get_stim() > 250 ) {
++ if( ( get_stim() > 250 ) && !( has_trait( trait_VAMP_SKIN ) ) ) {
+ add_msg_player_or_npc( m_bad,
+ _( "You have a sudden heart attack!" ),
+ _( "<npcname> has a sudden heart attack!" ) );
+ get_event_bus().send<event_type::dies_from_drug_overdose>( getID(), efftype_id() );
+ set_part_hp_cur( body_part_torso, 0 );
+- } else if( get_stim() < -200 || get_painkiller() > 240 ) {
++ } else if( ( get_stim() < -200 || get_painkiller() > 240 ) && !( has_trait( trait_VAMP_SKIN ) ) ) {
+ add_msg_player_or_npc( m_bad,
+ _( "Your breathing stops completely." ),
+ _( "<npcname>'s breathing stops completely." ) );
+ get_event_bus().send<event_type::dies_from_drug_overdose>( getID(), efftype_id() );
+ set_part_hp_cur( body_part_torso, 0 );
+- } else if( has_effect( effect_jetinjector ) && get_effect_dur( effect_jetinjector ) > 40_minutes ) {
++ } else if( ( has_effect( effect_jetinjector ) &&
++ get_effect_dur( effect_jetinjector ) > 40_minutes ) &&
++ !( has_trait( trait_VAMP_SKIN ) ) ) {
+ if( !( has_trait( trait_NOPAIN ) ) ) {
+ add_msg_player_or_npc( m_bad,
+ _( "Your heart spasms painfully and stops." ),
+@@ -6176,13 +6192,14 @@
+ }
+ get_event_bus().send<event_type::dies_from_drug_overdose>( getID(), effect_jetinjector );
+ set_part_hp_cur( body_part_torso, 0 );
+- } else if( get_effect_dur( effect_adrenaline ) > 50_minutes ) {
++ } else if( ( get_effect_dur( effect_adrenaline ) > 50_minutes ) &&
++ !( has_trait( trait_VAMP_SKIN ) ) ) {
+ add_msg_player_or_npc( m_bad,
+ _( "Your heart spasms and stops." ),
+ _( "<npcname>'s heart spasms and stops." ) );
+ get_event_bus().send<event_type::dies_from_drug_overdose>( getID(), effect_adrenaline );
+ set_part_hp_cur( body_part_torso, 0 );
+- } else if( get_effect_int( effect_drunk ) > 4 ) {
++ } else if( ( get_effect_int( effect_drunk ) > 4 ) && !( has_trait( trait_VAMP_SKIN ) ) ) {
+ add_msg_player_or_npc( m_bad,
+ _( "Your breathing slows down to a stop." ),
+ _( "<npcname>'s breathing slows down to a stop." ) );
+@@ -6447,7 +6464,7 @@
+
+ void Character::update_bodytemp()
+ {
+- if( has_trait( trait_DEBUG_NOTEMP ) ) {
++ if( has_trait( trait_DEBUG_NOTEMP ) || has_trait( trait_VAMP_SKIN ) ) {
+ set_all_parts_temp_conv( BODYTEMP_NORM );
+ set_all_parts_temp_cur( BODYTEMP_NORM );
+ return;
+@@ -11496,7 +11513,7 @@
+ int Character::heartrate_bpm() const
+ {
+ //Dead have no heartbeat usually and no heartbeat in omnicell
+- if( is_dead_state() || has_trait( trait_SLIMESPAWNER ) ) {
++ if( is_dead_state() || has_trait( trait_SLIMESPAWNER ) || has_trait( trait_VAMP_SKIN ) ) {
+ return 0;
+ }
+ //This function returns heartrate in BPM basing of health, physical state, tiredness,
+
+--- a/src/character.h
++++ b/src/character.h
+@@ -111,6 +111,7 @@
+ /// @note vision modes do not necessarily match json ids or flags
+ enum vision_modes {
+ DEBUG_NIGHTVISION,
++ VAMP_VISION,
+ NV_GOGGLES,
+ NIGHTVISION_1,
+ NIGHTVISION_2,
+@@ -256,6 +256,8 @@
+ ALLERGY_WEAK,
+ /// Penalty for eating human flesh (unless psycho/cannibal)
+ CANNIBALISM,
++ // Vampirism (unless psycho/cannibal)
++ VAMPIRISM,
+ /// Comestible has parasites
+ PARASITES,
+ /// Rotten (or, for saprophages, not rotten enough)
+
+--- a/src/consumption.cpp
++++ b/src/consumption.cpp
+@@ -132,6 +132,7 @@
+ static const trait_id trait_THRESH_LUPINE( "THRESH_LUPINE" );
+ static const trait_id trait_THRESH_PLANT( "THRESH_PLANT" );
+ static const trait_id trait_THRESH_URSINE( "THRESH_URSINE" );
++static const trait_id trait_VAMP_HUNGER( "VAMP_HUNGER" );
+ static const trait_id trait_VEGETARIAN( "VEGETARIAN" );
+ static const trait_id trait_WATERSLEEP( "WATERSLEEP" );
+
+@@ -146,6 +147,15 @@
+ flag_id( "ALLERGEN_MEAT" ), flag_id( "ALLERGEN_EGG" )
+ }};
+
++static const std::array<flag_id, 11> vamp_blacklist {{
++ flag_id( "ALLERGEN_VEGGY" ), flag_id( "ALLERGEN_FRUIT" ),
++ flag_id( "ALLERGEN_WHEAT" ), flag_id( "ALLERGEN_NUT" ),
++ flag_id( "ALLERGEN_MEAT" ), flag_id( "ALLERGEN_EGG" ),
++ flag_id( "ALLERGEN_JUNK" ), flag_id( "ALLERGEN_MILK" ),
++ flag_id( "ALLERGEN_HONEY" ), flag_id( "ALLERGEN_FOODSTUFF" ),
++ flag_id( "ALLERGEN_ALCOHOL" )
++ }};
++
+ // TODO: Move pizza scraping here.
+ static int compute_default_effective_kcal( const item &comest, const Character &you,
+ const cata::flat_set<flag_id> &extra_flags = {} )
+@@ -711,6 +721,13 @@
+ return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION, _( "Ugh, you can't drink that!" ) );
+ }
+
++ if( has_trait_flag( STATIC( json_character_flag( "VAMPIRE" ) ) ) &&
++ food.has_any_flag( vamp_blacklist ) &&
++ !food.has_flag( flag_VAMPIRISM_OK ) ) {
++ return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION,
++ _( "Bleh. This isn't blood!" ) );
++ }
++
+ if( has_trait( trait_CARNIVORE ) && nutrition_for( food ) > 0 &&
+ food.has_any_flag( carnivore_blacklist ) && !food.has_flag( flag_CARNIVORE_OK ) ) {
+ return ret_val<edible_rating>::make_failure( INEDIBLE_MUTATION,
+@@ -763,11 +780,18 @@
+ }
+ }
+
++ const bool food_is_human_blood = food.has_flag( flag_VAMPIRISM );
++ if( food_is_human_blood && !has_trait_flag( STATIC( json_character_flag( "VAMPIRE" ) ) ) &&
++ !has_trait_flag( STATIC( json_character_flag( "CANNIBAL" ) ) ) ) {
++ add_consequence( _( "The thought of drinking human blood makes you feel sick." ), VAMPIRISM );
++ }
++
+ const bool carnivore = has_trait( trait_CARNIVORE );
+ const bool food_is_human_flesh = food.has_flag( flag_CANNIBALISM ) ||
+ ( food.has_flag( flag_STRICT_HUMANITARIANISM ) &&
+ !has_trait_flag( json_flag_STRICT_HUMANITARIAN ) );
+- if( food_is_human_flesh && !has_trait_flag( STATIC( json_character_flag( "CANNIBAL" ) ) ) ) {
++ if( food_is_human_flesh && !has_trait_flag( STATIC( json_character_flag( "CANNIBAL" ) ) ) &&
++ !food.has_flag( flag_VAMPIRISM ) ) {
+ add_consequence( _( "The thought of eating human flesh makes you feel sick." ), CANNIBALISM );
+ }
+
+@@ -1136,10 +1160,54 @@
+ }
+ }
+
++ const bool food_is_human_blood = food.has_flag( flag_VAMPIRISM );
++ if( food_is_human_blood ) {
++ // Sapiovores don't recognize humans as the same species.
++ // But let them possibly feel cool about eating sapient stuff - treat like psycho
++ // However, spiritual sapiovores should still recognize humans as having a soul or special for religious reasons
++ const bool vamp = has_trait( trait_VAMP_HUNGER );
++ const bool cannibal = has_trait( trait_CANNIBAL );
++ const bool psycho = has_trait( trait_PSYCHOPATH );
++ const bool sapiovore = has_trait( trait_SAPIOVORE );
++ const bool spiritual = has_trait( trait_SPIRITUAL );
++ if( vamp ) {
++ add_msg_if_player( m_good, _( "You relish drinking the human blood." ) );
++ add_morale( MORALE_CANNIBAL, 30, 200 );
++ } else if( ( cannibal || sapiovore ) && psycho && spiritual ) {
++ add_msg_if_player( m_good,
++ _( "You drink the human blood, and in doing so, devour their spirit." ) );
++ // You're not really consuming anything special; you just think you are.
++ add_morale( MORALE_CANNIBAL, 25, 300 );
++ } else if( cannibal && psycho ) {
++ add_msg_if_player( m_good, _( "You drink the human blood." ) );
++ add_morale( MORALE_CANNIBAL, 15, 200 );
++ } else if( ( cannibal || sapiovore ) && spiritual ) {
++ add_msg_if_player( m_good, _( "You consume the sacred human blood." ) );
++ // Boosted because you understand the philosophical implications of your actions, and YOU LIKE THEM.
++ add_morale( MORALE_CANNIBAL, 15, 200 );
++ } else if( cannibal ) {
++ add_msg_if_player( m_good, _( "You indulge your shameful hunger." ) );
++ add_morale( MORALE_CANNIBAL, 10, 50 );
++ } else if( ( psycho || sapiovore ) && spiritual ) {
++ add_msg_if_player( _( "You greedily devour the taboo liquid." ) );
++ // Small bonus for violating a taboo.
++ add_morale( MORALE_CANNIBAL, 5, 50 );
++ } else if( psycho || sapiovore ) {
++ add_msg_if_player( _( "Meh. You've eaten worse." ) );
++ } else if( spiritual ) {
++ add_msg_if_player( m_bad,
++ _( "This is probably going to count against you if there's still an afterlife." ) );
++ add_morale( MORALE_CANNIBAL, -60, -400, 60_minutes, 30_minutes );
++ } else {
++ add_msg_if_player( m_bad, _( "You feel horrible for eating a person." ) );
++ add_morale( MORALE_CANNIBAL, -60, -400, 60_minutes, 30_minutes );
++ }
++ }
++
+ const bool food_is_human_flesh = food.has_flag( flag_CANNIBALISM ) ||
+ ( food.has_flag( flag_STRICT_HUMANITARIANISM ) &&
+ !has_trait_flag( json_flag_STRICT_HUMANITARIAN ) );
+- if( food_is_human_flesh ) {
++ if( food_is_human_flesh && !food_is_human_blood ) {
+ // Sapiovores don't recognize humans as the same species.
+ // But let them possibly feel cool about eating sapient stuff - treat like psycho
+ // However, spiritual sapiovores should still recognize humans as having a soul or special for religious reasons
+@@ -1214,6 +1282,11 @@
+ add_msg_if_player( m_bad, _( "Yuck! How can anybody eat this stuff?" ) );
+ add_morale( allergy, -75, -400, 30_minutes, 24_minutes );
+ }
++ if( allergy != MORALE_NULL && has_trait_flag( STATIC( json_character_flag( "VAMPIRE" ) ) ) ) {
++ add_msg_if_player( m_bad, _( "I can't stomach anything but blood!" ) );
++ add_morale( allergy, -75, -400, 30_minutes, 24_minutes );
++ vomit();
++ }
+ if( food.has_flag( flag_ALLERGEN_JUNK ) ) {
+ if( has_trait( trait_PROJUNK ) ) {
+ add_msg_if_player( m_good, _( "Mmm, junk food." ) );
+
+--- a/src/flag.cpp
++++ b/src/flag.cpp
+@@ -13,8 +13,11 @@
+ const flag_id flag_ACT_IN_FIRE( "ACT_IN_FIRE" );
+ const flag_id flag_ACT_ON_RANGED_HIT( "ACT_ON_RANGED_HIT" );
+ const flag_id flag_ALARMCLOCK( "ALARMCLOCK" );
++const flag_id flag_ALLERGEN_ALCOHOL( "ALLERGEN_ALCOHOL" );
+ const flag_id flag_ALLERGEN_EGG( "ALLERGEN_EGG" );
++const flag_id flag_ALLERGEN_FOODSTUFF( "ALLERGEN_FOODSTUFF" );
+ const flag_id flag_ALLERGEN_FRUIT( "ALLERGEN_FRUIT" );
++const flag_id flag_ALLERGEN_HONEY( "ALLERGEN_HONEY" );
+ const flag_id flag_ALLERGEN_JUNK( "ALLERGEN_JUNK" );
+ const flag_id flag_ALLERGEN_MEAT( "ALLERGEN_MEAT" );
+ const flag_id flag_ALLERGEN_MILK( "ALLERGEN_MILK" );
+@@ -303,6 +306,8 @@
+ const flag_id flag_USE_EAT_VERB( "USE_EAT_VERB" );
+ const flag_id flag_USE_PLAYER_ENERGY( "USE_PLAYER_ENERGY" );
+ const flag_id flag_USE_UPS( "USE_UPS" );
++const flag_id flag_VAMPIRISM( "VAMPIRISM" );
++const flag_id flag_VAMPIRISM_OK( "VAMPIRISM_OK" );
+ const flag_id flag_VARSIZE( "VARSIZE" );
+ const flag_id flag_VEHICLE( "VEHICLE" );
+ const flag_id flag_WAIST( "WAIST" );
+
+--- a/src/flag.h
++++ b/src/flag.h
+@@ -20,8 +20,11 @@
+ extern const flag_id flag_ACT_IN_FIRE;
+ extern const flag_id flag_ACT_ON_RANGED_HIT;
+ extern const flag_id flag_ALARMCLOCK;
++extern const flag_id flag_ALLERGEN_ALCOHOL;
+ extern const flag_id flag_ALLERGEN_EGG;
++extern const flag_id flag_ALLERGEN_FOODSTUFF;
+ extern const flag_id flag_ALLERGEN_FRUIT;
++extern const flag_id flag_ALLERGEN_HONEY;
+ extern const flag_id flag_ALLERGEN_JUNK;
+ extern const flag_id flag_ALLERGEN_MEAT;
+ extern const flag_id flag_ALLERGEN_MILK;
+@@ -310,6 +313,8 @@
+ extern const flag_id flag_USE_EAT_VERB;
+ extern const flag_id flag_USE_PLAYER_ENERGY;
+ extern const flag_id flag_USE_UPS;
++extern const flag_id flag_VAMPIRISM;
++extern const flag_id flag_VAMPIRISM_OK;
+ extern const flag_id flag_VARSIZE;
+ extern const flag_id flag_VEHICLE;
+ extern const flag_id flag_WAIST;
+
+--- a/src/game.cpp
++++ b/src/game.cpp
+@@ -1039,8 +1039,9 @@
+ int iInfoLine = 0;
+
+ if( u.has_amount( itype_holybook_bible1, 1 ) || u.has_amount( itype_holybook_bible2, 1 ) ||
+- u.has_amount( itype_holybook_bible3, 1 ) ) {
+- if( !( u.has_trait( trait_id( "CANNIBAL" ) ) || u.has_trait( trait_id( "PSYCHOPATH" ) ) ) ) {
++ u.has_amount( itype_holybook_bible3, 1 ) || u.has_trait( trait_id( "THRESH_VAMP" ) ) ) {
++ if( !( u.has_trait( trait_id( "CANNIBAL" ) ) || u.has_trait( trait_id( "PSYCHOPATH" ) ) ||
++ u.has_trait( trait_id( "THRESH_VAMP" ) ) ) ) {
+ vRip.emplace_back( " _______ ___" );
+ vRip.emplace_back( " < `/ |" );
+ vRip.emplace_back( " > _ _ (" );
+
+--- a/src/item.cpp
++++ b/src/item.cpp
+@@ -155,6 +155,7 @@
+
+ static const json_character_flag json_flag_CANNIBAL( "CANNIBAL" );
+ static const json_character_flag json_flag_IMMUNE_SPOIL( "IMMUNE_SPOIL" );
++static const json_character_flag json_flag_VAMPIRE( "VAMPIRE" );
+
+ static const bionic_id bio_digestion( "bio_digestion" );
+
+@@ -1967,19 +1967,35 @@
+ info.emplace_back( "DESCRIPTION",
+ _( "* This food will cause an <bad>allergic reaction</bad>." ) );
+ }
++ if( food_item->has_flag( flag_VAMPIRISM ) &&
++ parts->test( iteminfo_parts::FOOD_VAMPIRISM ) ) {
++ if( player_character.has_trait_flag( json_flag_VAMPIRE ) ) {
++ info.emplace_back( "DESCRIPTION",
++ _( "* This food contains <good>human blood</good>." ) );
++ } else if( player_character.has_trait_flag( json_flag_CANNIBAL ) ) {
++ info.emplace_back( "DESCRIPTION",
++ _( "* This food contains <good>human flesh</good>." ) );
++ } else {
++ info.emplace_back( "DESCRIPTION",
++ _( "* This food contains <bad>human flesh</bad>." ) );
++ }
++ }
+
+ if( food_item->has_flag( flag_CANNIBALISM ) &&
++ !food_item->has_flag( flag_VAMPIRISM ) &&
+ parts->test( iteminfo_parts::FOOD_CANNIBALISM ) ) {
+- if( !player_character.has_trait_flag( json_flag_CANNIBAL ) ) {
++ if( player_character.has_trait_flag( json_flag_CANNIBAL ) &&
++ !player_character.has_trait_flag( json_flag_VAMPIRE ) ) {
+ info.emplace_back( "DESCRIPTION",
+- _( "* This food contains <bad>human flesh</bad>." ) );
++ _( "* This food contains <good>human flesh</good>." ) );
+ } else {
+ info.emplace_back( "DESCRIPTION",
+- _( "* This food contains <good>human flesh</good>." ) );
++ _( "* This food contains <bad>human flesh</bad>." ) );
+ }
+ }
+
+- if( food_item->is_tainted() && parts->test( iteminfo_parts::FOOD_CANNIBALISM ) ) {
++ if( food_item->is_tainted() && ( parts->test( iteminfo_parts::FOOD_CANNIBALISM ) ||
++ parts->test( iteminfo_parts::FOOD_VAMPIRISM ) ) ) {
+ info.emplace_back( "DESCRIPTION",
+ _( "* This food is <bad>tainted</bad> and will poison you." ) );
+ }
+@@ -4374,6 +4390,7 @@
+ case ALLERGY:
+ case ALLERGY_WEAK:
+ case CANNIBALISM:
++ case VAMPIRISM:
+ case PARASITES:
+ ret = c_red;
+ break;
+
+--- a/src/item_factory.cpp
++++ b/src/item_factory.cpp
+@@ -2505,6 +2505,7 @@
+ // First allergens:
+ // An item is an allergen even if it has trace amounts of allergenic material
+ { material_id( "hflesh" ), flag_CANNIBALISM },
++ { material_id( "blood" ), flag_VAMPIRISM },
+
+ { material_id( "hflesh" ), flag_ALLERGEN_MEAT },
+ { material_id( "iflesh" ), flag_ALLERGEN_MEAT },
+@@ -2519,10 +2520,15 @@
+ { material_id( "mushroom" ), flag_ALLERGEN_VEGGY },
+ { material_id( "milk" ), flag_ALLERGEN_MILK },
+ { material_id( "egg" ), flag_ALLERGEN_EGG },
++ { material_id( "alcohol" ), flag_ALLERGEN_ALCOHOL },
++ { material_id( "foodstuff" ), flag_ALLERGEN_FOODSTUFF },
++ { material_id( "honey" ), flag_ALLERGEN_HONEY },
+ { material_id( "junk" ), flag_ALLERGEN_JUNK },
+ // Not food, but we can keep it here
+ { material_id( "wool" ), flag_ALLERGEN_WOOL },
+ // Now "made of". Those flags should not be passed
++ { material_id( "blood" ), flag_VAMPIRISM_OK },
++ { material_id( "blood" ), flag_CARNIVORE_OK },
+ { material_id( "flesh" ), flag_CARNIVORE_OK },
+ { material_id( "hflesh" ), flag_CARNIVORE_OK },
+ { material_id( "iflesh" ), flag_CARNIVORE_OK },
+
+--- a/src/iteminfo_query.h
++++ b/src/iteminfo_query.h
+@@ -41,6 +41,7 @@
+ FOOD_VITAMINS,
+ FOOD_VIT_EFFECTS,
+ FOOD_CANNIBALISM,
++ FOOD_VAMPIRISM,
+ FOOD_TAINT,
+ FOOD_POISON,
+ FOOD_ALLERGEN,
+
+--- a/src/iuse.cpp
++++ b/src/iuse.cpp
+@@ -342,6 +342,7 @@
+ static const trait_id trait_THRESH_PLANT( "THRESH_PLANT" );
+ static const trait_id trait_TOLERANCE( "TOLERANCE" );
+ static const trait_id trait_URSINE_EYE( "URSINE_EYE" );
++static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" );
+ static const trait_id trait_WAYFARER( "WAYFARER" );
+
+ static const quality_id qual_AXE( "AXE" );
+@@ -4547,6 +4549,9 @@
+ if( p->has_trait( trait_ILLITERATE ) ) {
+ p->add_msg_if_player( m_info, _( "You don't know what you're looking at." ) );
+ return cata::nullopt;
++ } else if( p->has_trait( trait_VAMP_SKIN ) ) {
++ p->add_msg_if_player( _( "Your %s shows warning: 'No heartbeat detected. "
++ "This device must be worn to provide fitness feedback.'" ), it->tname() );
+ } else {
+ //What else should block using f-band?
+ const int bpm = p->heartrate_bpm();
+
+--- a/src/map_field.cpp
++++ b/src/map_field.cpp
+@@ -92,6 +92,7 @@
+ static const trait_id trait_M_SKIN3( "M_SKIN3" );
+ static const trait_id trait_THRESH_MARLOSS( "THRESH_MARLOSS" );
+ static const trait_id trait_THRESH_MYCUS( "THRESH_MYCUS" );
++static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" );
+
+ using namespace map_field_processing;
+
+@@ -1558,7 +1559,8 @@
+ if( ( cur.get_field_intensity() > 1 || !one_in( 3 ) ) && ( !inside || one_in( 3 ) ) ) {
+ u.add_env_effect( effect_teargas, bodypart_id( "mouth" ), 5, 20_seconds );
+ }
+- if( cur.get_field_intensity() > 1 && ( !inside || one_in( 3 ) ) ) {
++ if( !( u.has_trait( trait_VAMP_SKIN ) ) && cur.get_field_intensity() > 1 && ( !inside ||
++ one_in( 3 ) ) ) {
+ u.add_env_effect( effect_blind, bodypart_id( "eyes" ), cur.get_field_intensity() * 2, 10_seconds );
+ }
+ }
+@@ -1685,7 +1687,8 @@
+ // The gas won't harm you inside a vehicle.
+ if( !inside ) {
+ // Full body suits protect you from the effects of the gas.
+- if( !( u.worn_with_flag( STATIC( flag_id( "GAS_PROOF" ) ) ) &&
++ if( !( u.has_trait( trait_VAMP_SKIN ) ) &&
++ !( u.worn_with_flag( STATIC( flag_id( "GAS_PROOF" ) ) ) &&
+ u.get_env_resist( bodypart_id( "mouth" ) ) >= 15 &&
+ u.get_env_resist( bodypart_id( "eyes" ) ) >= 15 ) ) {
+ const int intensity = cur.get_field_intensity();
+
+--- a/src/memorial_logger.cpp
++++ b/src/memorial_logger.cpp
+@@ -90,6 +90,7 @@
+ static const trait_id trait_CANNIBAL( "CANNIBAL" );
+ static const trait_id trait_PSYCHOPATH( "PSYCHOPATH" );
+ static const trait_id trait_SAPIOVORE( "SAPIOVORE" );
++static const trait_id trait_THRESH_VAMP( "THRESH_VAMP" );
+
+ memorial_log_entry::memorial_log_entry( const std::string &preformatted_msg ) :
+ preformatted_( preformatted_msg )
+@@ -590,6 +591,7 @@
+ character_id ch = e.get<character_id>( "killer" );
+ if( ch == avatar_id ) {
+ std::string name = e.get<cata_variant_type::string>( "victim_name" );
++ bool vampire = player_character.has_trait( trait_THRESH_VAMP );
+ bool cannibal = player_character.has_trait( trait_CANNIBAL );
+ bool psycho = player_character.has_trait( trait_PSYCHOPATH );
+ if( player_character.has_trait( trait_SAPIOVORE ) ) {
+@@ -613,6 +615,10 @@
+ add( pgettext( "memorial_male", "Killed an innocent, %s." ),
+ pgettext( "memorial_female", "Killed an innocent, %s." ),
+ name );
++ } else if( vampire ) {
++ add( pgettext( "memorial_male", "Killed an innocent human, %s." ),
++ pgettext( "memorial_female", "Killed an innocent human, %s." ),
++ name );
+ } else {
+ add( pgettext( "memorial_male",
+ "Killed an innocent person, %s, in cold blood and "
+
+--- a/src/morale_types.cpp
++++ b/src/morale_types.cpp
+@@ -40,6 +40,7 @@
+
+ morale_type( "morale_food_bad" ),
+ morale_type( "morale_cannibal" ),
++ morale_type( "morale_vampire" ),
+ morale_type( "morale_vegetarian" ),
+ morale_type( "morale_meatarian" ),
+ morale_type( "morale_antifruit" ),
+@@ -138,6 +139,7 @@
+ const morale_type MORALE_CRAVING_MARLOSS( "morale_craving_marloss" );
+ const morale_type MORALE_FOOD_BAD( "morale_food_bad" );
+ const morale_type MORALE_CANNIBAL( "morale_cannibal" );
++const morale_type MORALE_VAMPIRE( "morale_vampire" );
+ const morale_type MORALE_VEGETARIAN( "morale_vegetarian" );
+ const morale_type MORALE_MEATARIAN( "morale_meatarian" );
+ const morale_type MORALE_ANTIFRUIT( "morale_antifruit" );
+
+--- a/src/morale_types.h
++++ b/src/morale_types.h
+@@ -62,6 +62,7 @@
+ extern const morale_type MORALE_CRAVING_MARLOSS;
+ extern const morale_type MORALE_FOOD_BAD;
+ extern const morale_type MORALE_CANNIBAL;
++extern const morale_type MORALE_VAMPIRE;
+ extern const morale_type MORALE_VEGETARIAN;
+ extern const morale_type MORALE_MEATARIAN;
+ extern const morale_type MORALE_ANTIFRUIT;
+
+--- a/src/mutation.cpp
++++ b/src/mutation.cpp
+@@ -67,6 +67,7 @@
+ static const trait_id trait_STR_ALPHA( "STR_ALPHA" );
+ static const trait_id trait_THRESH_MARLOSS( "THRESH_MARLOSS" );
+ static const trait_id trait_THRESH_MYCUS( "THRESH_MYCUS" );
++static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" );
+ static const trait_id trait_TREE_COMMUNION( "TREE_COMMUNION" );
+ static const trait_id trait_VOMITOUS( "VOMITOUS" );
+ static const trait_id trait_WEB_WEAVER( "WEB_WEAVER" );
+@@ -877,6 +878,10 @@
+ {
+ bool force_bad = one_in( 3 );
+ bool force_good = false;
++ if( has_trait( trait_VAMP_SKIN ) ) {
++ add_msg_if_player( m_good, _( "Your Vampire blood quickly destroys the mutagenic contagion." ) );
++ return;
++ }
+ if( has_trait( trait_ROBUST ) && force_bad ) {
+ // Robust Genetics gives you a 33% chance for a good mutation,
+ // instead of the 33% chance of a bad one.
+
+--- a/src/npc.cpp
++++ b/src/npc.cpp
+@@ -124,6 +124,7 @@
+ static const trait_id trait_SAPIOVORE( "SAPIOVORE" );
+ static const trait_id trait_SCHIZOPHRENIC( "SCHIZOPHRENIC" );
+ static const trait_id trait_TERRIFYING( "TERRIFYING" );
++static const trait_id trait_THRESH_VAMP( "THRESH_VAMP" );
+
+ class monfaction;
+
+@@ -2600,12 +2601,15 @@
+
+ Character &player_character = get_player_character();
+ if( killer == &player_character && ( !guaranteed_hostile() || hit_by_player ) ) {
++ bool vampire = player_character.has_trait( trait_THRESH_VAMP );
+ bool cannibal = player_character.has_trait( trait_CANNIBAL );
+ bool psycho = player_character.has_trait( trait_PSYCHOPATH );
+ if( player_character.has_trait( trait_SAPIOVORE ) || psycho ) {
+ // No morale effect
+ } else if( cannibal ) {
+ player_character.add_morale( MORALE_KILLED_INNOCENT, -5, 0, 2_days, 3_hours );
++ } else if( vampire ) {
++ player_character.add_morale( MORALE_KILLED_INNOCENT, -5, 0, 2_days, 3_hours );
+ } else {
+ player_character.add_morale( MORALE_KILLED_INNOCENT, -100, 0, 2_days, 3_hours );
+ }
+
+--- a/src/player_hardcoded_effects.cpp
++++ b/src/player_hardcoded_effects.cpp
+@@ -115,6 +115,8 @@
+ static const trait_id trait_SEESLEEP( "SEESLEEP" );
+ static const trait_id trait_SCHIZOPHRENIC( "SCHIZOPHRENIC" );
+ static const trait_id trait_THRESH_MYCUS( "THRESH_MYCUS" );
++static const trait_id trait_THRESH_VAMP( "THRESH_VAMP" );
++static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" );
+ static const trait_id trait_WATERSLEEP( "WATERSLEEP" );
+
+ static const json_character_flag json_flag_ALARMCLOCK( "ALARMCLOCK" );
+@@ -882,7 +884,9 @@
+ }
+
+ if( dur > 1800_minutes && one_in( 300 * 512 ) ) {
+- if( !has_trait( trait_NOPAIN ) ) {
++ if( has_trait( trait_VAMP_SKIN ) ) {
++ return;
++ } else if( !has_trait( trait_NOPAIN ) ) {
+ add_msg_if_player( m_bad,
+ _( "Your heart spasms painfully and stops, dragging you back to reality as you die." ) );
+ } else {
+@@ -1337,6 +1341,9 @@
+ // Determine the strength of effects or dreams based upon category strength
+ int strength = 0; // Category too weak for any effect or dream
+ if( crossed_threshold() ) {
++ if( has_trait( trait_THRESH_VAMP ) ) {
++ highcat = mutation_category_id( "VAMP" );
++ }
+ strength = 4; // Post-human.
+ } else if( highest >= 20 && highest < 35 ) {
+ strength = 1; // Low strength
+
+--- a/src/suffer.cpp
++++ b/src/suffer.cpp
+@@ -151,6 +151,8 @@
+ static const trait_id trait_TROGLO2( "TROGLO2" );
+ static const trait_id trait_TROGLO3( "TROGLO3" );
+ static const trait_id trait_UNSTABLE( "UNSTABLE" );
++static const trait_id trait_VAMP_CURSE( "VAMP_CURSE" );
++static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" );
+ static const trait_id trait_VOMITOUS( "VOMITOUS" );
+ static const trait_id trait_WEB_SPINNER( "WEB_SPINNER" );
+ static const trait_id trait_WEB_WEAVER( "WEB_WEAVER" );
+@@ -243,7 +245,8 @@
+
+ void Character::suffer_while_underwater()
+ {
+- if( !has_trait( trait_GILLS ) && !has_trait( trait_GILLS_CEPH ) ) {
++ if( !has_trait( trait_GILLS ) && !has_trait( trait_GILLS_CEPH ) &&
++ !has_trait( trait_VAMP_SKIN ) ) {
+ oxygen--;
+ }
+ if( oxygen < 12 && worn_with_flag( flag_REBREATHER ) ) {
+@@ -746,7 +749,8 @@
+ return;
+ }
+
+- if( has_trait( trait_ALBINO ) || has_effect( effect_datura ) || has_trait( trait_SUNBURN ) ) {
++ if( has_trait( trait_ALBINO ) || has_effect( effect_datura ) || has_trait( trait_SUNBURN ) ||
++ has_trait( trait_VAMP_CURSE ) ) {
+ suffer_from_sunburn();
+ }
+
+@@ -772,6 +776,13 @@
+ mod_int_bonus( -4 );
+ mod_per_bonus( -4 );
+ }
++ if( has_trait( trait_VAMP_CURSE ) ) {
++ mod_str_bonus( -4 );
++ mod_dex_bonus( -4 );
++ add_miss_reason( _( "You can't tolerate the sunlight!" ), 4 );
++ mod_int_bonus( -4 );
++ mod_per_bonus( -4 );
++ }
+ }
+
+ std::map<bodypart_id, float> Character::bodypart_exposure()
+@@ -803,7 +814,8 @@
+
+ void Character::suffer_from_sunburn()
+ {
+- if( !has_trait( trait_ALBINO ) && !has_effect( effect_datura ) && !has_trait( trait_SUNBURN ) ) {
++ if( !has_trait( trait_ALBINO ) && !has_effect( effect_datura ) && !has_trait( trait_SUNBURN ) &&
++ !has_trait( trait_VAMP_CURSE ) ) {
+ return;
+ }
+
+@@ -820,6 +832,12 @@
+ return;
+ }
+ sunlight_effect = _( "The sunlight burns!" );
++ } else if( has_trait( trait_VAMP_CURSE ) ) {
++ // Sunburn effects occur about 10 times per minute
++ if( !one_turn_in( 6_seconds ) ) {
++ return;
++ }
++ sunlight_effect = _( "The sunlight sears" );
+ }
+
+ // Sunglasses can keep the sun off the eyes.
+@@ -897,7 +915,7 @@
+ }
+
+ // Solar Sensitivity (SUNBURN) trait causes injury to exposed parts
+- if( has_trait( trait_SUNBURN ) ) {
++ if( has_trait( trait_SUNBURN ) || has_trait( trait_VAMP_CURSE ) ) {
+ mod_pain( 1 );
+ // Check exposure of all body parts
+ for( const std::pair<const bodypart_id, float> &bp_exp : bp_exposure ) {
+
+--- a/data/json/field_type.json
++++ b/data/json/field_type.json
+@@ -354,7 +354,7 @@
+ "has_fume": true,
+ "percent_spread": 90,
+ "dirty_transparency_cache": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] }
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] }
+ },
+ {
+ "id": "fd_rubble",
+@@ -429,7 +429,7 @@
+ "dirty_transparency_cache": true,
+ "percent_spread": 10,
+ "outdoor_age_speedup": "0 turns",
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 7 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 7 ] ], "traits": [ "VAMP_SKIN" ] },
+ "priority": 8,
+ "half_life": "2 minutes",
+ "phase": "gas",
+@@ -515,7 +515,7 @@
+ "outdoor_age_speedup": "3 minutes",
+ "dirty_transparency_cache": true,
+ "has_fume": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] },
+ "priority": 8,
+ "half_life": "10 minutes",
+ "phase": "gas",
+@@ -544,7 +544,7 @@
+ "outdoor_age_speedup": "0 turns",
+ "dirty_transparency_cache": true,
+ "has_fume": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] },
+ "priority": 8,
+ "half_life": "5 minutes",
+ "phase": "gas",
+@@ -590,7 +590,7 @@
+ "wandering_field": "fd_toxic_gas",
+ "gas_absorption_factor": 15,
+ "dirty_transparency_cache": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] },
+ "phase": "gas",
+ "display_items": false,
+ "display_field": true,
+@@ -1138,7 +1138,7 @@
+ "outdoor_age_speedup": "5 minutes",
+ "dirty_transparency_cache": true,
+ "has_fume": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] },
+ "priority": 8,
+ "half_life": "50 minutes",
+ "phase": "gas",
+@@ -1160,7 +1160,7 @@
+ "outdoor_age_speedup": "1 minutes",
+ "dirty_transparency_cache": true,
+ "has_fume": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] },
+ "priority": 8,
+ "half_life": "15 minutes",
+ "phase": "gas",
+@@ -1407,7 +1407,7 @@
+ "outdoor_age_speedup": "3 minutes",
+ "dirty_transparency_cache": true,
+ "has_fume": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] },
+ "priority": 8,
+ "half_life": "10 minutes",
+ "phase": "gas"
+@@ -1427,7 +1427,7 @@
+ "outdoor_age_speedup": "1 minutes",
+ "dirty_transparency_cache": true,
+ "has_fume": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] },
+ "priority": 8,
+ "half_life": "30 minutes",
+ "phase": "gas",
+@@ -1449,7 +1449,7 @@
+ "outdoor_age_speedup": "1 minutes",
+ "dirty_transparency_cache": true,
+ "has_fume": true,
+- "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ] },
++ "immunity_data": { "body_part_env_resistance": [ [ "mouth", 15 ] ], "traits": [ "VAMP_SKIN" ] },
+ "priority": 8,
+ "half_life": "30 minutes",
+ "phase": "gas",
+
+--- a/data/json/flags.json
++++ b/data/json/flags.json
+@@ -1120,6 +1120,21 @@
+ "context": [ "COMESTIBLE" ]
+ },
+ {
++ "id": "ALLERGEN_HONEY",
++ "type": "json_flag",
++ "context": [ "COMESTIBLE" ]
++ },
++ {
++ "id": "ALLERGEN_ALCOHOL",
++ "type": "json_flag",
++ "context": [ "COMESTIBLE" ]
++ },
++ {
++ "id": "ALLERGEN_FOODSTUFF",
++ "type": "json_flag",
++ "context": [ "COMESTIBLE" ]
++ },
++ {
+ "id": "ALLERGEN_JUNK",
+ "type": "json_flag",
+ "context": [ "COMESTIBLE" ]
+@@ -1199,6 +1214,16 @@
+ "type": "json_flag",
+ "context": [ ]
+ },
++ {
++ "id": "VAMPIRISM",
++ "type": "json_flag",
++ "context": [ ]
++ },
++ {
++ "id": "VAMPIRISM_OK",
++ "type": "json_flag",
++ "context": [ ]
++ },
+ {
+ "id": "CASING",
+ "type": "json_flag",
+
+--- a/data/json/morale_types.json
++++ b/data/json/morale_types.json
+@@ -130,6 +130,11 @@
+ "text": "Ate Demihuman Flesh"
+ },
+ {
++ "id": "morale_vampire",
++ "type": "morale_type",
++ "text": "Drank Human Blood"
++ },
++ {
+ "id": "morale_vegetarian",
+ "type": "morale_type",
+ "text": "Ate Vegetables"
+
+--- a/data/json/mutations/mutation_ordering.json
++++ b/data/json/mutations/mutation_ordering.json
+@@ -75,6 +75,7 @@
+ "TROGLO2",
+ "TROGLO3",
+ "URSINE_FUR",
++ "VAMP_SKIN",
+ "VISCOUS"
+ ],
+ "order": 1500
+@@ -182,7 +183,7 @@
+ { "id": [ "FLOWERS" ], "order": 5000 },
+ { "id": [ "ELFA_EARS", "FELINE_EARS", "LUPINE_EARS", "URSINE_EARS" ], "order": 5500 },
+ { "id": [ "ANTENNAE", "ANTLERS", "CURVED_HORNS", "HORNS", "POINTED_HORNS" ], "order": 6000 },
+- { "id": [ "COMPOUND_EYES", "ELFAEYES", "FEL_EYE", "LIZ_EYE" ], "order": 6500 },
++ { "id": [ "COMPOUND_EYES", "ELFAEYES", "FEL_EYE", "LIZ_EYE", "VAMP_EYES" ], "order": 6500 },
+ {
+ "id": [
+ "BEAK",