--- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -190,6 +190,7 @@ static const itype_id itype_burnt_out_bionic( "burnt_out_bionic" ); static const itype_id itype_muscle( "muscle" ); +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" ); @@ -506,7 +507,8 @@ !corpse.in_species( species_ZOMBIE ) ); if( is_human && !( you.has_flag( json_flag_CANNIBAL ) || you.has_flag( json_flag_PSYCHOPATH ) || - you.has_flag( json_flag_SAPIOVORE ) ) ) { + you.has_flag( json_flag_SAPIOVORE ) || + you.has_flag( json_flag_VAMPIRE ) ) ) { if( you.is_avatar() ) { if( query_yn( _( "Would you dare desecrate the mortal remains of a fellow human being?" ) ) ) { --- a/src/character.cpp +++ b/src/character.cpp @@ -441,6 +441,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_PACIFIST( "PACIFIST" ); static const trait_id trait_PADDED_FEET( "PADDED_FEET" ); static const trait_id trait_PAINRESIST( "PAINRESIST" ); @@ -477,6 +480,8 @@ 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_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_WEB_SPINNER( "WEB_SPINNER" ); @@ -500,6 +505,7 @@ case blood_type::blood_A: return "A"; case blood_type::blood_B: return "B"; case blood_type::blood_AB: return "AB"; + case blood_type::blood_V: return "V"; // *INDENT-ON* case blood_type::num_bt: break; @@ -2270,6 +2276,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 ); } @@ -2337,8 +2346,10 @@ ( LIGHT_AMBIENT_LIT - LIGHT_AMBIENT_MINIMAL ) ); float range = get_per() / 3.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] ) { @@ -4274,7 +4285,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(); @@ -4732,19 +4751,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!" ), _( " has a sudden heart attack!" ) ); get_event_bus().send( 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." ), _( "'s breathing stops completely." ) ); get_event_bus().send( 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." ), @@ -4755,13 +4776,14 @@ } get_event_bus().send( 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." ), _( "'s heart spasms and stops." ) ); get_event_bus().send( 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." ), _( "'s breathing slows down to a stop." ) ); @@ -8844,7 +8866,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; } --- a/src/character_body.cpp +++ b/src/character_body.cpp @@ -76,6 +76,7 @@ static const trait_id trait_PYROMANIA( "PYROMANIA" ); static const trait_id trait_SLIMY( "SLIMY" ); static const trait_id trait_URSINE_FUR( "URSINE_FUR" ); +static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" ); static const vitamin_id vitamin_blood( "blood" ); @@ -402,7 +403,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; --- a/src/character.h +++ b/src/character.h @@ -120,6 +120,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, @@ -241,6 +242,7 @@ blood_A, blood_B, blood_AB, + blood_V, num_bt }; @@ -265,6 +267,8 @@ ALLERGY_WEAK, /// Penalty for eating human flesh (unless psycho/cannibal) CANNIBALISM, + /// Penalty for drinking human blood (unless vampire) + VAMPIRISM, /// Comestible has parasites PARASITES, /// Rotten (or, for saprophages, not rotten enough) --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -77,11 +77,17 @@ static const efftype_id effect_took_thorazine( "took_thorazine" ); static const efftype_id effect_visuals( "visuals" ); +static const flag_id json_flag_ALLERGEN_ALCOHOL( "ALLERGEN_ALCOHOL" ); static const flag_id json_flag_ALLERGEN_EGG( "ALLERGEN_EGG" ); +static const flag_id json_flag_ALLERGEN_FOODSTUFF( "ALLERGEN_FOODSTUFF" ); static const flag_id json_flag_ALLERGEN_FRUIT( "ALLERGEN_FRUIT" ); +static const flag_id json_flag_ALLERGEN_HONEY( "ALLERGEN_HONEY" ); +static const flag_id json_flag_ALLERGEN_JUNK( "ALLERGEN_JUNK" ); static const flag_id json_flag_ALLERGEN_MEAT( "ALLERGEN_MEAT" ); +static const flag_id json_flag_ALLERGEN_MILK( "ALLERGEN_MILK" ); static const flag_id json_flag_ALLERGEN_NUT( "ALLERGEN_NUT" ); static const flag_id json_flag_ALLERGEN_VEGGY( "ALLERGEN_VEGGY" ); +static const flag_id json_flag_ALLERGEN_WATER( "ALLERGEN_WATER" ); static const flag_id json_flag_ALLERGEN_WHEAT( "ALLERGEN_WHEAT" ); static const item_category_id item_category_chems( "chems" ); @@ -144,6 +150,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" ); @@ -154,6 +161,15 @@ json_flag_ALLERGEN_WHEAT, json_flag_ALLERGEN_NUT }}; +static const std::array vamp_blacklist {{ + json_flag_ALLERGEN_VEGGY, json_flag_ALLERGEN_FRUIT, + json_flag_ALLERGEN_WHEAT, json_flag_ALLERGEN_NUT, + json_flag_ALLERGEN_MEAT, json_flag_ALLERGEN_EGG, + json_flag_ALLERGEN_JUNK, json_flag_ALLERGEN_MILK, + json_flag_ALLERGEN_HONEY, json_flag_ALLERGEN_FOODSTUFF, + json_flag_ALLERGEN_ALCOHOL, json_flag_ALLERGEN_WATER + }}; + static const std::array herbivore_blacklist {{ json_flag_ALLERGEN_MEAT, json_flag_ALLERGEN_EGG }}; @@ -805,6 +821,12 @@ _( "Eww. Inedible plant stuff!" ) ); } + if( has_trait( trait_VAMP_HUNGER ) && food.has_any_flag( vamp_blacklist ) && + !food.has_flag( flag_VAMPIRISM_OK ) ) { + return ret_val::make_failure( INEDIBLE_MUTATION, + _( "Bleh. This isn't blood!" ) ); + } + if( ( has_trait( trait_HERBIVORE ) || has_trait( trait_RUMINANT ) ) && food.has_any_flag( herbivore_blacklist ) ) { // Like non-cannibal, but more strict! @@ -851,8 +873,15 @@ } } + const bool food_is_human_blood = food.has_flag( flag_VAMPIRISM ); + if( food_is_human_blood && !has_flag( STATIC( json_character_flag( "VAMPIRE" ) ) ) && + !has_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 ) || + const bool food_is_human_flesh = ( food.has_flag( flag_CANNIBALISM ) && + !food.has_flag( flag_VAMPIRISM ) ) || ( food.has_flag( flag_STRICT_HUMANITARIANISM ) && !has_flag( json_flag_STRICT_HUMANITARIAN ) ); if( food_is_human_flesh && !has_flag( STATIC( json_character_flag( "CANNIBAL" ) ) ) ) { @@ -1216,10 +1245,58 @@ } } + 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 ); + const bool numb = has_trait( trait_NUMB ); + 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 drank 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 if( numb ) { + add_msg_if_player( m_bad, _( "You find this drink distasteful, but necessary." ) ); + add_morale( MORALE_CANNIBAL, -60, -400, 60_minutes, 30_minutes ); + } else { + add_msg_if_player( m_bad, _( "You feel horrible for drinking of 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_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 @@ -1303,6 +1380,11 @@ add_msg_if_player( m_bad, _( "Your stomach begins gurgling and you feel bloated and ill." ) ); add_morale( allergy, -75, -400, 30_minutes, 24_minutes ); } + if( allergy != MORALE_NULL && has_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/debug_menu.cpp +++ b/src/debug_menu.cpp @@ -1728,6 +1728,7 @@ btype.addentry( static_cast( blood_type::blood_A ), true, '2', "A" ); btype.addentry( static_cast( blood_type::blood_B ), true, '3', "B" ); btype.addentry( static_cast( blood_type::blood_AB ), true, '4', "AB" ); + btype.addentry( static_cast( blood_type::blood_AB ), true, '4', "V" ); btype.query(); if( btype.ret < 0 ) { break; --- a/src/do_turn.cpp +++ b/src/do_turn.cpp @@ -95,8 +95,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_CANNIBAL ) || u.has_trait( trait_PSYCHOPATH ) ) ) { + u.has_amount( itype_holybook_bible3, 1 ) || u.has_trait( trait_id( "THRESH_REAL_VAMP" ) ) ) { + if( !( u.has_trait( trait_id( "CANNIBAL" ) ) || u.has_trait( trait_id( "PSYCHOPATH" ) ) || + u.has_trait( trait_id( "THRESH_REAL_VAMP" ) ) ) ) { vRip.emplace_back( " _______ ___" ); vRip.emplace_back( " < `/ |" ); vRip.emplace_back( " > _ _ (" ); --- a/src/flag.cpp +++ b/src/flag.cpp @@ -15,13 +15,17 @@ 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" ); const flag_id flag_ALLERGEN_NUT( "ALLERGEN_NUT" ); const flag_id flag_ALLERGEN_VEGGY( "ALLERGEN_VEGGY" ); +const flag_id flag_ALLERGEN_WATER( "ALLERGEN_WATER" ); const flag_id flag_ALLERGEN_WHEAT( "ALLERGEN_WHEAT" ); const flag_id flag_ALLERGEN_WOOL( "ALLERGEN_WOOL" ); const flag_id flag_ALLOWS_NATURAL_ATTACKS( "ALLOWS_NATURAL_ATTACKS" ); @@ -337,6 +341,9 @@ 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_VAMPIRE( "VAMPIRE" ); +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 @@ -24,13 +24,17 @@ 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_FRUIT; +extern const flag_id flag_ALLERGEN_FOODSTUFF; +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; extern const flag_id flag_ALLERGEN_NUT; extern const flag_id flag_ALLERGEN_VEGGY; +extern const flag_id flag_ALLERGEN_WATER; extern const flag_id flag_ALLERGEN_WHEAT; extern const flag_id flag_ALLERGEN_WOOL; extern const flag_id flag_ALLOWS_NATURAL_ATTACKS; @@ -342,6 +346,9 @@ 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_VAMPIRE; +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/item.cpp +++ b/src/item.cpp @@ -170,6 +170,7 @@ static const itype_id itype_waterproof_gunmod( "waterproof_gunmod" ); static const json_character_flag json_flag_CANNIBAL( "CANNIBAL" ); +static const json_character_flag json_flag_VAMPIRE( "VAMPIRE" ); static const json_character_flag json_flag_IMMUNE_SPOIL( "IMMUNE_SPOIL" ); static const matec_id RAPID( "RAPID" ); @@ -2477,7 +2480,24 @@ _( "* This food will cause an allergic reaction." ) ); } + if( food_item->has_flag( flag_VAMPIRISM ) && + parts->test( iteminfo_parts::FOOD_VAMPIRISM ) ) { + if( !player_character.has_flag( json_flag_CANNIBAL ) && + !player_character.has_flag( json_flag_VAMPIRE ) ) { + info.emplace_back( "DESCRIPTION", + _( "* This food contains human flesh." ) ); + } else if( player_character.has_flag( json_flag_CANNIBAL ) && + !player_character.has_flag( json_flag_VAMPIRE ) ) { + info.emplace_back( "DESCRIPTION", + _( "* This food contains human flesh." ) ); + } else { + info.emplace_back( "DESCRIPTION", + _( "* This food contains human blood." ) ); + } + } + if( food_item->has_flag( flag_CANNIBALISM ) && + !food_item->has_flag( flag_VAMPIRISM ) && parts->test( iteminfo_parts::FOOD_CANNIBALISM ) ) { if( !player_character.has_flag( json_flag_CANNIBAL ) ) { info.emplace_back( "DESCRIPTION", @@ -2488,7 +2508,8 @@ } } - 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 tainted and will poison you." ) ); } @@ -5959,6 +5980,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 @@ -85,10 +85,13 @@ static const item_group_id Item_spawn_data_EMPTY_GROUP( "EMPTY_GROUP" ); +static const material_id material_alcohol( "alcohol" ); static const material_id material_bean( "bean" ); +static const material_id material_blood( "blood" ); static const material_id material_egg( "egg" ); static const material_id material_flesh( "flesh" ); static const material_id material_fruit( "fruit" ); +static const material_id material_foodplace_foodstuff( "foodplace_foodstuff" ); static const material_id material_garlic( "garlic" ); static const material_id material_hflesh( "hflesh" ); static const material_id material_honey( "honey" ); @@ -101,6 +104,7 @@ static const material_id material_oil( "oil" ); static const material_id material_tomato( "tomato" ); static const material_id material_veggy( "veggy" ); +static const material_id material_water( "water" ); static const material_id material_wheat( "wheat" ); static const material_id material_wool( "wool" ); @@ -3356,10 +3360,11 @@ // Set for all items (not just food and clothing) to avoid edge cases void Item_factory::set_allergy_flags( itype &item_template ) { - static const std::array, 22> all_pairs = { { + static const std::array, 29> all_pairs = { { // First allergens: // An item is an allergen even if it has trace amounts of allergenic material { material_hflesh, flag_CANNIBALISM }, + { material_hflesh, flag_VAMPIRISM }, { material_hflesh, flag_ALLERGEN_MEAT }, { material_iflesh, flag_ALLERGEN_MEAT }, @@ -3374,10 +3379,16 @@ { material_mushroom, flag_ALLERGEN_VEGGY }, { material_milk, flag_ALLERGEN_MILK }, { material_egg, flag_ALLERGEN_EGG }, + { material_alcohol, flag_ALLERGEN_ALCOHOL }, + { material_foodplace_foodstuff, flag_ALLERGEN_FOODSTUFF }, + { material_water, flag_ALLERGEN_WATER }, + { material_honey, flag_ALLERGEN_HONEY }, { material_junk, flag_ALLERGEN_JUNK }, // Not food, but we can keep it here { material_wool, flag_ALLERGEN_WOOL }, // Now "made of". Those flags should not be passed + { material_blood, flag_VAMPIRISM_OK }, + { material_blood, flag_CARNIVORE_OK }, { material_flesh, flag_CARNIVORE_OK }, { material_hflesh, flag_CARNIVORE_OK }, { material_iflesh, flag_CARNIVORE_OK }, --- a/src/iteminfo_query.h +++ b/src/iteminfo_query.h @@ -44,6 +44,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_MYCUS( "THRESH_MYCUS" ); static const trait_id trait_THRESH_PLANT( "THRESH_PLANT" ); static const trait_id trait_TOLERANCE( "TOLERANCE" ); +static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" ); static const trait_id trait_WAYFARER( "WAYFARER" ); static const vitamin_id vitamin_blood( "blood" ); @@ -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 a warning: 'No heartbeat detected. " + "This device must be worn to provide fitness feedback.'" ), it->tname() ); } else { //What else should block using f-band? std::string msg; --- a/src/map_field.cpp +++ b/src/map_field.cpp @@ -94,6 +94,7 @@ static const trait_id trait_THRESH_INSECT( "THRESH_INSECT" ); 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_THRESH_SPIDER( "THRESH_SPIDER" ); using namespace map_field_processing; @@ -1623,7 +1624,8 @@ if( ( cur.get_field_intensity() > 1 || !one_in( 3 ) ) && ( !inside || one_in( 3 ) ) ) { you.add_env_effect( effect_teargas, bodypart_id( "mouth" ), 5, 20_seconds ); } - if( cur.get_field_intensity() > 1 && ( !inside || one_in( 3 ) ) ) { + if( !( you.has_trait( trait_VAMP_SKIN ) ) && cur.get_field_intensity() > 1 && ( !inside || + one_in( 3 ) ) ) { you.add_env_effect( effect_blind, bodypart_id( "eyes" ), cur.get_field_intensity() * 2, 10_seconds ); } @@ -1748,7 +1750,8 @@ // The gas won't harm you inside a vehicle. if( !inside ) { // Full body suits protect you from the effects of the gas. - if( !( you.worn_with_flag( STATIC( flag_id( "GAS_PROOF" ) ) ) && + if( !( you.has_trait( trait_VAMP_SKIN ) ) && + !( you.worn_with_flag( STATIC( flag_id( "GAS_PROOF" ) ) ) && you.get_env_resist( bodypart_id( "mouth" ) ) >= 15 && you.get_env_resist( bodypart_id( "eyes" ) ) >= 15 ) ) { const int intensity = cur.get_field_intensity(); --- a/src/memorial_logger.cpp +++ b/src/memorial_logger.cpp @@ -60,6 +60,7 @@ static const trait_id trait_CANNIBAL( "CANNIBAL" ); static const trait_id trait_PSYCHOPATH( "PSYCHOPATH" ); +static const trait_id trait_THRESH_REAL_VAMP( "THRESH_REAL_VAMP" ); static const trait_id trait_SAPIOVORE( "SAPIOVORE" ); memorial_log_entry::memorial_log_entry( const std::string &preformatted_msg ) : @@ -560,6 +561,7 @@ character_id ch = e.get( "killer" ); if( ch == avatar_id ) { std::string name = e.get( "victim_name" ); + bool vampire = player_character.has_trait( trait_THRESH_REAL_VAMP ); bool cannibal = player_character.has_trait( trait_CANNIBAL ); bool psycho = player_character.has_trait( trait_PSYCHOPATH ); if( player_character.has_trait( trait_SAPIOVORE ) ) { @@ -583,6 +585,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 @@ -101,6 +101,7 @@ const morale_type MORALE_BOOK( "morale_book" ); const morale_type MORALE_BUTCHER( "morale_butcher" ); const morale_type MORALE_CANNIBAL( "morale_cannibal" ); +const morale_type MORALE_VAMPIRE( "morale_vampire" ); const morale_type MORALE_CHAT( "morale_chat" ); const morale_type MORALE_COLD( "morale_cold" ); const morale_type MORALE_COMFY( "morale_comfy" ); @@ -138,6 +139,7 @@ static const morale_type morale_book( "morale_book" ); static const morale_type morale_butcher( "morale_butcher" ); static const morale_type morale_cannibal( "morale_cannibal" ); +static const morale_type morale_vampire( "morale_vampire" ); static const morale_type morale_chat( "morale_chat" ); static const morale_type morale_cold( "morale_cold" ); static const morale_type morale_comfy( "morale_comfy" ); --- a/src/morale_types.h +++ b/src/morale_types.h @@ -64,6 +64,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_ANTIVEGGY; extern const morale_type MORALE_MEATARIAN; --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -82,6 +82,7 @@ static const trait_id trait_SNAIL_TRAIL( "SNAIL_TRAIL" ); static const trait_id trait_STR_ALPHA( "STR_ALPHA" ); static const trait_id trait_TREE_COMMUNION( "TREE_COMMUNION" ); +static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" ); static const trait_id trait_VOMITOUS( "VOMITOUS" ); static const trait_id trait_WEB_WEAVER( "WEB_WEAVER" ); @@ -1032,6 +1033,11 @@ bool allow_bad = picked_bad; bool allow_neutral = true; + if( has_trait( trait_VAMP_SKIN ) ) { + add_msg_if_player( m_good, _( "Your Vampire blood quickly destroys the mutagenic contagion." ) ); + return; + } + if( true_random_chance > 0 && one_in( true_random_chance ) ) { cat = mutation_category_ANY; allow_good = true; // because i'm WILD YEAH --- a/src/npc.cpp +++ b/src/npc.cpp @@ -156,6 +156,7 @@ static const trait_id trait_SAPIOVORE( "SAPIOVORE" ); static const trait_id trait_SQUEAMISH( "SQUEAMISH" ); static const trait_id trait_TERRIFYING( "TERRIFYING" ); +static const trait_id trait_THRESH_REAL_VAMP( "THRESH_REAL_VAMP" ); class monfaction; @@ -3012,12 +3013,15 @@ Character &player_character = get_player_character(); if( killer == &player_character && ( !guaranteed_hostile() || hit_by_player ) ) { + bool vampire = player_character.has_trait( trait_THRESH_REAL_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 @@ -126,6 +126,8 @@ static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_SCHIZOPHRENIC( "SCHIZOPHRENIC" ); static const trait_id trait_THRESH_MYCUS( "THRESH_MYCUS" ); +static const trait_id trait_THRESH_REAL_VAMP( "THRESH_REAL_VAMP" ); +static const trait_id trait_VAMP_SKIN( "VAMP_SKIN" ); static const trait_id trait_WATERSLEEP( "WATERSLEEP" ); static const vitamin_id vitamin_blood( "blood" ); @@ -721,7 +723,9 @@ } if( dur > 1800_minutes && one_in( 300 * 512 ) ) { - if( !u.has_trait( trait_NOPAIN ) ) { + if( u.has_trait( trait_VAMP_SKIN ) ) { + return; + } else if( !u.has_trait( trait_NOPAIN ) ) { u.add_msg_if_player( m_bad, _( "Your heart spasms painfully and stops, dragging you back to reality as you die." ) ); } else { --- a/src/suffer.cpp +++ b/src/suffer.cpp @@ -176,6 +176,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" ); @@ -286,7 +288,7 @@ void suffer::while_underwater( Character &you ) { - if( !you.has_flag( json_flag_GILLS ) ) { + if( !you.has_flag( json_flag_GILLS ) && !you.has_trait( trait_VAMP_SKIN ) ) { you.oxygen--; } if( you.oxygen < 12 && you.worn_with_flag( flag_REBREATHER ) ) { @@ -862,12 +864,12 @@ you.vitamin_mod( vitamin_vitC, 1 ); } - if( you.has_trait( trait_SUNBURN ) ) { + if( you.has_trait( trait_SUNBURN ) || you.has_trait( trait_VAMP_CURSE ) ) { suffer::from_sunburn( you, true ); } // Albinism and datura have the same effects and do not stack with each other or sunburn. - if( !you.has_trait( trait_SUNBURN ) && + if( ( !you.has_trait( trait_SUNBURN ) && !you.has_trait( trait_VAMP_CURSE ) ) && ( you.has_trait( trait_ALBINO ) || you.has_effect( effect_datura ) ) ) { suffer::from_sunburn( you, false ); } @@ -895,6 +897,13 @@ you.mod_int_bonus( -4 ); you.mod_per_bonus( -4 ); } + if( you.has_trait( trait_VAMP_CURSE ) ) { + you.mod_str_bonus( -4 ); + you.mod_dex_bonus( -4 ); + you.add_miss_reason( _( "You can't tolerate the sunlight!" ), 4 ); + you.mod_int_bonus( -4 ); + you.mod_per_bonus( -4 ); + } } std::map Character::bodypart_exposure() @@ -948,7 +957,10 @@ void suffer::from_sunburn( Character &you, bool severe ) { // Sunburn effects and albinism/datura occur about once per minute - if( !one_turn_in( 1_minutes ) ) { + // Vampirism is ten times per minute + if( !one_turn_in( 1_minutes ) && !you.has_trait( trait_VAMP_CURSE ) ) { + return; + } else if( !one_turn_in( 6_seconds ) && you.has_trait( trait_VAMP_CURSE ) ) { return; } @@ -2034,7 +2046,7 @@ healing_factor *= mutation_value( "mending_modifier" ); - if( has_flag( json_flag_MEND_ALL ) ) { + if( has_flag( json_flag_MEND_ALL ) || has_trait( trait_VAMP_SKIN ) ) { needs_splint = false; } --- a/data/json/field_type.json +++ b/data/json/field_type.json @@ -376,7 +376,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", @@ -467,7 +467,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", @@ -553,7 +553,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", @@ -609,7 +609,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", @@ -638,7 +638,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 ], [ "eyes", 15 ] ], "traits": [ "VAMP_SKIN" ] }, "priority": 8, "half_life": "5 minutes", "phase": "gas", @@ -684,7 +684,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, @@ -1240,7 +1240,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", @@ -1262,7 +1262,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", @@ -1306,7 +1306,11 @@ "outdoor_age_speedup": "5 turns", "dirty_transparency_cache": true, "has_fume": true, - "immunity_data": { "flags": [ "MYCUS_IMMUNE" ], "body_part_env_resistance": [ [ "mouth", 15 ], [ "sensor", 15 ] ] }, + "immunity_data": { + "flags": [ "MYCUS_IMMUNE" ], + "body_part_env_resistance": [ [ "mouth", 15 ], [ "sensor", 15 ] ], + "traits": [ "VAMP_SKIN" ] + }, "priority": 8, "half_life": "4 minutes", "phase": "gas", @@ -1509,7 +1509,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" @@ -1529,7 +1529,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", @@ -1551,7 +1551,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,22 @@ "type": "json_flag" }, { + "id": "ALLERGEN_HONEY", + "type": "json_flag" + }, + { + "id": "ALLERGEN_ALCOHOL", + "type": "json_flag" + }, + { + "id": "ALLERGEN_FOODSTUFF", + "type": "json_flag" + }, + { + "id": "ALLERGEN_WATER", + "type": "json_flag" + }, + { "id": "ALLERGEN_JUNK", "type": "json_flag" }, @@ -1189,6 +1205,14 @@ "type": "json_flag" }, { + "id": "VAMPIRISM", + "type": "json_flag" + }, + { + "id": "VAMPIRISM_OK", + "type": "json_flag" + }, + { "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 @@ -80,6 +80,7 @@ "TROGLO2", "TROGLO3", "URSINE_FUR", + "VAMP_SKIN", "VISCOUS" ], "order": 1500 @@ -189,7 +190,7 @@ { "id": [ "FLOWERS" ], "order": 5000 }, { "id": [ "ELFA_EARS", "FELINE_EARS", "LUPINE_EARS", "RABBIT_EARS", "URSINE_EARS" ], "order": 5500 }, { "id": [ "ANTENNAE", "ANTLERS", "HORNS_CURLED", "HORNS", "HORNS_POINTED" ], "order": 6000 }, - { "id": [ "COMPOUND_EYES", "ELFAEYES", "FEL_EYE", "LIZ_EYE" ], "order": 6500 }, + { "id": [ "COMPOUND_EYES", "ELFAEYES", "FEL_EYE", "LIZ_EYE", "REAL_VAMP_EYES" ], "order": 6500 }, { "id": [ "BEAK",