diff options
Diffstat (limited to 'vampirism.patch')
-rw-r--r-- | vampirism.patch | 911 |
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", |